testDriverContract function

  1. @isTestGroup
void testDriverContract({
  1. required String packageName,
  2. required SqlDialect dialect,
  3. required DdlGenerator createDdlGenerator(),
  4. required Schema probe,
})

Verifies the CLI-facing contract of the driver package packageName.

Where testDriverConformance proves runtime behavior against a real database, this suite proves the package layout the CLI relies on:

  • the main library exports a dialect constant, passed as dialect.
  • the driver's table function tags tables with its dialect's name, which is what snapshot building filters on.
  • lib/ddl.dart exists and its main serves the DDL generator over the isolate protocol, answering a real generate request exactly like the in-process generator.
testDriverContract(
  packageName: 'raindrop_sqlite',
  dialect: dialect,
  createDdlGenerator: SQLiteHarness().createDdlGenerator,
  probe: sqliteTable('probe', UserSchema.new),
);

Implementation

@isTestGroup
void testDriverContract({
  required String packageName,
  required SqlDialect dialect,
  required DdlGenerator Function() createDdlGenerator,
  required Schema<dynamic> probe,
}) {
  group('driver contract', () {
    test('the exported dialect constant is the one tables are tagged with', () {
      expect(dialect.name, createDdlGenerator().dialect.name);
      expect(probe.$.dialect?.name, dialect.name);
    });

    test('the table function tags tables with the dialect name', () {
      expect(probe.$.dialect?.name, createDdlGenerator().dialect.name);
    });

    test('lib/ddl.dart serves the DDL generator over the isolate protocol',
        () async {
      final entrypoint = await Isolate.resolvePackageUri(
        Uri.parse('package:$packageName/ddl.dart'),
      );
      expect(
        entrypoint,
        isNotNull,
        reason: 'package:$packageName/ddl.dart must exist: it is the '
            'entrypoint the CLI spawns for DDL generation.',
      );

      final generator = createDdlGenerator();
      final operations = fixtureCreateTableOperations(generator.dialect);

      final handshake = ReceivePort();
      final errors = ReceivePort();
      final exit = ReceivePort();
      final isolate = await Isolate.spawnUri(
        entrypoint!,
        [],
        handshake.sendPort,
        onError: errors.sendPort,
        onExit: exit.sendPort,
        packageConfig: await _packageConfig(),
      );

      try {
        final failure = errors.first.then(
          // Fires only when the entrypoint fails to load.
          // coverage:ignore-start
          (error) => throw StateError('The DDL entrypoint failed:\n$error'),
          // coverage:ignore-end
        );
        final commands =
            await Future.any([handshake.first, failure]) as SendPort;

        final replies = ReceivePort();
        commands.send({
          'action': 'generate',
          'operations': [for (final op in operations) op.toMap()],
          'replyPort': replies.sendPort,
        });
        final reply = ((await Future.any([replies.first, failure]))! as Map)
            .cast<String, Object?>();
        replies.close();

        expect(reply['success'], isTrue, reason: '${reply['error']}');
        expect(reply['sql'], generator.generate(operations));
      } finally {
        handshake.close();
        errors.close();
        isolate.kill(priority: Isolate.immediate);
        await exit.first;
        exit.close();
      }
    });
  });
}