From e801dceae67980cb8228f684f0f68990a89ca738 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sun, 11 Jun 2023 17:44:04 +0200 Subject: [PATCH] Add wasm integration test using the database --- .../web_wasm/lib/driver.dart | 20 +- .../web_wasm/lib/src/database.dart | 16 ++ .../web_wasm/lib/src/database.g.dart | 186 ++++++++++++++++++ .../integration_tests/web_wasm/pubspec.yaml | 1 + .../web_wasm/test/drift_wasm_test.dart | 94 +++++++++ .../web_wasm/tool/drift_wasm_test.dart | 62 ------ .../integration_tests/web_wasm/web/main.dart | 52 +++-- 7 files changed, 349 insertions(+), 82 deletions(-) create mode 100644 extras/integration_tests/web_wasm/lib/src/database.dart create mode 100644 extras/integration_tests/web_wasm/lib/src/database.g.dart create mode 100644 extras/integration_tests/web_wasm/test/drift_wasm_test.dart delete mode 100644 extras/integration_tests/web_wasm/tool/drift_wasm_test.dart diff --git a/extras/integration_tests/web_wasm/lib/driver.dart b/extras/integration_tests/web_wasm/lib/driver.dart index 221070bd..c1e7fec1 100644 --- a/extras/integration_tests/web_wasm/lib/driver.dart +++ b/extras/integration_tests/web_wasm/lib/driver.dart @@ -1,11 +1,13 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:isolate'; import 'package:build_daemon/client.dart'; import 'package:build_daemon/constants.dart'; import 'package:build_daemon/data/build_status.dart'; import 'package:build_daemon/data/build_target.dart'; import 'package:collection/collection.dart'; +import 'package:package_config/package_config.dart'; import 'package:path/path.dart' as p; import 'package:shelf/shelf_io.dart'; import 'package:shelf_proxy/shelf_proxy.dart'; @@ -27,8 +29,13 @@ class TestAssetServer { } static Future start() async { - final script = Platform.script.toFilePath(windows: Platform.isWindows); - final packageDir = p.dirname(p.dirname(script)); + final packageConfig = + await loadPackageConfigUri((await Isolate.packageConfig)!); + final ownPackage = packageConfig['web_wasm']!.root; + var packageDir = ownPackage.toFilePath(windows: Platform.isWindows); + if (packageDir.endsWith('/')) { + packageDir = packageDir.substring(0, packageDir.length - 1); + } final buildRunner = await BuildDaemonClient.connect( packageDir, @@ -92,8 +99,8 @@ class DriftWebDriver { Set storages, Set missingFeatures, })> probeImplementations() async { - final rawResult = - await driver.executeAsync('detectImplementations(arguments[0])', []); + final rawResult = await driver + .executeAsync('detectImplementations("", arguments[0])', []); final result = json.decode(rawResult); return ( @@ -107,4 +114,9 @@ class DriftWebDriver { }, ); } + + Future openDatabase([WasmStorageImplementation? implementation]) async { + await driver.executeAsync( + 'open(arguments[0], arguments[1])', [implementation?.name]); + } } diff --git a/extras/integration_tests/web_wasm/lib/src/database.dart b/extras/integration_tests/web_wasm/lib/src/database.dart new file mode 100644 index 00000000..c9c64c32 --- /dev/null +++ b/extras/integration_tests/web_wasm/lib/src/database.dart @@ -0,0 +1,16 @@ +import 'package:drift/drift.dart'; + +part 'database.g.dart'; + +class TestTable extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get content => text()(); +} + +@DriftDatabase(tables: [TestTable]) +class TestDatabase extends _$TestDatabase { + TestDatabase(super.e); + + @override + int get schemaVersion => 1; +} diff --git a/extras/integration_tests/web_wasm/lib/src/database.g.dart b/extras/integration_tests/web_wasm/lib/src/database.g.dart new file mode 100644 index 00000000..9abedd16 --- /dev/null +++ b/extras/integration_tests/web_wasm/lib/src/database.g.dart @@ -0,0 +1,186 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'database.dart'; + +// ignore_for_file: type=lint +class $TestTableTable extends TestTable + with TableInfo<$TestTableTable, TestTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TestTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _contentMeta = + const VerificationMeta('content'); + @override + late final GeneratedColumn content = GeneratedColumn( + 'content', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, content]; + @override + String get aliasedName => _alias ?? 'test_table'; + @override + String get actualTableName => 'test_table'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('content')) { + context.handle(_contentMeta, + content.isAcceptableOrUnknown(data['content']!, _contentMeta)); + } else if (isInserting) { + context.missing(_contentMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + TestTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TestTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + content: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}content'])!, + ); + } + + @override + $TestTableTable createAlias(String alias) { + return $TestTableTable(attachedDatabase, alias); + } +} + +class TestTableData extends DataClass implements Insertable { + final int id; + final String content; + const TestTableData({required this.id, required this.content}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['content'] = Variable(content); + return map; + } + + TestTableCompanion toCompanion(bool nullToAbsent) { + return TestTableCompanion( + id: Value(id), + content: Value(content), + ); + } + + factory TestTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TestTableData( + id: serializer.fromJson(json['id']), + content: serializer.fromJson(json['content']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'content': serializer.toJson(content), + }; + } + + TestTableData copyWith({int? id, String? content}) => TestTableData( + id: id ?? this.id, + content: content ?? this.content, + ); + @override + String toString() { + return (StringBuffer('TestTableData(') + ..write('id: $id, ') + ..write('content: $content') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, content); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TestTableData && + other.id == this.id && + other.content == this.content); +} + +class TestTableCompanion extends UpdateCompanion { + final Value id; + final Value content; + const TestTableCompanion({ + this.id = const Value.absent(), + this.content = const Value.absent(), + }); + TestTableCompanion.insert({ + this.id = const Value.absent(), + required String content, + }) : content = Value(content); + static Insertable custom({ + Expression? id, + Expression? content, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (content != null) 'content': content, + }); + } + + TestTableCompanion copyWith({Value? id, Value? content}) { + return TestTableCompanion( + id: id ?? this.id, + content: content ?? this.content, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (content.present) { + map['content'] = Variable(content.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TestTableCompanion(') + ..write('id: $id, ') + ..write('content: $content') + ..write(')')) + .toString(); + } +} + +abstract class _$TestDatabase extends GeneratedDatabase { + _$TestDatabase(QueryExecutor e) : super(e); + late final $TestTableTable testTable = $TestTableTable(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [testTable]; +} diff --git a/extras/integration_tests/web_wasm/pubspec.yaml b/extras/integration_tests/web_wasm/pubspec.yaml index f28deb1f..843705fb 100644 --- a/extras/integration_tests/web_wasm/pubspec.yaml +++ b/extras/integration_tests/web_wasm/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: shelf_proxy: ^1.0.4 path: ^1.8.3 js: ^0.6.7 + package_config: ^2.1.0 dev_dependencies: build_runner: ^2.4.5 diff --git a/extras/integration_tests/web_wasm/test/drift_wasm_test.dart b/extras/integration_tests/web_wasm/test/drift_wasm_test.dart new file mode 100644 index 00000000..4ecc1160 --- /dev/null +++ b/extras/integration_tests/web_wasm/test/drift_wasm_test.dart @@ -0,0 +1,94 @@ +// ignore: implementation_imports +import 'dart:io'; + +import 'package:drift/src/web/wasm_setup/types.dart'; +import 'package:test/test.dart'; +import 'package:web_wasm/driver.dart'; +import 'package:webdriver/async_io.dart'; + +enum Browser { + chrome( + driverUriString: 'http://localhost:4444/wd/hub/', + isChromium: true, + unsupportedImplementations: {WasmStorageImplementation.opfsShared}, + missingFeatures: {MissingBrowserFeature.dedicatedWorkersInSharedWorkers}, + ), + firefox(driverUriString: 'http://localhost:4444/'); + + final bool isChromium; + final String driverUriString; + final Set unsupportedImplementations; + final Set missingFeatures; + + const Browser({ + required this.driverUriString, + this.isChromium = false, + this.unsupportedImplementations = const {}, + this.missingFeatures = const {}, + }); + + Uri get driverUri => Uri.parse(driverUriString); + + Set get availableImplementations { + return WasmStorageImplementation.values.toSet() + ..removeAll(unsupportedImplementations); + } + + Future spawnDriver() async { + return switch (this) { + firefox => Process.start('geckodriver', []), + chrome => + Process.start('chromedriver', ['--port=4444', '--url-base=/wd/hub']), + }; + } +} + +void main() { + late TestAssetServer server; + + setUpAll(() async { + server = await TestAssetServer.start(); + }); + tearDownAll(() => server.close()); + + for (final browser in Browser.values) { + group(browser.name, () { + late Process driverProcess; + late DriftWebDriver driver; + + setUpAll(() async => driverProcess = await browser.spawnDriver()); + tearDownAll(() => driverProcess.kill()); + + setUp(() async { + final rawDriver = await createDriver( + spec: browser.isChromium ? WebDriverSpec.JsonWire : WebDriverSpec.W3c, + uri: browser.driverUri, + ); + + driver = DriftWebDriver(server, rawDriver); + + await driver.driver.get('http://localhost:8080/'); + }); + + tearDown(() => driver.driver.quit()); + + test('compatibility check', () async { + final result = await driver.probeImplementations(); + + final expectedImplementations = WasmStorageImplementation.values.toSet() + ..removeAll(browser.unsupportedImplementations); + + expect(result.missingFeatures, browser.missingFeatures); + expect(result.storages, expectedImplementations); + }); + + group('supports', () { + for (final entry in browser.availableImplementations) { + test(entry.name, () async { + await driver.openDatabase(); + }); + } + }); + }); + } +} diff --git a/extras/integration_tests/web_wasm/tool/drift_wasm_test.dart b/extras/integration_tests/web_wasm/tool/drift_wasm_test.dart deleted file mode 100644 index cd3381f2..00000000 --- a/extras/integration_tests/web_wasm/tool/drift_wasm_test.dart +++ /dev/null @@ -1,62 +0,0 @@ -// ignore: implementation_imports -import 'package:drift/src/web/wasm_setup/types.dart'; -import 'package:test/test.dart'; -import 'package:web_wasm/driver.dart'; -import 'package:webdriver/async_io.dart'; - -enum Browser { - chrome( - isChromium: true, - unsupportedImplementations: {WasmStorageImplementation.opfsShared}, - missingFeatures: {MissingBrowserFeature.dedicatedWorkersInSharedWorkers}, - ), - firefox(); - - final bool isChromium; - final Set unsupportedImplementations; - final Set missingFeatures; - - const Browser({ - this.isChromium = false, - this.unsupportedImplementations = const {}, - this.missingFeatures = const {}, - }); -} - -void main(List args) { - final browser = Browser.values.byName(args[0]); - final webDriverUri = Uri.parse(args[1]); - - late TestAssetServer server; - late DriftWebDriver driver; - - setUpAll(() async { - server = await TestAssetServer.start(); - }); - tearDownAll(() => server.close()); - - setUp(() async { - final rawDriver = await createDriver( - spec: browser.isChromium ? WebDriverSpec.JsonWire : WebDriverSpec.W3c, - uri: webDriverUri, - ); - - driver = DriftWebDriver(server, rawDriver); - - await driver.driver.get('http://localhost:8080/'); - }); - - tearDown(() => driver.driver.quit()); - - group('compatibility check', () { - test('can enumerate', () async { - final result = await driver.probeImplementations(); - - final expectedImplementations = WasmStorageImplementation.values.toSet() - ..removeAll(browser.unsupportedImplementations); - - expect(result.missingFeatures, browser.missingFeatures); - expect(result.storages, expectedImplementations); - }); - }); -} diff --git a/extras/integration_tests/web_wasm/web/main.dart b/extras/integration_tests/web_wasm/web/main.dart index 7c10dadb..4305296c 100644 --- a/extras/integration_tests/web_wasm/web/main.dart +++ b/extras/integration_tests/web_wasm/web/main.dart @@ -5,27 +5,31 @@ import 'dart:js_util'; import 'package:drift/wasm.dart'; // ignore: invalid_use_of_internal_member import 'package:drift/src/web/wasm_setup.dart'; +import 'package:web_wasm/src/database.dart'; const dbName = 'drift_test'; +TestDatabase? openedDatabase; void main() { _addCallbackForWebDriver('detectImplementations', _detectImplementations); + _addCallbackForWebDriver('open', _open); document.getElementById('selfcheck')?.onClick.listen((event) async { print('starting'); - final database = await openDatabase(); + final database = await _opener.open(); print('selected storage: ${database.chosenImplementation}'); print('missing features: ${database.missingFeatures}'); }); } -void _addCallbackForWebDriver(String name, Future Function() impl) { - setProperty(globalThis, name, allowInterop((Function callback) async { +void _addCallbackForWebDriver(String name, Future Function(String?) impl) { + setProperty(globalThis, name, + allowInterop((String? arg, Function callback) async { Object? result; try { - result = await impl(); + result = await impl(arg); } catch (e, s) { final console = getProperty(globalThis, 'console'); callMethod(console, 'error', [e, s]); @@ -35,16 +39,6 @@ void _addCallbackForWebDriver(String name, Future Function() impl) { })); } -Future _detectImplementations() async { - final opener = _opener; - await opener.probe(); - - return json.encode({ - 'impls': opener.availableImplementations.map((r) => r.name).toList(), - 'missing': opener.missingFeatures.map((r) => r.name).toList(), - }); -} - WasmDatabaseOpener get _opener { return WasmDatabaseOpener( databaseName: dbName, @@ -53,6 +47,32 @@ WasmDatabaseOpener get _opener { ); } -Future openDatabase() async { - return await _opener.open(); +Future _detectImplementations(String? _) async { + final opener = _opener; + await opener.probe(); + + return json.encode({ + 'impls': opener.availableImplementations.map((r) => r.name).toList(), + 'missing': opener.missingFeatures.map((r) => r.name).toList(), + }); +} + +Future _open(String? implementationName) async { + final opener = _opener; + WasmDatabaseResult result; + + if (implementationName != null) { + await opener.probe(); + result = await opener + .openWith(WasmStorageImplementation.values.byName(implementationName)); + } else { + result = await opener.open(); + } + + final db = openedDatabase = TestDatabase(result.resolvedExecutor); + + // Make sure it works! + await db.customSelect('SELECT 1').get(); + + return true; }