Index: lib/src/executable.dart |
diff --git a/lib/src/executable.dart b/lib/src/executable.dart |
index f7753a0f612d84e537cd4d9f4c5d2fd956a182da..bf619b2af8e314702745953d9131994c50dc946b 100644 |
--- a/lib/src/executable.dart |
+++ b/lib/src/executable.dart |
@@ -17,12 +17,13 @@ import 'package:yaml/yaml.dart'; |
import 'backend/metadata.dart'; |
import 'backend/test_platform.dart'; |
-import 'runner/reporter/compact.dart'; |
-import 'runner/reporter/expanded.dart'; |
+import 'runner/engine.dart'; |
import 'runner/application_exception.dart'; |
import 'runner/load_exception.dart'; |
import 'runner/load_exception_suite.dart'; |
import 'runner/loader.dart'; |
+import 'runner/reporter/compact.dart'; |
+import 'runner/reporter/expanded.dart'; |
import 'util/exit_codes.dart' as exit_codes; |
import 'util/io.dart'; |
import 'utils.dart'; |
@@ -166,15 +167,6 @@ transformers: |
} |
} |
- var metadata = new Metadata(verboseTrace: options["verbose-trace"]); |
- var platforms = options["platform"].map(TestPlatform.find); |
- var loader = new Loader(platforms, |
- pubServeUrl: pubServeUrl, |
- packageRoot: options["package-root"], |
- color: color, |
- metadata: metadata, |
- jsTrace: options["js-trace"]); |
- |
var concurrency = _defaultConcurrency; |
if (options["concurrency"] != null) { |
try { |
@@ -198,83 +190,52 @@ transformers: |
paths = ["test"]; |
} |
+ var pattern; |
+ if (options["name"] != null) { |
+ if (options["plain-name"] != null) { |
+ _printUsage("--name and --plain-name may not both be passed."); |
+ exitCode = exit_codes.data; |
+ return; |
+ } |
+ pattern = new RegExp(options["name"]); |
+ } else if (options["plain-name"] != null) { |
+ pattern = options["plain-name"]; |
+ } |
+ |
+ var metadata = new Metadata(verboseTrace: options["verbose-trace"]); |
+ var platforms = options["platform"].map(TestPlatform.find); |
+ var loader = new Loader(platforms, |
+ pubServeUrl: pubServeUrl, |
+ packageRoot: options["package-root"], |
+ color: color, |
+ metadata: metadata, |
+ jsTrace: options["js-trace"]); |
+ |
var signalSubscription; |
- var closed = false; |
signalSubscription = _signals.listen((_) { |
signalSubscription.cancel(); |
- closed = true; |
loader.close(); |
}); |
try { |
- var suites = await mergeStreams(paths.map((path) { |
- if (new Directory(path).existsSync()) return loader.loadDir(path); |
- if (new File(path).existsSync()) return loader.loadFile(path); |
- return new Stream.fromFuture(new Future.error( |
- new LoadException(path, 'Does not exist.'), |
- new Trace.current())); |
- })).transform(new StreamTransformer.fromHandlers( |
- handleError: (error, stackTrace, sink) { |
- if (error is! LoadException) { |
- sink.addError(error, stackTrace); |
- } else { |
- sink.add(new LoadExceptionSuite(error, stackTrace)); |
- } |
- })).toList(); |
- |
- if (closed) return; |
- suites = flatten(suites); |
+ var engine = new Engine(concurrency: concurrency); |
- var pattern; |
- if (options["name"] != null) { |
- if (options["plain-name"] != null) { |
- _printUsage("--name and --plain-name may not both be passed."); |
- exitCode = exit_codes.data; |
- return; |
- } |
- pattern = new RegExp(options["name"]); |
- } else if (options["plain-name"] != null) { |
- pattern = options["plain-name"]; |
- } |
- |
- if (pattern != null) { |
- suites = suites.map((suite) { |
- // Don't ever filter out load errors. |
- if (suite is LoadExceptionSuite) return suite; |
- return suite.change( |
- tests: suite.tests.where((test) => test.name.contains(pattern))); |
- }).toList(); |
- |
- if (suites.every((suite) => suite.tests.isEmpty)) { |
- stderr.write('No tests match '); |
- |
- if (pattern is RegExp) { |
- stderr.writeln('regular expression "${pattern.pattern}".'); |
- } else { |
- stderr.writeln('"$pattern".'); |
- } |
- exitCode = exit_codes.data; |
- return; |
- } |
- } |
+ var watch = options["reporter"] == "compact" |
+ ? CompactReporter.watch |
+ : ExpandedReporter.watch; |
- var reporter = options["reporter"] == "compact" |
- ? new CompactReporter( |
- flatten(suites), |
- concurrency: concurrency, |
- color: color, |
- verboseTrace: options["verbose-trace"]) |
- : new ExpandedReporter( |
- flatten(suites), |
- concurrency: concurrency, |
- color: color, |
- verboseTrace: options["verbose-trace"]); |
+ watch( |
+ engine, |
+ color: color, |
+ verboseTrace: options["verbose-trace"], |
+ printPath: paths.length > 1 || |
+ new Directory(paths.single).existsSync(), |
+ printPlatform: platforms.length > 1); |
// Override the signal handler to close [reporter]. [loader] will still be |
// closed in the [whenComplete] below. |
- signalSubscription.onData((_) { |
+ signalSubscription.onData((_) async { |
signalSubscription.cancel(); |
- closed = true; |
// Wait a bit to print this message, since printing it eagerly looks weird |
// if the tests then finish immediately. |
@@ -286,15 +247,35 @@ transformers: |
print("Press Control-C again to terminate immediately."); |
}); |
- reporter.close().then((_) => timer.cancel()); |
+ await engine.close(); |
+ timer.cancel(); |
+ await loader.close(); |
}); |
try { |
- var success = await reporter.run(); |
- exitCode = success ? 0 : 1; |
+ var results = await Future.wait([ |
+ _loadSuites(paths, pattern, loader, engine), |
+ engine.run() |
+ ], eagerError: true); |
+ |
+ // Explicitly check "== true" here because [engine.run] can return `null` |
+ // if the engine was closed prematurely. |
+ exitCode = results.last == true ? 0 : 1; |
} finally { |
signalSubscription.cancel(); |
- await reporter.close(); |
+ await engine.close(); |
+ } |
+ |
+ if (engine.passed.length == 0 && engine.failed.length == 0 && |
+ engine.skipped.length == 0 && pattern != null) { |
+ stderr.write('No tests match '); |
+ |
+ if (pattern is RegExp) { |
+ stderr.writeln('regular expression "${pattern.pattern}".'); |
+ } else { |
+ stderr.writeln('"$pattern".'); |
+ } |
+ exitCode = exit_codes.data; |
} |
} on ApplicationException catch (error) { |
stderr.writeln(error.message); |
@@ -313,6 +294,42 @@ transformers: |
} |
} |
+/// Load the test suites in [paths] that match [pattern] and pass them to |
+/// [engine]. |
+/// |
+/// This completes once all the tests have been added to the engine. It does not |
+/// run the engine. |
+Future _loadSuites(List<String> paths, Pattern pattern, Loader loader, |
+ Engine engine) async { |
+ var completer = new Completer(); |
+ |
+ mergeStreams(paths.map((path) { |
+ if (new Directory(path).existsSync()) return loader.loadDir(path); |
+ if (new File(path).existsSync()) return loader.loadFile(path); |
+ |
+ return new Stream.fromFuture(new Future.error( |
+ new LoadException(path, 'Does not exist.'), |
+ new Trace.current())); |
+ })).map((suite) { |
+ if (pattern == null) return suite; |
+ return suite.change( |
+ tests: suite.tests.where((test) => test.name.contains(pattern))); |
+ }).listen((suite) => engine.suiteSink.add(suite), |
kevmoo
2015/06/17 22:20:04
consider using await async here – won't need a com
nweiz
2015/06/17 22:41:08
We can't use it here, because we need to intercept
|
+ onError: (error, stackTrace) { |
+ if (error is LoadException) { |
+ engine.suiteSink.add(new LoadExceptionSuite(error, stackTrace)); |
+ } else if (!completer.isCompleted) { |
+ completer.completeError(error, stackTrace); |
+ } |
+ }, onDone: () => completer.complete()); |
+ |
+ await completer.future; |
+ |
+ // Once we've loaded all the suites, notify the engine that no more will be |
+ // coming. |
+ engine.suiteSink.close(); |
+} |
+ |
/// Print usage information for this command. |
/// |
/// If [error] is passed, it's used in place of the usage message and the whole |