start static method
Extracts metadata about all the tests in the function returned by
getMain
and returns a channel that will send information about them.
The main function is wrapped in a closure so that we can handle it being undefined here rather than in the generated code.
Once that's done, this starts listening for commands about which tests to run.
If hidePrints
is true
(the default), calls to print()
within this
suite will not be forwarded to the parent zone's print handler. However,
the caller may want them to be forwarded in (for example) a browser
context where they'll be visible in the development console.
If beforeLoad
is passed, it's called before the tests have been declared
for this worker.
Implementation
static StreamChannel<Object?> start(Function Function() getMain,
{bool hidePrints = true,
Future Function(
StreamChannel<Object?> Function(String name) suiteChannel)?
beforeLoad}) {
// Synchronous in order to allow `print` output to show up immediately, even
// if they are followed by long running synchronous work.
var controller =
StreamChannelController<Object?>(allowForeignErrors: false, sync: true);
var channel = MultiChannel<Object?>(controller.local);
var verboseChain = true;
var printZone = hidePrints ? null : Zone.current;
var spec = ZoneSpecification(print: (_, __, ___, line) {
if (printZone != null) printZone.print(line);
channel.sink.add({'type': 'print', 'line': line});
});
final suiteChannelManager = SuiteChannelManager();
StackTraceFormatter().asCurrent(() {
runZonedGuarded(() async {
Function? main;
try {
main = getMain();
} on NoSuchMethodError catch (_) {
_sendLoadException(channel, 'No top-level main() function defined.');
return;
} catch (error, stackTrace) {
_sendError(channel, error, stackTrace, verboseChain);
return;
}
if (main is! FutureOr<void> Function()) {
_sendLoadException(
channel, 'Top-level main() function takes arguments.');
return;
}
var queue = StreamQueue(channel.stream);
var message = await queue.next as Map;
assert(message['type'] == 'initial');
queue.rest.cast<Map>().listen((message) {
if (message['type'] == 'close') {
controller.local.sink.close();
return;
}
assert(message['type'] == 'suiteChannel');
suiteChannelManager.connectIn(message['name'] as String,
channel.virtualChannel((message['id'] as num).toInt()));
});
if ((message['asciiGlyphs'] as bool?) ?? false) glyph.ascii = true;
var metadata = Metadata.deserialize(message['metadata'] as Map);
verboseChain = metadata.verboseTrace;
var declarer = Declarer(
metadata: metadata,
platformVariables: Set.from(message['platformVariables'] as Iterable),
collectTraces: message['collectTraces'] as bool,
noRetry: message['noRetry'] as bool,
// TODO: Change to non-nullable https://github.com/dart-lang/test/issues/1591
allowDuplicateTestNames:
message['allowDuplicateTestNames'] as bool? ?? true,
);
StackTraceFormatter.current!.configure(
except: _deserializeSet(message['foldTraceExcept'] as List),
only: _deserializeSet(message['foldTraceOnly'] as List));
if (beforeLoad != null) {
await beforeLoad(suiteChannelManager.connectOut);
}
await declarer.declare(main);
var suite = Suite(
declarer.build(),
SuitePlatform.deserialize(message['platform'] as Object),
path: message['path'] as String,
ignoreTimeouts: message['ignoreTimeouts'] as bool? ?? false,
);
runZoned(() {
Invoker.guard(
() => RemoteListener._(suite, printZone)._listen(channel));
},
// Make the declarer visible to running tests so that they'll throw
// useful errors when calling `test()` and `group()` within a test,
// and so they can add to the declarer's `tearDownAll()` list.
zoneValues: {#test.declarer: declarer});
}, (error, stackTrace) {
_sendError(channel, error, stackTrace, verboseChain);
}, zoneSpecification: spec);
});
return controller.foreign;
}