diff --git a/lib/src/backend/impls/objectbox/backend/backend.dart b/lib/src/backend/impls/objectbox/backend/backend.dart index a346fccf..b758954b 100644 --- a/lib/src/backend/impls/objectbox/backend/backend.dart +++ b/lib/src/backend/impls/objectbox/backend/backend.dart @@ -8,6 +8,7 @@ import 'dart:isolate'; import 'package:collection/collection.dart'; import 'package:flutter/services.dart'; +import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; @@ -39,16 +40,20 @@ final class FMTCObjectBoxBackend implements FMTCBackend { /// specify the application group (of less than 20 chars). See /// [the ObjectBox docs](https://docs.objectbox.io/getting-started) for /// details. + /// + /// Avoid using [useInMemoryDatabase] outside of testing purposes. @override Future initialise({ String? rootDirectory, int maxDatabaseSize = 10000000, String? macosApplicationGroup, + @visibleForTesting bool useInMemoryDatabase = false, }) => FMTCObjectBoxBackendInternal._instance.initialise( rootDirectory: rootDirectory, maxDatabaseSize: maxDatabaseSize, macosApplicationGroup: macosApplicationGroup, + useInMemoryDatabase: useInMemoryDatabase, ); /// {@macro fmtc.backend.uninitialise} diff --git a/lib/src/backend/impls/objectbox/backend/internal.dart b/lib/src/backend/impls/objectbox/backend/internal.dart index 570f713b..31b0f0a2 100644 --- a/lib/src/backend/impls/objectbox/backend/internal.dart +++ b/lib/src/backend/impls/objectbox/backend/internal.dart @@ -18,11 +18,10 @@ class _ObjectBoxBackendImpl implements FMTCObjectBoxBackendInternal { @override String get friendlyIdentifier => 'ObjectBox'; - @override - Directory? rootDirectory; - void get expectInitialised => _sendPort ?? (throw RootUnavailable()); + late String rootDirectory; + // Worker communication protocol storage SendPort? _sendPort; @@ -117,16 +116,21 @@ class _ObjectBoxBackendImpl implements FMTCObjectBoxBackendInternal { required String? rootDirectory, required int maxDatabaseSize, required String? macosApplicationGroup, + required bool useInMemoryDatabase, }) async { if (_sendPort != null) throw RootAlreadyInitialised(); - this.rootDirectory = await Directory( - path.join( - rootDirectory ?? - (await getApplicationDocumentsDirectory()).absolute.path, - 'fmtc', - ), - ).create(recursive: true); + if (useInMemoryDatabase) { + this.rootDirectory = Store.inMemoryPrefix + (rootDirectory ?? 'fmtc'); + } else { + await Directory( + this.rootDirectory = path.join( + rootDirectory ?? + (await getApplicationDocumentsDirectory()).absolute.path, + 'fmtc', + ), + ).create(recursive: true); + } // Prepare to recieve `SendPort` from worker _workerResOneShot[0] = Completer(); @@ -200,7 +204,7 @@ class _ObjectBoxBackendImpl implements FMTCObjectBoxBackendInternal { _worker, ( sendPort: receivePort.sendPort, - rootDirectory: this.rootDirectory!, + rootDirectory: this.rootDirectory, maxDatabaseSize: maxDatabaseSize, macosApplicationGroup: macosApplicationGroup, rootIsolateToken: ServicesBinding.rootIsolateToken!, @@ -389,7 +393,7 @@ class _ObjectBoxBackendImpl implements FMTCObjectBoxBackendInternal { required String url, }) async => (await _sendCmdOneShot( - type: _WorkerCmdType.deleteStore, + type: _WorkerCmdType.deleteTile, args: {'storeName': storeName, 'url': url}, ))!['wasOrphan']; diff --git a/lib/src/backend/impls/objectbox/backend/internal_worker.dart b/lib/src/backend/impls/objectbox/backend/internal_worker.dart index f7210e71..272427ad 100644 --- a/lib/src/backend/impls/objectbox/backend/internal_worker.dart +++ b/lib/src/backend/impls/objectbox/backend/internal_worker.dart @@ -65,7 +65,7 @@ enum _WorkerCmdType { Future _worker( ({ SendPort sendPort, - Directory rootDirectory, + String rootDirectory, int maxDatabaseSize, String? macosApplicationGroup, RootIsolateToken rootIsolateToken, @@ -85,7 +85,7 @@ Future _worker( late final Store root; try { root = await openStore( - directory: input.rootDirectory.absolute.path, + directory: input.rootDirectory, maxDBSizeInKB: input.maxDatabaseSize, // Defaults to 10 GB macosApplicationGroup: input.macosApplicationGroup, ); @@ -238,7 +238,11 @@ Future _worker( root.close(); if (cmd.args['deleteRoot'] == true) { - input.rootDirectory.deleteSync(recursive: true); + if (input.rootDirectory.startsWith(Store.inMemoryPrefix)) { + Store.removeDbFiles(input.rootDirectory); + } else { + Directory(input.rootDirectory).deleteSync(recursive: true); + } } sendRes(id: cmd.id); @@ -248,8 +252,8 @@ Future _worker( sendRes( id: cmd.id, data: { - 'size': Store.dbFileSize(input.rootDirectory.absolute.path) / - 1024, // Convert to KiB + 'size': + Store.dbFileSize(input.rootDirectory) / 1024, // Convert to KiB }, ); case _WorkerCmdType.rootSize: @@ -318,7 +322,7 @@ Future _worker( size: 0, hits: 0, misses: 0, - metadataJson: '', + metadataJson: '{}', ), mode: PutMode.insert, ); @@ -465,6 +469,8 @@ Future _worker( case _WorkerCmdType.writeTile: final storeName = cmd.args['storeName']! as String; final url = cmd.args['url']! as String; + + // TODO: `null` `bytes` is never actually used. Do we need to keep it? final bytes = cmd.args['bytes'] as Uint8List?; final tiles = root.box(); @@ -509,10 +515,6 @@ Future _worker( ..length += 1 ..size += existingTile.bytes.lengthInBytes, ); - updateRootStatistics( - deltaLength: 1, - deltaSize: existingTile.bytes.lengthInBytes, - ); } case (false, false): // Existing tile, update required final storesToUpdate = {}; @@ -715,12 +717,12 @@ Future _worker( (throw StoreNotExists(storeName: storeName)); query.close(); - final Map json = - store.metadataJson == '' ? {} : jsonDecode(store.metadataJson); - json[key] = value; - stores.put( - store..metadataJson = jsonEncode(json), + store + ..metadataJson = jsonEncode( + (jsonDecode(store.metadataJson) as Map) + ..[key] = value, + ), mode: PutMode.update, ); }, @@ -743,13 +745,12 @@ Future _worker( (throw StoreNotExists(storeName: storeName)); query.close(); - final Map json = - store.metadataJson == '' ? {} : jsonDecode(store.metadataJson); - // ignore: cascade_invocations - json.addAll(kvs); - stores.put( - store..metadataJson = jsonEncode(json), + store + ..metadataJson = jsonEncode( + (jsonDecode(store.metadataJson) as Map) + ..addAll(kvs), + ), mode: PutMode.update, ); }, @@ -776,8 +777,8 @@ Future _worker( query.close(); final metadata = - jsonDecode(store.metadataJson) as Map; - final removedVal = metadata.remove(key); + jsonDecode(store.metadataJson) as Map; + final removedVal = metadata.remove(key) as String?; stores.put( store..metadataJson = jsonEncode(metadata), @@ -805,7 +806,7 @@ Future _worker( query.close(); stores.put( - store..metadataJson = jsonEncode({}), + store..metadataJson = '{}', mode: PutMode.update, ); }, @@ -903,7 +904,7 @@ Future _worker( final outputDir = path.dirname(outputPath); - if (outputDir == input.rootDirectory.absolute.path) { + if (path.equals(outputDir, input.rootDirectory)) { throw ExportInRootDirectoryForbidden(); } @@ -1028,8 +1029,7 @@ Future _worker( final strategy = cmd.args['strategy'] as ImportConflictStrategy; final storesToImport = cmd.args['stores'] as List?; - final importDir = - path.join(input.rootDirectory.absolute.path, 'import_tmp'); + final importDir = path.join(input.rootDirectory, 'import_tmp'); final importDirIO = Directory(importDir)..createSync(); final importFile = @@ -1437,8 +1437,7 @@ Future _worker( case _WorkerCmdType.listImportableStores: final importPath = cmd.args['path']! as String; - final importDir = - path.join(input.rootDirectory.absolute.path, 'import_tmp'); + final importDir = path.join(input.rootDirectory, 'import_tmp'); final importDirIO = Directory(importDir)..createSync(); final importFile = diff --git a/lib/src/backend/interfaces/backend/internal.dart b/lib/src/backend/interfaces/backend/internal.dart index 5f46a976..55400632 100644 --- a/lib/src/backend/interfaces/backend/internal.dart +++ b/lib/src/backend/interfaces/backend/internal.dart @@ -2,9 +2,10 @@ // A full license can be found at .\LICENSE import 'dart:async'; -import 'dart:io'; import 'dart:typed_data'; +import 'package:meta/meta.dart'; + import '../../../../flutter_map_tile_caching.dart'; import '../../export_internal.dart'; @@ -32,12 +33,6 @@ abstract interface class FMTCBackendInternal /// Generic description/name of this backend abstract final String friendlyIdentifier; - /// The filesystem directory in use - /// - /// May also be used as an indicator as to whether the root has been - /// initialised. - Directory? get rootDirectory; - /// {@template fmtc.backend.realSize} /// Retrieve the actual total size of the database in KiBs /// @@ -86,6 +81,8 @@ abstract interface class FMTCBackendInternal /// /// This operation cannot be undone! Ensure you confirm with the user that /// this action is expected. + /// + /// Does nothing if the store does not already exist. /// {@endtemplate} Future deleteStore({ required String storeName, @@ -99,6 +96,8 @@ abstract interface class FMTCBackendInternal /// /// This operation cannot be undone! Ensure you confirm with the user that /// this action is expected. + /// + /// Does nothing if the store does not already exist. /// {@endtemplate} Future resetStore({ required String storeName, @@ -173,6 +172,7 @@ abstract interface class FMTCBackendInternal /// * `null` : if there was no existing tile /// * `true` : if the tile itself could be deleted (it was orphaned) /// * `false`: if the tile still belonged to at least one other store + @visibleForTesting Future deleteTile({ required String storeName, required String url, diff --git a/lib/src/bulk_download/manager.dart b/lib/src/bulk_download/manager.dart index fb2c5f91..c27e9a46 100644 --- a/lib/src/bulk_download/manager.dart +++ b/lib/src/bulk_download/manager.dart @@ -6,7 +6,6 @@ part of '../../flutter_map_tile_caching.dart'; Future _downloadManager( ({ SendPort sendPort, - String rootDirectory, DownloadableRegion region, String storeName, int parallelThreads, @@ -189,7 +188,6 @@ Future _downloadManager( ( sendPort: downloadThreadReceivePort.sendPort, storeName: input.storeName, - rootDirectory: input.rootDirectory, options: input.region.options, maxBufferLength: threadBufferLength, skipExistingTiles: input.skipExistingTiles, diff --git a/lib/src/bulk_download/thread.dart b/lib/src/bulk_download/thread.dart index cd26a1d3..6f512a7e 100644 --- a/lib/src/bulk_download/thread.dart +++ b/lib/src/bulk_download/thread.dart @@ -7,7 +7,6 @@ Future _singleDownloadThread( ({ SendPort sendPort, String storeName, - String rootDirectory, TileLayer options, int maxBufferLength, bool skipExistingTiles, diff --git a/lib/src/store/download.dart b/lib/src/store/download.dart index 3d5ad499..5c3674c0 100644 --- a/lib/src/store/download.dart +++ b/lib/src/store/download.dart @@ -173,7 +173,6 @@ class DownloadManagement { _downloadManager, ( sendPort: receivePort.sendPort, - rootDirectory: FMTCBackendAccess.internal.rootDirectory!.absolute.path, region: region, storeName: _storeDirectory.storeName, parallelThreads: parallelThreads, diff --git a/lib/src/store/metadata.dart b/lib/src/store/metadata.dart index ec96216a..7a7a1a4f 100644 --- a/lib/src/store/metadata.dart +++ b/lib/src/store/metadata.dart @@ -33,7 +33,7 @@ class StoreMetadata { .setBulkMetadata(storeName: _storeName, kvs: kvs); /// {@macro fmtc.backend.removeMetadata} - Future remove({ + Future remove({ required String key, }) => FMTCBackendAccess.internal diff --git a/lib/src/store/store.dart b/lib/src/store/store.dart index 95a8af23..0031c624 100644 --- a/lib/src/store/store.dart +++ b/lib/src/store/store.dart @@ -82,4 +82,7 @@ class FMTCStore { @override int get hashCode => storeName.hashCode; + + @override + String toString() => 'FMTCStore(storeName: $storeName)'; } diff --git a/test/general_test.dart b/test/general_test.dart new file mode 100644 index 00000000..8bdf89ab --- /dev/null +++ b/test/general_test.dart @@ -0,0 +1,469 @@ +// Copyright © Luka S (JaffaKetchup) under GPL-v3 +// A full license can be found at .\LICENSE + +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:flutter_map_tile_caching/custom_backend_api.dart'; +import 'package:flutter_map_tile_caching/flutter_map_tile_caching.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; + +// bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-dart/main/install.sh) + +void main() { + setUpAll(() { + // Necessary to locate the ObjectBox libs + Directory.current = + Directory(p.join(Directory.current.absolute.path, 'test')); + }); + + group( + 'Basic store usage & root stats consistency', + () { + setUpAll( + () => FMTCObjectBoxBackend().initialise(useInMemoryDatabase: true), + ); + + test( + 'Initially zero/empty', + () async { + expect(await FMTCRoot.stats.length, 0); + expect(await FMTCRoot.stats.size, 0); + expect(await FMTCRoot.stats.storesAvailable, []); + }, + ); + + test( + '"store1" creation', + () async { + await const FMTCStore('store1').manage.create(); + + expect(await FMTCRoot.stats.length, 0); + expect(await FMTCRoot.stats.size, 0); + expect( + await FMTCRoot.stats.storesAvailable, + [const FMTCStore('store1')], + ); + }, + ); + + test( + 'Duplicate creation allowed', + () async { + await const FMTCStore('store1').manage.create(); + + expect(await FMTCRoot.stats.length, 0); + expect(await FMTCRoot.stats.size, 0); + expect( + await FMTCRoot.stats.storesAvailable, + [const FMTCStore('store1')], + ); + }, + ); + + test( + '"store2" creation', + () async { + await const FMTCStore('store2').manage.create(); + + expect(await FMTCRoot.stats.length, 0); + expect(await FMTCRoot.stats.size, 0); + expect( + await FMTCRoot.stats.storesAvailable, + [const FMTCStore('store1'), const FMTCStore('store2')], + ); + }, + ); + + test( + '"store2" deletion', + () async { + await const FMTCStore('store2').manage.delete(); + + expect(await FMTCRoot.stats.length, 0); + expect(await FMTCRoot.stats.size, 0); + expect( + await FMTCRoot.stats.storesAvailable, + [const FMTCStore('store1')], + ); + }, + ); + + test( + 'Duplicate deletion allowed', + () async { + await const FMTCStore('store2').manage.delete(); + + expect(await FMTCRoot.stats.length, 0); + expect(await FMTCRoot.stats.size, 0); + expect( + await FMTCRoot.stats.storesAvailable, + [const FMTCStore('store1')], + ); + }, + ); + + test( + 'Cannot reset/rename "store2"', + () async { + expect( + () => const FMTCStore('store2').manage.reset(), + throwsA(const TypeMatcher()), + ); + expect( + () => const FMTCStore('store2').manage.rename('store0'), + throwsA(const TypeMatcher()), + ); + + expect(await FMTCRoot.stats.length, 0); + expect(await FMTCRoot.stats.size, 0); + expect( + await FMTCRoot.stats.storesAvailable, + [const FMTCStore('store1')], + ); + }, + ); + + test( + '"store1" reset', + () async { + await const FMTCStore('store1').manage.reset(); + + expect(await FMTCRoot.stats.length, 0); + expect(await FMTCRoot.stats.size, 0); + expect( + await FMTCRoot.stats.storesAvailable, + [const FMTCStore('store1')], + ); + }, + ); + + test( + '"store1" rename to "store3"', + () async { + await const FMTCStore('store1').manage.rename('store3'); + + expect(await FMTCRoot.stats.length, 0); + expect(await FMTCRoot.stats.size, 0); + expect( + await FMTCRoot.stats.storesAvailable, + [const FMTCStore('store3')], + ); + }, + ); + + tearDownAll( + () => FMTCObjectBoxBackend() + .uninitialise(deleteRoot: true, immediate: true), + ); + }, + timeout: const Timeout(Duration(seconds: 1)), + ); + + group( + 'Metadata', + () { + setUpAll(() async { + await FMTCObjectBoxBackend().initialise(useInMemoryDatabase: true); + await const FMTCStore('store').manage.create(); + }); + + test( + 'Initially empty', + () async { + expect(await const FMTCStore('store').metadata.read, {}); + }, + ); + + test( + 'Write', + () async { + await const FMTCStore('store') + .metadata + .set(key: 'key', value: 'value'); + expect( + await const FMTCStore('store').metadata.read, + {'key': 'value'}, + ); + }, + ); + + test( + 'Overwrite', + () async { + await const FMTCStore('store') + .metadata + .set(key: 'key', value: 'value2'); + expect( + await const FMTCStore('store').metadata.read, + {'key': 'value2'}, + ); + }, + ); + + test( + 'Bulk (over)write', + () async { + await const FMTCStore('store') + .metadata + .setBulk(kvs: {'key': 'value3', 'key2': 'value4'}); + expect( + await const FMTCStore('store').metadata.read, + {'key': 'value3', 'key2': 'value4'}, + ); + }, + ); + + test( + 'Remove existing', + () async { + expect( + await const FMTCStore('store').metadata.remove(key: 'key2'), + 'value4', + ); + expect( + await const FMTCStore('store').metadata.read, + {'key': 'value3'}, + ); + }, + ); + + test( + 'Remove non-existent', + () async { + expect( + await const FMTCStore('store').metadata.remove(key: 'key3'), + null, + ); + expect( + await const FMTCStore('store').metadata.read, + {'key': 'value3'}, + ); + }, + ); + + test( + 'Reset', + () async { + await const FMTCStore('store').metadata.reset(); + expect(await const FMTCStore('store').metadata.read, {}); + }, + ); + + tearDownAll( + () => FMTCObjectBoxBackend() + .uninitialise(deleteRoot: true, immediate: true), + ); + }, + timeout: const Timeout(Duration(seconds: 1)), + ); + + group( + 'Tile operations & stats consistency', + () { + setUpAll(() async { + await FMTCObjectBoxBackend().initialise(useInMemoryDatabase: true); + await const FMTCStore('store1').manage.create(); + await const FMTCStore('store2').manage.create(); + }); + + final tileA64 = + (url: 'https://example.com/0/0/0.png', bytes: Uint8List(64)); + final tileA128 = + (url: 'https://example.com/0/0/0.png', bytes: Uint8List(128)); + final tileB64 = + (url: 'https://example.com/1/1/1.png', bytes: Uint8List(64)); + final tileB128 = + (url: 'https://example.com/1/1/1.png', bytes: Uint8List(128)); + + test( + 'Initially semi-zero/empty', + () async { + expect( + await const FMTCStore('store1').stats.all, + (length: 0, size: 0, hits: 0, misses: 0), + ); + expect( + await const FMTCStore('store2').stats.all, + (length: 0, size: 0, hits: 0, misses: 0), + ); + expect(await FMTCRoot.stats.length, 0); + expect(await FMTCRoot.stats.size, 0); + expect( + await FMTCRoot.stats.storesAvailable, + [const FMTCStore('store1'), const FMTCStore('store2')], + ); + }, + ); + + test( + 'Write tile (A64) to "store1"', + () async { + await FMTCBackendAccess.internal.writeTile( + storeName: 'store1', + url: tileA64.url, + bytes: tileA64.bytes, + ); + expect( + await const FMTCStore('store1').stats.all, + (length: 1, size: 0.0625, hits: 0, misses: 0), + ); + expect(await FMTCRoot.stats.length, 1); + expect(await FMTCRoot.stats.size, 0.0625); + }, + ); + + test( + 'Write tile (A64) again to "store1"', + () async { + await FMTCBackendAccess.internal.writeTile( + storeName: 'store1', + url: tileA64.url, + bytes: tileA64.bytes, + ); + expect( + await const FMTCStore('store1').stats.all, + (length: 1, size: 0.0625, hits: 0, misses: 0), + ); + expect(await FMTCRoot.stats.length, 1); + expect(await FMTCRoot.stats.size, 0.0625); + }, + ); + + test( + 'Write tile (A128) to "store1"', + () async { + await FMTCBackendAccess.internal.writeTile( + storeName: 'store1', + url: tileA128.url, + bytes: tileA128.bytes, + ); + expect( + await const FMTCStore('store1').stats.all, + (length: 1, size: 0.125, hits: 0, misses: 0), + ); + expect(await FMTCRoot.stats.length, 1); + expect(await FMTCRoot.stats.size, 0.125); + }, + ); + + test( + 'Write tile (B64) to "store1"', + () async { + await FMTCBackendAccess.internal.writeTile( + storeName: 'store1', + url: tileB64.url, + bytes: tileB64.bytes, + ); + expect( + await const FMTCStore('store1').stats.all, + (length: 2, size: 0.1875, hits: 0, misses: 0), + ); + expect(await FMTCRoot.stats.length, 2); + expect(await FMTCRoot.stats.size, 0.1875); + }, + ); + + test( + 'Write tile (B128) to "store1"', + () async { + await FMTCBackendAccess.internal.writeTile( + storeName: 'store1', + url: tileB128.url, + bytes: tileB128.bytes, + ); + expect( + await const FMTCStore('store1').stats.all, + (length: 2, size: 0.25, hits: 0, misses: 0), + ); + expect(await FMTCRoot.stats.length, 2); + expect(await FMTCRoot.stats.size, 0.25); + }, + ); + + test( + 'Write tile (B64) again to "store1"', + () async { + await FMTCBackendAccess.internal.writeTile( + storeName: 'store1', + url: tileB64.url, + bytes: tileB64.bytes, + ); + expect( + await const FMTCStore('store1').stats.all, + (length: 2, size: 0.1875, hits: 0, misses: 0), + ); + expect(await FMTCRoot.stats.length, 2); + expect(await FMTCRoot.stats.size, 0.1875); + }, + ); + + test( + 'Delete tile (B(64)) from "store1"', + () async { + await FMTCBackendAccess.internal.deleteTile( + storeName: 'store1', + url: tileB128.url, + ); + expect( + await const FMTCStore('store1').stats.all, + (length: 1, size: 0.125, hits: 0, misses: 0), + ); + expect(await FMTCRoot.stats.length, 1); + expect(await FMTCRoot.stats.size, 0.125); + }, + ); + + test( + 'Semi-write tile (A(128)) to "store2"', + () async { + await FMTCBackendAccess.internal.writeTile( + storeName: 'store2', + url: tileA128.url, + bytes: null, + ); + expect( + await const FMTCStore('store1').stats.all, + (length: 1, size: 0.125, hits: 0, misses: 0), + ); + expect( + await const FMTCStore('store2').stats.all, + (length: 1, size: 0.125, hits: 0, misses: 0), + ); + expect(await FMTCRoot.stats.length, 1); + expect(await FMTCRoot.stats.size, 0.125); + }, + ); + + test( + 'Semi-delete tile (A(128)) from "store2"', + () async { + await FMTCBackendAccess.internal.deleteTile( + storeName: 'store2', + url: tileA128.url, + ); + expect( + await const FMTCStore('store1').stats.all, + (length: 1, size: 0.125, hits: 0, misses: 0), + ); + expect( + await const FMTCStore('store2').stats.all, + (length: 0, size: 0, hits: 0, misses: 0), + ); + expect(await FMTCRoot.stats.length, 1); + expect(await FMTCRoot.stats.size, 0.125); + }, + ); + // then real semi-write to store2 + // then delete as above + // then other sizes + + tearDownAll( + () => FMTCObjectBoxBackend() + .uninitialise(deleteRoot: true, immediate: true), + ); + }, + timeout: const Timeout(Duration(seconds: 1)), + ); +} diff --git a/test/lib/objectbox.dll b/test/lib/objectbox.dll new file mode 100644 index 00000000..cbf26108 Binary files /dev/null and b/test/lib/objectbox.dll differ diff --git a/test/lib/objectbox.lib b/test/lib/objectbox.lib new file mode 100644 index 00000000..711c8f3f Binary files /dev/null and b/test/lib/objectbox.lib differ diff --git a/test/fmtc_test.dart b/test/region_tile_test.dart similarity index 100% rename from test/fmtc_test.dart rename to test/region_tile_test.dart