Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(112)

Unified Diff: lib/src/runner.dart

Issue 1248073003: Add a --pause-after-load flag for debugging. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: lib/src/runner.dart
diff --git a/lib/src/runner.dart b/lib/src/runner.dart
index 394348d37f5754e637e4613ed251383f8fb33761..c0c91f063f7b9f9e9cf151412183bc2449b27168 100644
--- a/lib/src/runner.dart
+++ b/lib/src/runner.dart
@@ -7,20 +7,26 @@ library test.runner;
import 'dart:async';
import 'dart:io';
-import 'package:async/async.dart';
-
import 'backend/metadata.dart';
+import 'backend/suite.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 'util/async_thunk.dart';
+import 'util/io.dart';
import 'utils.dart';
+/// The set of platforms for which debug flags are (currently) not supported.
+final _debugUnsupportedPlatforms = new Set.from(
+ [TestPlatform.vm, TestPlatform.phantomJS, TestPlatform.contentShell]);
+
/// 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
@@ -36,13 +42,16 @@ class Runner {
/// 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 thunk for ensuring [close] only runs once.
final _closeThunk = new AsyncThunk();
bool get _closed => _closeThunk.hasRun;
- /// Whether [run] has been called.
- bool _hasRun = false;
-
/// Creates a new runner based on [configuration].
factory Runner(Configuration configuration) {
var metadata = new Metadata(
@@ -60,7 +69,7 @@ class Runner {
? CompactReporter.watch
: ExpandedReporter.watch;
- watch(
+ var reporter = watch(
engine,
color: configuration.color,
verboseTrace: configuration.verboseTrace,
@@ -68,28 +77,34 @@ class Runner {
new Directory(configuration.paths.single).existsSync(),
printPlatform: configuration.platforms.length > 1);
- return new Runner._(configuration, loader, engine);
+ return new Runner._(configuration, loader, engine, reporter);
}
- Runner._(this._configuration, this._loader, this._engine);
+ Runner._(this._configuration, 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 {
- _hasRun = true;
-
if (_closed) {
throw new StateError("run() may not be called on a closed Runner.");
}
+ var suites = _loadSuites();
+
var success;
- var results = await Future.wait([
- _loadSuites(),
- _engine.run()
- ], eagerError: true);
- success = results.last;
+ if (_configuration.pauseAfterLoad) {
+ // TODO(nweiz): disable timeouts when debugging.
+ 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;
@@ -118,18 +133,22 @@ class Runner {
/// filesystem.
Future close() => _closeThunk.run(() async {
var timer;
- if (_hasRun) {
+ 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), () {
- // Print a blank line first to ensure that this doesn't interfere with
- // the compact reporter's unfinished line.
- print('');
+ // 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();
@@ -137,12 +156,12 @@ class Runner {
await _loader.close();
});
- /// Load the test suites in [_configuration.paths] that match
- /// [_configuration.pattern].
- Future _loadSuites() async {
- var group = new FutureGroup();
-
- mergeStreams(_configuration.paths.map((path) {
+ /// Return a stream of [LoadSuite]s in [_configuration.paths].
+ ///
+ /// Only tests that match [_configuration.pattern] will be included in the
+ /// suites once they're loaded.
+ Stream<LoadSuite> _loadSuites() {
+ return mergeStreams(_configuration.paths.map((path) {
if (new Directory(path).existsSync()) return _loader.loadDir(path);
if (new File(path).existsSync()) return _loader.loadFile(path);
@@ -150,22 +169,79 @@ class Runner {
new LoadSuite("loading $path", () =>
throw new LoadException(path, 'Does not exist.'))
]);
- })).listen((loadSuite) {
- group.add(new Future.sync(() {
- _engine.suiteSink.add(loadSuite.changeSuite((suite) {
- if (_configuration.pattern == null) return suite;
- return suite.change(tests: suite.tests.where(
- (test) => test.name.contains(_configuration.pattern)));
- }));
- }));
- }, onError: (error, stackTrace) {
- group.add(new Future.error(error, stackTrace));
- }, onDone: group.close);
-
- await group.future;
-
- // Once we've loaded all the suites, notify the engine that no more will be
- // coming.
- _engine.suiteSink.close();
+ })).map((loadSuite) {
+ return loadSuite.changeSuite((suite) {
+ if (_configuration.pattern == null) return suite;
+ return suite.change(tests: suite.tests.where((test) =>
+ test.name.contains(_configuration.pattern)));
+ });
+ });
+ }
+
+ /// Loads each suite in [suites] in order, pausing after load for platforms
+ /// that support debugging.
+ Future<bool> _loadThenPause(Stream<LoadSuite> suites) async {
+ var unsupportedPlatforms = _configuration.platforms
+ .where(_debugUnsupportedPlatforms.contains)
+ .map((platform) =>
+ platform == TestPlatform.vm ? "the Dart VM" : platform.name)
+ .toList();
+
+ if (unsupportedPlatforms.isNotEmpty) {
+ warn(
+ wordWrap("Debugging is currently unsupported on "
+ "${toSentence(unsupportedPlatforms)}."),
+ color: _configuration.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(Suite suite) async {
+ if (suite.platform == null) return;
+ if (_debugUnsupportedPlatforms.contains(suite.platform)) return;
+
+ try {
+ _reporter.pause();
+
+ var bold = _configuration.color ? '\u001b[1m' : '';
+ var noColor = _configuration.color ? '\u001b[0m' : '';
+ print('');
+ print(wordWrap(
+ "${bold}The test runner is paused.${noColor} Open the dev console in "
+ "${suite.platform} and set breakpoints. Once you're finished, "
+ "return to this terminal and press Enter."));
+
+ // TODO(nweiz): Display something in the paused browsers indicating that
+ // they're paused.
+
+ await stdinLines.next;
+ } finally {
+ _reporter.resume();
+ }
}
}

Powered by Google App Engine
This is Rietveld 408576698