Index: lib/src/runner/engine.dart |
diff --git a/lib/src/runner/engine.dart b/lib/src/runner/engine.dart |
index f50fd4885c924f3952d129f597759c701bbb8c09..30053ba14aa0d091dde5ea845891e2b66618e657 100644 |
--- a/lib/src/runner/engine.dart |
+++ b/lib/src/runner/engine.dart |
@@ -17,14 +17,33 @@ import '../backend/state.dart'; |
import '../backend/suite.dart'; |
import '../backend/test.dart'; |
import '../util/delegating_sink.dart'; |
+import 'load_suite.dart'; |
/// An [Engine] manages a run that encompasses multiple test suites. |
/// |
+/// Test suites are provided by passing them into [suiteSink]. Once all suites |
+/// have been provided, the user should close [suiteSink] to indicate this. |
+/// [run] won't terminate until [suiteSink] is closed. Suites will be run in the |
+/// order they're provided to [suiteSink]. Tests within those suites will |
+/// likewise be run in the order of [Suite.tests]. |
+/// |
/// The current status of every test is visible via [liveTests]. [onTestStarted] |
/// can also be used to be notified when a test is about to be run. |
/// |
-/// Suites will be run in the order they're provided to [new Engine]. Tests |
-/// within those suites will likewise be run in the order of [Suite.tests]. |
+/// The engine has some special logic for [LoadSuite]s and the tests they |
+/// contain, referred to as "load tests". Load tests exist to provide visibility |
+/// into the process of loading test files, but as long as that process is |
+/// proceeding normally users usually don't care about it, so the engine only |
+/// surfaces running load tests (that is, includes them in [liveTests] and other |
+/// collections) under specific circumstances. |
+/// |
+/// If only load tests are running, exactly one load test will be in [active] |
+/// and [liveTests]. If this test passes, it will be removed from both [active] |
+/// and [liveTests] and *will not* be added to [passed]. If at any point a load |
+/// test fails, it will be added to [failed] and [liveTests]. |
+/// |
+/// Load tests will always be emitted through [onTestStarted] so users can watch |
+/// their event streams once they start running. |
class Engine { |
/// Whether [run] has been called yet. |
var _runCalled = false; |
@@ -97,7 +116,13 @@ class Engine { |
/// The tests that are still running, in the order they begain running. |
List<LiveTest> get active => new UnmodifiableListView(_active); |
- final _active = new List<LiveTest>(); |
+ final _active = new QueueList<LiveTest>(); |
+ |
+ /// The tests from [LoadSuite]s that are still running, in the order they |
+ /// began running. |
+ /// |
+ /// This is separate from [active] because load tests aren't always surfaced. |
+ final _activeLoadTests = new List<LiveTest>(); |
/// Creates an [Engine] that will run all tests provided via [suiteSink]. |
/// |
@@ -135,6 +160,11 @@ class Engine { |
_runCalled = true; |
_suiteController.stream.listen((suite) { |
+ if (suite is LoadSuite) { |
+ _group.add(_addLoadSuite(suite)); |
+ return; |
+ } |
+ |
_group.add(_pool.withResource(() { |
if (_closed) return null; |
@@ -148,10 +178,23 @@ class Engine { |
_liveTests.add(liveTest); |
_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()); |
+ } |
+ |
liveTest.onStateChange.listen((state) { |
if (state.status != Status.complete) return; |
_active.remove(liveTest); |
+ // 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); |
@@ -188,6 +231,49 @@ class Engine { |
return controller.liveTest; |
} |
+ /// Adds listeners for [suite]. |
+ /// |
+ /// Load suites have specific logic apart from normal test suites. |
+ Future _addLoadSuite(LoadSuite suite) async { |
+ var liveTest = await suite.tests.single.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); |
+ } |
+ |
+ liveTest.onStateChange.listen((state) { |
+ if (state.status != Status.complete) return; |
+ _activeLoadTests.remove(liveTest); |
+ |
+ // Only one load test will be active at any given time, and it will always |
+ // be the only active test. Remove it and, if possible, surface another |
+ // 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); |
+ } |
+ } |
+ |
+ // 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); |
+ }); |
+ |
+ // Run the test immediately. We don't want loading to be blocked on suites |
+ // that are already running. |
+ _onTestStartedController.add(liveTest); |
+ await liveTest.run(); |
+ } |
+ |
/// Signals that the caller is done paying attention to test results and the |
/// engine should release any resources it has allocated. |
/// |
@@ -203,6 +289,7 @@ class Engine { |
if (_closedBeforeDone == null) _closedBeforeDone = true; |
_suiteController.close(); |
- return Future.wait(liveTests.map((liveTest) => liveTest.close())); |
+ var allLiveTests = liveTests.toSet()..addAll(_activeLoadTests); |
+ return Future.wait(allLiveTests.map((liveTest) => liveTest.close())); |
} |
} |