Skip to content

Add an API to do some customization and then directly run tests in process #1310

@natebosch

Description

@natebosch

Background

Most of our runners assume some separation between the process that orchestrates running all the tests, and the tests themselves. This is different Isolates in the VM case, and external processes like browsers in other cases.

We also support running a _test.dart file directly:

/// The global declarer.
///
/// This is used if a test file is run directly, rather than through the runner.
Declarer? _globalDeclarer;
/// Gets the declarer for the current scope.
///
/// When using the runner, this returns the [Zone]-scoped declarer that's set by
/// [IsolateListener] or [IframeListener]. If the test file is run directly,
/// this returns [_globalDeclarer] (and sets it up on the first call).
Declarer get _declarer {
var declarer = Declarer.current;
if (declarer != null) return declarer;
if (_globalDeclarer != null) return _globalDeclarer!;
// Since there's no Zone-scoped declarer, the test file is being run directly.
// In order to run the tests, we set up our own Declarer via
// [_globalDeclarer], and schedule a microtask to run the tests once they're
// finished being defined.
_globalDeclarer = Declarer();
scheduleMicrotask(() async {
var suite = RunnerSuite(const PluginEnvironment(), SuiteConfiguration.empty,
_globalDeclarer!.build(), SuitePlatform(Runtime.vm, os: currentOSGuess),
path: p.prettyUri(Uri.base));
var engine = Engine();
engine.suiteSink.add(suite);
engine.suiteSink.close();
ExpandedReporter.watch(engine, PrintSink(),
color: true, printPath: false, printPlatform: false);
var success = await runZoned(() => Invoker.guard(engine.run),
zoneValues: {#test.declarer: _globalDeclarer});
if (success == true) return null;
print('');
unawaited(Future.error('Dummy exception to set exit code.'));
});
return _globalDeclarer!;
}

This support is hardcoded to the expanded reporter and does not take any arguments for things like test filters.

In all cases when we run tests through a runner (internally, flutter_test, build_test, and package:test runner) we "wrap" the users library with out own and forward to their main in some way where the details vary by platform.

Goal

We should make it easier to combine the concepts of running without an external runner doing the orchestration, along with allowing some customization beyond the defaults. The immediate need is to register and pick a custom reporter.

Plan

Add a function in package:test_core/backend.dart like Future<bool> runTests(FutureOr<void> Function() userMain, {ReporterDetails reporter}).

Then platforms that want to build a custom "runner" that is baked in to a single execution instead of having a separate orchestration would use it by generating a wrapping library like:

import 'package:test_core/backend.dart';
import 'package:my_custom_test/my_custom_test.dart' as custom;  // The reporter implementation.

import '$userTestFile' as test; // Generated portion of the file - imports the users _test.dart file

void main() {
  runTests(test.main, reporter: custom.reporter);
}

We'd start with ReporterDetails for now, and we can hopefully add other named arguments for other customizations that crop up. For instance we might support test filtering by tag with an additional named argument.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions