Add wasm integration test using the database

This commit is contained in:
Simon Binder 2023-06-11 17:44:04 +02:00
parent 3f178a154f
commit e801dceae6
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
7 changed files with 349 additions and 82 deletions

View File

@ -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<TestAssetServer> 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<WasmStorageImplementation> storages,
Set<MissingBrowserFeature> 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<void> openDatabase([WasmStorageImplementation? implementation]) async {
await driver.executeAsync(
'open(arguments[0], arguments[1])', [implementation?.name]);
}
}

View File

@ -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;
}

View File

@ -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<int> id = GeneratedColumn<int>(
'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<String> content = GeneratedColumn<String>(
'content', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true);
@override
List<GeneratedColumn> get $columns => [id, content];
@override
String get aliasedName => _alias ?? 'test_table';
@override
String get actualTableName => 'test_table';
@override
VerificationContext validateIntegrity(Insertable<TestTableData> 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<GeneratedColumn> get $primaryKey => {id};
@override
TestTableData map(Map<String, dynamic> 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<TestTableData> {
final int id;
final String content;
const TestTableData({required this.id, required this.content});
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
map['id'] = Variable<int>(id);
map['content'] = Variable<String>(content);
return map;
}
TestTableCompanion toCompanion(bool nullToAbsent) {
return TestTableCompanion(
id: Value(id),
content: Value(content),
);
}
factory TestTableData.fromJson(Map<String, dynamic> json,
{ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return TestTableData(
id: serializer.fromJson<int>(json['id']),
content: serializer.fromJson<String>(json['content']),
);
}
@override
Map<String, dynamic> toJson({ValueSerializer? serializer}) {
serializer ??= driftRuntimeOptions.defaultSerializer;
return <String, dynamic>{
'id': serializer.toJson<int>(id),
'content': serializer.toJson<String>(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<TestTableData> {
final Value<int> id;
final Value<String> 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<TestTableData> custom({
Expression<int>? id,
Expression<String>? content,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (content != null) 'content': content,
});
}
TestTableCompanion copyWith({Value<int>? id, Value<String>? content}) {
return TestTableCompanion(
id: id ?? this.id,
content: content ?? this.content,
);
}
@override
Map<String, Expression> toColumns(bool nullToAbsent) {
final map = <String, Expression>{};
if (id.present) {
map['id'] = Variable<int>(id.value);
}
if (content.present) {
map['content'] = Variable<String>(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<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@override
List<DatabaseSchemaEntity> get allSchemaEntities => [testTable];
}

View File

@ -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

View File

@ -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<WasmStorageImplementation> unsupportedImplementations;
final Set<MissingBrowserFeature> missingFeatures;
const Browser({
required this.driverUriString,
this.isChromium = false,
this.unsupportedImplementations = const {},
this.missingFeatures = const {},
});
Uri get driverUri => Uri.parse(driverUriString);
Set<WasmStorageImplementation> get availableImplementations {
return WasmStorageImplementation.values.toSet()
..removeAll(unsupportedImplementations);
}
Future<Process> 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();
});
}
});
});
}
}

View File

@ -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<WasmStorageImplementation> unsupportedImplementations;
final Set<MissingBrowserFeature> missingFeatures;
const Browser({
this.isChromium = false,
this.unsupportedImplementations = const {},
this.missingFeatures = const {},
});
}
void main(List<String> 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);
});
});
}

View File

@ -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<String> _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<WasmDatabaseResult> openDatabase() async {
return await _opener.open();
Future<String> _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<bool> _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;
}