Run drift web tests with WebAssembly

This commit is contained in:
Simon Binder 2022-04-04 21:48:42 +02:00
parent a721e896d3
commit b56cf96b09
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
15 changed files with 100 additions and 40 deletions

View File

@ -15,19 +15,21 @@ dependencies:
js: ^0.6.3
meta: ^1.3.0
stream_channel: ^2.1.0
sqlite3: ^1.6.0
sqlite3: ^1.6.2
dev_dependencies:
build_test: ^2.0.0
build_runner_core: ^7.0.0
build_verify: ^3.0.0
drift_dev: any
http: ^0.13.4
uuid: ^3.0.0
path: ^1.8.0
build_runner: ^2.0.0
test: ^1.17.0
mockito: ^5.0.7
rxdart: ^0.27.0
shelf: ^1.3.0
dependency_overrides:
drift_dev:

View File

@ -164,7 +164,7 @@ void main() {
await db
.update(db.categories)
.write(const CategoriesCompanion(description: Value('changed')));
}, onPlatform: needsAdaptionForWeb());
});
test('custom expressions can introduces new tables to watch', () async {
final custom = CustomExpression<int>('1', watchedTables: [db.sharedTodos]);
@ -174,5 +174,5 @@ void main() {
expect(stream, emitsInOrder([1, 1]));
db.markTablesUpdated({db.sharedTodos});
}, onPlatform: needsAdaptionForWeb());
});
}

View File

@ -1,4 +1,3 @@
@TestOn('vm') // because of skips.dart
import 'package:drift/drift.dart' hide isNull;
import 'package:test/test.dart';
@ -150,5 +149,5 @@ void main() {
),
);
});
}, skip: ifOlderThanSqlite335());
}, skip: ifOlderThanSqlite335(sqlite3Version));
}

View File

@ -1,4 +1,3 @@
@TestOn('vm') // because of skips.dart
import 'package:drift/drift.dart';
import 'package:test/test.dart';
@ -112,5 +111,5 @@ void main() {
descriptionInUpperCase: 'DESCRIPTION',
),
);
}, skip: ifOlderThanSqlite335());
}, skip: ifOlderThanSqlite335(sqlite3Version));
}

View File

@ -27,5 +27,5 @@ void main() {
updates: someTables, updateKind: UpdateKind.update);
expect(await watchValue().first, 2);
}, onPlatform: needsAdaptionForWeb());
});
}

View File

@ -34,6 +34,6 @@ void main() {
throwsA(isA<StateError>().having((e) => e.message, 'message',
contains("Can't re-open a database after closing it."))),
);
}, onPlatform: needsAdaptionForWeb());
});
}
}

View File

@ -27,5 +27,5 @@ void main() {
..orderBy([(_) => OrderingTerm.random()]))
.get();
expect(rows.isSorted((a, b) => a.id.compareTo(b.id)), isFalse);
}, onPlatform: needsAdaptionForWeb());
});
}

View File

@ -1,5 +1,5 @@
import 'package:sqlite3/sqlite3.dart';
import 'package:sqlite3/common.dart';
String? ifOlderThanSqlite335() => sqlite3.version.versionNumber > 3035000
String? ifOlderThanSqlite335(Version version) => version.versionNumber > 3035000
? null
: 'RETURNING not supported by sqlite version ${sqlite3.version.libVersion}';
: 'RETURNING not supported by sqlite version ${version.libVersion}';

View File

@ -1,4 +1,9 @@
import 'package:drift/drift.dart';
import 'package:sqlite3/common.dart';
Version get sqlite3Version {
return throw UnsupportedError('Stub, should resolve to web or vm');
}
DatabaseConnection testInMemoryDatabase() {
throw UnsupportedError('Stub, should resolve to web or vm');

View File

@ -1,5 +1,10 @@
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:sqlite3/sqlite3.dart';
Version get sqlite3Version {
return sqlite3.version;
}
DatabaseConnection testInMemoryDatabase() {
return DatabaseConnection.fromExecutor(NativeDatabase.memory());

View File

@ -1,10 +1,32 @@
import 'package:drift/drift.dart';
import 'package:drift/remote.dart';
import 'package:drift/wasm.dart';
import 'package:http/http.dart' as http;
import 'package:sqlite3/wasm.dart';
import 'package:test/scaffolding.dart';
Version get sqlite3Version {
return Version('3.38.2', 'stub', 3038200);
}
Future<WasmSqlite3>? _loadedSqlite3;
Future<WasmSqlite3> get sqlite3 {
return _loadedSqlite3 ??= Future.sync(() async {
final channel = spawnHybridUri('/test/test_utils/sqlite_server.dart');
final port = await channel.stream.first as int;
final response =
await http.get(Uri.parse('http://localhost:$port/sqlite3.wasm'));
return WasmSqlite3.load(response.bodyBytes);
});
}
// At some point, we should use a `WasmDatabase` here since it was compiled
// in a reasonable way and will be must more reliable than proxying to a VM,
// but this is the easiest setup for now.
DatabaseConnection testInMemoryDatabase() {
return remote(spawnHybridUri('/test/test_utils/sqlite_server.dart'));
return DatabaseConnection.fromExecutor(LazyDatabase(() async {
final sqlite = await sqlite3;
return WasmDatabase.inMemory(sqlite);
}));
}

View File

@ -1,21 +1,57 @@
import 'package:drift/drift.dart';
import 'package:drift/native.dart';
import 'package:drift/remote.dart';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:stream_channel/stream_channel.dart';
/// This function is used as a [hybrid test] call to use the system's sqlite
/// in browser test.
///
/// To avoid excessive mocking, drift tests run against an actual sqlite3, but
/// getting sqlite3 to run on the browser is a bit of a hassle and most tests
/// exist to test core drift components, not the sqlite3 web implementation.
///
/// While we have separate integration tests to ensure drift works in the
/// browser, unit tests just use a stream channel and a drift remote.
///
/// [hybrid test]: https://pub.dev/packages/test#browservm-hybrid-tests
/// Spawns an HTTP server serving the sqlite WebAssembly module to use in web
/// tests.
Future<void> hybridMain(StreamChannel channel) async {
final connection = DatabaseConnection.fromExecutor(NativeDatabase.memory());
final server = DriftServer(connection);
server.serve(channel);
final wasmFile = File(p.join('..', 'extras', 'assets', 'sqlite3.wasm'));
if (!await wasmFile.exists()) {
throw UnsupportedError('Could not find sqlite3 WebAssembly module at '
'${wasmFile.absolute.path}!');
}
final server = await HttpServer.bind('localhost', 0);
final handler =
const Pipeline().addMiddleware(_cors()).addHandler(_serveFile(wasmFile));
io.serveRequests(server, handler);
channel.sink.add(server.port);
await channel.stream
.listen(null)
.asFuture<void>()
.then<void>((_) => server.close());
}
const _corsHeaders = {'Access-Control-Allow-Origin': '*'};
Middleware _cors() {
Response? handleOptionsRequest(Request request) {
if (request.method == 'OPTIONS') {
return Response.ok(null, headers: _corsHeaders);
} else {
// Returning null will run the regular request handler
return null;
}
}
Response addCorsHeaders(Response response) {
return response.change(headers: _corsHeaders);
}
return createMiddleware(
requestHandler: handleOptionsRequest, responseHandler: addCorsHeaders);
}
Handler _serveFile(File file) {
return (request) {
return Response(
200,
body: file.openRead(),
headers: {'Content-Type': 'application/wasm'},
);
};
}

View File

@ -1,13 +1,5 @@
import 'package:test/test.dart';
export 'database_stub.dart'
if (dart.library.ffi) 'database_vm.dart'
if (dart.library.js) 'database_web.dart';
export 'matchers.dart';
export 'mocks.dart';
Map<String, dynamic> needsAdaptionForWeb() {
return const {
'browser': Skip('TODO: This test needs adaptions to work on the web.')
};
}

Binary file not shown.

View File

@ -0,0 +1 @@
../../../extras/assets/sqlite3.wasm

BIN
extras/assets/sqlite3.wasm Normal file

Binary file not shown.