| Index: mojo/public/dart/third_party/test/lib/src/runner.dart
|
| diff --git a/mojo/public/dart/third_party/test/lib/src/runner.dart b/mojo/public/dart/third_party/test/lib/src/runner.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..9a5756bc6de3f9cd4727205c90e47c6cb89e1422
|
| --- /dev/null
|
| +++ b/mojo/public/dart/third_party/test/lib/src/runner.dart
|
| @@ -0,0 +1,256 @@
|
| +// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
|
| +// for details. All rights reserved. Use of this source code is governed by a
|
| +// BSD-style license that can be found in the LICENSE file.
|
| +
|
| +library test.runner;
|
| +
|
| +import 'dart:async';
|
| +import 'dart:io';
|
| +
|
| +import 'package:async/async.dart';
|
| +
|
| +import 'backend/test_platform.dart';
|
| +import 'runner/application_exception.dart';
|
| +import 'runner/configuration.dart';
|
| +import 'runner/engine.dart';
|
| +import 'runner/load_exception.dart';
|
| +import 'runner/load_suite.dart';
|
| +import 'runner/loader.dart';
|
| +import 'runner/reporter.dart';
|
| +import 'runner/reporter/compact.dart';
|
| +import 'runner/reporter/expanded.dart';
|
| +import 'runner/runner_suite.dart';
|
| +import 'util/io.dart';
|
| +import 'utils.dart';
|
| +
|
| +/// A class that loads and runs tests based on a [Configuration].
|
| +///
|
| +/// This maintains a [Loader] and an [Engine] and passes test suites from one to
|
| +/// the other, as well as printing out tests with a [CompactReporter] or an
|
| +/// [ExpandedReporter].
|
| +class Runner {
|
| + /// The configuration for the runner.
|
| + final Configuration _config;
|
| +
|
| + /// The loader that loads the test suites from the filesystem.
|
| + final Loader _loader;
|
| +
|
| + /// The engine that runs the test suites.
|
| + final Engine _engine;
|
| +
|
| + /// The reporter that's emitting the test runner's results.
|
| + final Reporter _reporter;
|
| +
|
| + /// The subscription to the stream returned by [_loadSuites].
|
| + StreamSubscription _suiteSubscription;
|
| +
|
| + /// The memoizer for ensuring [close] only runs once.
|
| + final _closeMemo = new AsyncMemoizer();
|
| + bool get _closed => _closeMemo.hasRun;
|
| +
|
| + /// Creates a new runner based on [configuration].
|
| + factory Runner(Configuration config) {
|
| + var loader = new Loader(config);
|
| + var engine = new Engine(concurrency: config.concurrency);
|
| +
|
| + var watch = config.reporter == "compact"
|
| + ? CompactReporter.watch
|
| + : ExpandedReporter.watch;
|
| +
|
| + var reporter = watch(
|
| + engine,
|
| + color: config.color,
|
| + verboseTrace: config.verboseTrace,
|
| + printPath: config.paths.length > 1 ||
|
| + new Directory(config.paths.single).existsSync(),
|
| + printPlatform: config.platforms.length > 1);
|
| +
|
| + return new Runner._(config, loader, engine, reporter);
|
| + }
|
| +
|
| + Runner._(this._config, this._loader, this._engine, this._reporter);
|
| +
|
| + /// Starts the runner.
|
| + ///
|
| + /// This starts running tests and printing their progress. It returns whether
|
| + /// or not they ran successfully.
|
| + Future<bool> run() async {
|
| + if (_closed) {
|
| + throw new StateError("run() may not be called on a closed Runner.");
|
| + }
|
| +
|
| + var suites = _loadSuites();
|
| +
|
| + var success;
|
| + if (_config.pauseAfterLoad) {
|
| + success = await _loadThenPause(suites);
|
| + } else {
|
| + _suiteSubscription = suites.listen(_engine.suiteSink.add);
|
| + var results = await Future.wait([
|
| + _suiteSubscription.asFuture().then((_) => _engine.suiteSink.close()),
|
| + _engine.run()
|
| + ], eagerError: true);
|
| + success = results.last;
|
| + }
|
| +
|
| + if (_closed) return false;
|
| +
|
| + if (_engine.passed.length == 0 && _engine.failed.length == 0 &&
|
| + _engine.skipped.length == 0 && _config.pattern != null) {
|
| + var message = 'No tests match ';
|
| +
|
| + if (_config.pattern is RegExp) {
|
| + var pattern = (_config.pattern as RegExp).pattern;
|
| + message += 'regular expression "$pattern".';
|
| + } else {
|
| + message += '"${_config.pattern}".';
|
| + }
|
| + throw new ApplicationException(message);
|
| + }
|
| +
|
| + // Explicitly check "== true" here because [Engine.run] can return `null`
|
| + // if the engine was closed prematurely.
|
| + return success == true;
|
| + }
|
| +
|
| + /// Closes the runner.
|
| + ///
|
| + /// This stops any future test suites from running. It will wait for any
|
| + /// currently-running VM tests, in case they have stuff to clean up on the
|
| + /// filesystem.
|
| + Future close() => _closeMemo.runOnce(() async {
|
| + var timer;
|
| + if (!_engine.isIdle) {
|
| + // Wait a bit to print this message, since printing it eagerly looks weird
|
| + // if the tests then finish immediately.
|
| + timer = new Timer(new Duration(seconds: 1), () {
|
| + // Pause the reporter while we print to ensure that we don't interfere
|
| + // with its output.
|
| + _reporter.pause();
|
| + print("Waiting for current test(s) to finish.");
|
| + print("Press Control-C again to terminate immediately.");
|
| + _reporter.resume();
|
| + });
|
| + }
|
| +
|
| + if (_suiteSubscription != null) _suiteSubscription.cancel();
|
| + _suiteSubscription = null;
|
| +
|
| + // Make sure we close the engine *before* the loader. Otherwise,
|
| + // LoadSuites provided by the loader may get into bad states.
|
| + await _engine.close();
|
| + if (timer != null) timer.cancel();
|
| + await _loader.close();
|
| + });
|
| +
|
| + /// Return a stream of [LoadSuite]s in [_config.paths].
|
| + ///
|
| + /// Only tests that match [_config.pattern] will be included in the
|
| + /// suites once they're loaded.
|
| + Stream<LoadSuite> _loadSuites() {
|
| + return mergeStreams(_config.paths.map((path) {
|
| + if (new Directory(path).existsSync()) return _loader.loadDir(path);
|
| + if (new File(path).existsSync()) return _loader.loadFile(path);
|
| +
|
| + return new Stream.fromIterable([
|
| + new LoadSuite("loading $path", () =>
|
| + throw new LoadException(path, 'Does not exist.'))
|
| + ]);
|
| + })).map((loadSuite) {
|
| + return loadSuite.changeSuite((suite) {
|
| + if (_config.pattern == null) return suite;
|
| + return suite.change(tests: suite.tests.where((test) =>
|
| + test.name.contains(_config.pattern)));
|
| + });
|
| + });
|
| + }
|
| +
|
| + /// Loads each suite in [suites] in order, pausing after load for platforms
|
| + /// that support debugging.
|
| + Future<bool> _loadThenPause(Stream<LoadSuite> suites) async {
|
| + if (_config.platforms.contains(TestPlatform.vm)) {
|
| + warn("Debugging is currently unsupported on the Dart VM.",
|
| + color: _config.color);
|
| + }
|
| +
|
| + _suiteSubscription = suites.asyncMap((loadSuite) async {
|
| + // Make the underlying suite null so that the engine doesn't start running
|
| + // it immediately.
|
| + _engine.suiteSink.add(loadSuite.changeSuite((_) => null));
|
| +
|
| + var suite = await loadSuite.suite;
|
| + if (suite == null) return;
|
| +
|
| + await _pause(suite);
|
| + if (_closed) return;
|
| +
|
| + _engine.suiteSink.add(suite);
|
| + await _engine.onIdle.first;
|
| + }).listen(null);
|
| +
|
| + var results = await Future.wait([
|
| + _suiteSubscription.asFuture().then((_) => _engine.suiteSink.close()),
|
| + _engine.run()
|
| + ]);
|
| + return results.last;
|
| + }
|
| +
|
| + /// Pauses the engine and the reporter so that the user can set breakpoints as
|
| + /// necessary.
|
| + ///
|
| + /// This is a no-op for test suites that aren't on platforms where debugging
|
| + /// is supported.
|
| + Future _pause(RunnerSuite suite) async {
|
| + if (suite.platform == null) return;
|
| + if (suite.platform == TestPlatform.vm) return;
|
| +
|
| + try {
|
| + _reporter.pause();
|
| +
|
| + var bold = _config.color ? '\u001b[1m' : '';
|
| + var yellow = _config.color ? '\u001b[33m' : '';
|
| + var noColor = _config.color ? '\u001b[0m' : '';
|
| + print('');
|
| +
|
| + if (suite.platform.isDartVM) {
|
| + var url = suite.environment.observatoryUrl;
|
| + if (url == null) {
|
| + print("${yellow}Observatory URL not found. Make sure you're using "
|
| + "${suite.platform.name} 1.11 or later.$noColor");
|
| + } else {
|
| + print("Observatory URL: $bold$url$noColor");
|
| + }
|
| + }
|
| +
|
| + if (suite.platform.isHeadless) {
|
| + var url = suite.environment.remoteDebuggerUrl;
|
| + if (url == null) {
|
| + print("${yellow}Remote debugger URL not found.$noColor");
|
| + } else {
|
| + print("Remote debugger URL: $bold$url$noColor");
|
| + }
|
| + }
|
| +
|
| + var buffer = new StringBuffer(
|
| + "${bold}The test runner is paused.${noColor} ");
|
| + if (!suite.platform.isHeadless) {
|
| + buffer.write("Open the dev console in ${suite.platform} ");
|
| + } else {
|
| + buffer.write("Open the remote debugger ");
|
| + }
|
| + if (suite.platform.isDartVM) buffer.write("or the Observatory ");
|
| +
|
| + buffer.write("and set breakpoints. Once you're finished, return to this "
|
| + "terminal and press Enter.");
|
| +
|
| + print(wordWrap(buffer.toString()));
|
| +
|
| + await inCompletionOrder([
|
| + suite.environment.displayPause(),
|
| + cancelableNext(stdinLines)
|
| + ]).first;
|
| + } finally {
|
| + _reporter.resume();
|
| + }
|
| + }
|
| +}
|
|
|