start static method

StreamChannel<Object?> start(
  1. Function getMain(), {
  2. bool hidePrints = true,
  3. Future beforeLoad(
    1. StreamChannel<Object?> suiteChannel(
      1. String name
      )
    )?,
})

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