Index: lib/src/runner/engine.dart |
diff --git a/lib/src/runner/engine.dart b/lib/src/runner/engine.dart |
index ca47c0998801c94ae39a7d58dacbeb066b6f033b..635e62e5e7e1ce339215f353379a172a1bcaf3b2 100644 |
--- a/lib/src/runner/engine.dart |
+++ b/lib/src/runner/engine.dart |
@@ -16,6 +16,9 @@ import '../backend/live_test.dart'; |
import '../backend/live_test_controller.dart'; |
import '../backend/state.dart'; |
import '../backend/test.dart'; |
+import '../util/iterable_set.dart'; |
+import 'live_suite.dart'; |
+import 'live_suite_controller.dart'; |
import 'load_suite.dart'; |
import 'runner_suite.dart'; |
@@ -102,8 +105,8 @@ class Engine { |
Set<RunnerSuite> get addedSuites => new UnmodifiableSetView(_addedSuites); |
final _addedSuites = new Set<RunnerSuite>(); |
- /// A broadcast that emits each [RunnerSuite] as it's added to the engine via |
- /// [suiteSink]. |
+ /// A broadcast stream that emits each [RunnerSuite] as it's added to the |
+ /// engine via [suiteSink]. |
/// |
/// Note that if a [LoadSuite] is added, this will only return that suite, not |
/// the suite it loads. |
@@ -112,7 +115,26 @@ class Engine { |
Stream<RunnerSuite> get onSuiteAdded => _onSuiteAddedController.stream; |
final _onSuiteAddedController = new StreamController<RunnerSuite>.broadcast(); |
- /// All the currently-known tests that have run, are running, or will run. |
+ /// All the currently-known suites that have run or are running. |
+ /// |
+ /// These are [LiveSuite]s, representing the in-progress state of each suite |
+ /// as its component tests are being run. |
+ /// |
+ /// Note that unlike [addedSuites], for suites that are loaded using |
+ /// [LoadSuite]s, both the [LoadSuite] and the suite it loads will eventually |
+ /// be in this set. |
+ Set<LiveSuite> get liveSuites => new UnmodifiableSetView(_liveSuites); |
+ final _liveSuites = new Set<LiveSuite>(); |
+ |
+ /// A broadcast stream that emits each [LiveSuite] as it's loaded. |
+ /// |
+ /// Note that unlike [onSuiteAdded], for suites that are loaded using |
+ /// [LoadSuite]s, both the [LoadSuite] and the suite it loads will eventually |
+ /// be emitted by this stream. |
+ Stream<LiveSuite> get onSuiteStarted => _onSuiteStartedController.stream; |
+ final _onSuiteStartedController = new StreamController<LiveSuite>.broadcast(); |
+ |
+ /// All the currently-known tests that have run or are running. |
/// |
/// These are [LiveTest]s, representing the in-progress state of each test. |
/// Tests that have not yet begun running are marked [Status.pending]; tests |
@@ -122,26 +144,27 @@ class Engine { |
/// [skipped], [failed], and [active]. |
/// |
/// [LiveTest.run] must not be called on these tests. |
- List<LiveTest> get liveTests => new UnmodifiableListView(_liveTests); |
- final _liveTests = new List<LiveTest>(); |
+ Set<LiveTest> get liveTests => new GroupSet.from( |
+ [passed, skipped, failed, new IterableSet(active)], |
+ disjoint: true); |
/// A stream that emits each [LiveTest] as it's about to start running. |
/// |
/// This is guaranteed to fire before [LiveTest.onStateChange] first fires. |
- Stream<LiveTest> get onTestStarted => _onTestStartedController.stream; |
- final _onTestStartedController = new StreamController<LiveTest>.broadcast(); |
+ Stream<LiveTest> get onTestStarted => _onTestStartedGroup.stream; |
+ final _onTestStartedGroup = new StreamGroup<LiveTest>.broadcast(); |
/// The set of tests that have completed and been marked as passing. |
- Set<LiveTest> get passed => new UnmodifiableSetView(_passed); |
- final _passed = new Set<LiveTest>(); |
+ Set<LiveTest> get passed => _passedGroup.set; |
+ final _passedGroup = new SetGroup<LiveTest>(disjoint: true); |
/// The set of tests that have completed and been marked as skipped. |
- Set<LiveTest> get skipped => new UnmodifiableSetView(_skipped); |
- final _skipped = new Set<LiveTest>(); |
+ Set<LiveTest> get skipped => _skippedGroup.set; |
+ final _skippedGroup = new SetGroup<LiveTest>(disjoint: true); |
/// The set of tests that have completed and been marked as failing or error. |
- Set<LiveTest> get failed => new UnmodifiableSetView(_failed); |
- final _failed = new Set<LiveTest>(); |
+ Set<LiveTest> get failed => _failedGroup.set; |
+ final _failedGroup = new SetGroup<LiveTest>(disjoint: true); |
/// The tests that are still running, in the order they begain running. |
List<LiveTest> get active => new UnmodifiableListView(_active); |
@@ -177,6 +200,8 @@ class Engine { |
? (concurrency == null ? 2 : concurrency * 2) |
: maxSuites) { |
_group.future.then((_) { |
+ _onTestStartedGroup.close(); |
+ _onSuiteStartedController.close(); |
if (_closedBeforeDone == null) _closedBeforeDone = false; |
}).catchError((_) { |
// Don't top-level errors. They'll be thrown via [success] anyway. |
@@ -213,18 +238,23 @@ class Engine { |
_group.add(new Future.sync(() async { |
var loadResource = await _loadPool.request(); |
+ var controller; |
if (suite is LoadSuite) { |
- suite = await _addLoadSuite(suite); |
- if (suite == null) { |
+ controller = await _addLoadSuite(suite); |
+ if (controller == null) { |
loadResource.release(); |
return; |
} |
+ } else { |
+ controller = new LiveSuiteController(suite); |
} |
+ _addLiveSuite(controller.liveSuite); |
+ |
await _runPool.withResource(() async { |
if (_closed) return; |
- await _runGroup(suite, suite.group, []); |
- loadResource.allowRelease(() => suite.close()); |
+ await _runGroup(controller, controller.liveSuite.suite.group, []); |
+ loadResource.allowRelease(() => controller.close()); |
}); |
})); |
}, onDone: () { |
@@ -235,23 +265,26 @@ class Engine { |
return success; |
} |
- /// Runs all the entries in [entries] in sequence. |
+ /// Runs all the entries in [group] in sequence. |
/// |
+ /// [suiteController] is the controller fo the suite that contains [group]. |
/// [parents] is a list of groups that contain [group]. It may be modified, |
/// but it's guaranteed to be in its original state once this function has |
/// finished. |
- Future _runGroup(RunnerSuite suite, Group group, List<Group> parents) async { |
+ Future _runGroup(LiveSuiteController suiteController, Group group, |
+ List<Group> parents) async { |
parents.add(group); |
try { |
if (group.metadata.skip) { |
- await _runLiveTest(_skippedTest(suite, group, parents)); |
+ await _runSkippedTest(suiteController, group, parents); |
return; |
} |
var setUpAllSucceeded = true; |
if (group.setUpAll != null) { |
- var liveTest = group.setUpAll.load(suite, groups: parents); |
- await _runLiveTest(liveTest, countSuccess: false); |
+ var liveTest = group.setUpAll.load(suiteController.liveSuite.suite, |
+ groups: parents); |
+ await _runLiveTest(suiteController, liveTest, countSuccess: false); |
setUpAllSucceeded = liveTest.state.result == Result.success; |
} |
@@ -260,12 +293,14 @@ class Engine { |
if (_closed) return; |
if (entry is Group) { |
- await _runGroup(suite, entry, parents); |
+ await _runGroup(suiteController, entry, parents); |
} else if (entry.metadata.skip) { |
- await _runLiveTest(_skippedTest(suite, entry, parents)); |
+ await _runSkippedTest(suiteController, entry, parents); |
} else { |
var test = entry as Test; |
- await _runLiveTest(test.load(suite, groups: parents)); |
+ await _runLiveTest( |
+ suiteController, |
+ test.load(suiteController.liveSuite.suite, groups: parents)); |
} |
} |
} |
@@ -273,8 +308,9 @@ class Engine { |
// Even if we're closed or setUpAll failed, we want to run all the |
// teardowns to ensure that any state is properly cleaned up. |
if (group.tearDownAll != null) { |
- var liveTest = group.tearDownAll.load(suite, groups: parents); |
- await _runLiveTest(liveTest, countSuccess: false); |
+ var liveTest = group.tearDownAll.load(suiteController.liveSuite.suite, |
+ groups: parents); |
+ await _runLiveTest(suiteController, liveTest, countSuccess: false); |
if (_closed) await liveTest.close(); |
} |
} finally { |
@@ -282,37 +318,18 @@ class Engine { |
} |
} |
- /// Returns a dummy [LiveTest] for a test or group marked as "skip". |
- /// |
- /// [parents] is a list of groups that contain [entry]. |
- LiveTest _skippedTest(RunnerSuite suite, GroupEntry entry, |
- List<Group> parents) { |
- // The netry name will be `null` for the root group. |
- var test = new LocalTest(entry.name ?? "(suite)", entry.metadata, () {}); |
- |
- var controller; |
- controller = new LiveTestController(suite, test, () { |
- controller.setState(const State(Status.running, Result.success)); |
- controller.setState(const State(Status.complete, Result.success)); |
- controller.completer.complete(); |
- }, () {}, groups: parents); |
- return controller.liveTest; |
- } |
- |
- /// Runs [liveTest]. |
+ /// Runs [liveTest] using [suiteController]. |
/// |
/// If [countSuccess] is `true` (the default), the test is put into [passed] |
/// if it succeeds. Otherwise, it's removed from [liveTests] entirely. |
- Future _runLiveTest(LiveTest liveTest, {bool countSuccess: true}) async { |
- _liveTests.add(liveTest); |
+ Future _runLiveTest(LiveSuiteController suiteController, LiveTest liveTest, |
+ {bool countSuccess: true}) async { |
_active.add(liveTest); |
// If there were no active non-load tests, the current active test would |
// have been a load test. In that case, remove it, since now we have a |
// non-load test to add. |
- if (_active.isNotEmpty && _active.first.suite is LoadSuite) { |
- _liveTests.remove(_active.removeFirst()); |
- } |
+ if (_active.first.suite is LoadSuite) _active.removeFirst(); |
liveTest.onStateChange.listen((state) { |
if (state.status != Status.complete) return; |
@@ -321,35 +338,45 @@ class Engine { |
// If we're out of non-load tests, surface a load test. |
if (_active.isEmpty && _activeLoadTests.isNotEmpty) { |
_active.add(_activeLoadTests.first); |
- _liveTests.add(_activeLoadTests.first); |
- } |
- |
- if (state.result != Result.success) { |
- _passed.remove(liveTest); |
- _failed.add(liveTest); |
- } else if (liveTest.test.metadata.skip) { |
- _skipped.add(liveTest); |
- } else if (countSuccess) { |
- _passed.add(liveTest); |
- } else { |
- _liveTests.remove(liveTest); |
} |
}); |
- _onTestStartedController.add(liveTest); |
+ suiteController.reportLiveTest(liveTest, countSuccess: countSuccess); |
- // First, schedule a microtask to ensure that [onTestStarted] fires before |
- // the first [LiveTest.onStateChange] event. Once the test finishes, use |
- // [new Future] to do a coarse-grained event loop pump to avoid starving |
- // non-microtask events. |
+ // Schedule a microtask to ensure that [onTestStarted] fires before the |
+ // first [LiveTest.onStateChange] event. |
await new Future.microtask(liveTest.run); |
+ |
+ // Once the test finishes, use [new Future] to do a coarse-grained event |
+ // loop pump to avoid starving non-microtask events. |
await new Future(() {}); |
if (!_restarted.contains(liveTest)) return; |
- await _runLiveTest(liveTest.copy(), countSuccess: countSuccess); |
+ await _runLiveTest(suiteController, liveTest.copy(), |
+ countSuccess: countSuccess); |
_restarted.remove(liveTest); |
} |
+ /// Runs a dummy [LiveTest] for a test or group marked as "skip". |
+ /// |
+ /// [suiteController] is the controller for the suite that contains [entry]. |
+ /// [parents] is a list of groups that contain [entry]. |
+ Future _runSkippedTest(LiveSuiteController suiteController, GroupEntry entry, |
+ List<Group> parents) { |
+ // The netry name will be `null` for the root group. |
+ var test = new LocalTest(entry.name ?? "(suite)", entry.metadata, () {}); |
+ |
+ var controller; |
+ controller = new LiveTestController( |
+ suiteController.liveSuite.suite, test, () { |
+ controller.setState(const State(Status.running, Result.success)); |
+ controller.setState(const State(Status.complete, Result.success)); |
+ controller.completer.complete(); |
+ }, () {}, groups: parents); |
+ |
+ return _runLiveTest(suiteController, controller.liveTest); |
+ } |
+ |
/// Closes [liveTest] and tells the engine to re-run it once it's done |
/// running. |
/// |
@@ -369,19 +396,18 @@ class Engine { |
await liveTest.close(); |
} |
- /// Adds listeners for [suite]. |
+ /// Runs [suite] and returns the [LiveSuiteController] for the suite it loads. |
/// |
- /// Load suites have specific logic apart from normal test suites. |
- Future<RunnerSuite> _addLoadSuite(LoadSuite suite) async { |
- var liveTest = await suite.test.load(suite); |
+ /// Returns `null` if the suite fails to load. |
+ Future<LiveSuiteController> _addLoadSuite(LoadSuite suite) async { |
+ var controller = new LiveSuiteController(suite); |
+ _addLiveSuite(controller.liveSuite); |
+ var liveTest = await suite.test.load(suite); |
_activeLoadTests.add(liveTest); |
// Only surface the load test if there are no other tests currently running. |
- if (_active.isEmpty) { |
- _liveTests.add(liveTest); |
- _active.add(liveTest); |
- } |
+ if (_active.isEmpty) _active.add(liveTest); |
liveTest.onStateChange.listen((state) { |
if (state.status != Status.complete) return; |
@@ -392,26 +418,43 @@ class Engine { |
// load test. |
if (_active.isNotEmpty && _active.first.suite == suite) { |
_active.remove(liveTest); |
- _liveTests.remove(liveTest); |
- |
- if (_activeLoadTests.isNotEmpty) { |
- _active.add(_activeLoadTests.last); |
- _liveTests.add(_activeLoadTests.last); |
- } |
+ if (_activeLoadTests.isNotEmpty) _active.add(_activeLoadTests.last); |
} |
+ }); |
+ |
+ controller.reportLiveTest(liveTest, countSuccess: false); |
+ controller.noMoreLiveTests(); |
+ |
+ // Schedule a microtask to ensure that [onTestStarted] fires before the |
+ // first [LiveTest.onStateChange] event. |
+ new Future.microtask(liveTest.run); |
- // Surface the load test if it fails so that the user can see the failure. |
- if (state.result == Result.success) return; |
- _failed.add(liveTest); |
- _liveTests.add(liveTest); |
+ var innerSuite = await suite.suite; |
+ if (innerSuite == null) return null; |
+ |
+ var innerController = new LiveSuiteController(innerSuite); |
+ innerController.liveSuite.onClose.then((_) { |
+ // When the main suite is closed, close the load suite and its test as |
+ // well. This doesn't release any resources, but it does close streams |
+ // which indicates that the load test won't experience an error in the |
+ // future. |
+ liveTest.close(); |
+ controller.close(); |
}); |
- // Run the test immediately. We don't want loading to be blocked on suites |
- // that are already running. |
- _onTestStartedController.add(liveTest); |
- await liveTest.run(); |
+ return innerController; |
+ } |
+ |
+ /// Add [liveSuite] and the information it exposes to the engine's |
+ /// informational streams and collections. |
+ void _addLiveSuite(LiveSuite liveSuite) { |
+ _liveSuites.add(liveSuite); |
+ _onSuiteStartedController.add(liveSuite); |
- return suite.suite; |
+ _onTestStartedGroup.add(liveSuite.onTestStarted); |
+ _passedGroup.add(liveSuite.passed); |
+ _skippedGroup.add(liveSuite.skipped); |
+ _failedGroup.add(liveSuite.failed); |
} |
/// Signals that the caller is done paying attention to test results and the |