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

Side by Side Diff: lib/src/runner/engine.dart

Issue 1187103004: Allow Suites to be added to an Engine over time. (Closed) Base URL: git@github.com:dart-lang/test@master
Patch Set: Created 5 years, 6 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 unified diff | Download patch
OLDNEW
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library test.runner.engine; 5 library test.runner.engine;
6 6
7 import 'dart:async'; 7 import 'dart:async';
8 import 'dart:collection'; 8 import 'dart:collection';
9 9
10 import 'package:async/async.dart' hide Result;
10 import 'package:collection/collection.dart'; 11 import 'package:collection/collection.dart';
11 import 'package:pool/pool.dart'; 12 import 'package:pool/pool.dart';
12 13
13 import '../backend/live_test.dart'; 14 import '../backend/live_test.dart';
14 import '../backend/live_test_controller.dart'; 15 import '../backend/live_test_controller.dart';
15 import '../backend/state.dart'; 16 import '../backend/state.dart';
16 import '../backend/suite.dart'; 17 import '../backend/suite.dart';
17 import '../backend/test.dart'; 18 import '../backend/test.dart';
18 import '../utils.dart'; 19 import '../util/delegating_sink.dart';
19 20
20 /// An [Engine] manages a run that encompasses multiple test suites. 21 /// An [Engine] manages a run that encompasses multiple test suites.
21 /// 22 ///
22 /// The current status of every test is visible via [liveTests]. [onTestStarted] 23 /// The current status of every test is visible via [liveTests]. [onTestStarted]
23 /// can also be used to be notified when a test is about to be run. 24 /// can also be used to be notified when a test is about to be run.
24 /// 25 ///
25 /// Suites will be run in the order they're provided to [new Engine]. Tests 26 /// Suites will be run in the order they're provided to [new Engine]. Tests
26 /// within those suites will likewise be run in the order of [Suite.tests]. 27 /// within those suites will likewise be run in the order of [Suite.tests].
27 class Engine { 28 class Engine {
28 /// Whether [run] has been called yet. 29 /// Whether [run] has been called yet.
29 var _runCalled = false; 30 var _runCalled = false;
30 31
31 /// Whether [close] has been called. 32 /// Whether [close] has been called.
32 var _closed = false; 33 var _closed = false;
33 34
35 /// Whether [close] was called before all the tests finished running.
36 ///
37 /// This is `null` if close hasn't been called and the tests are still
38 /// running, `true` if close was called before the tests finished running, and
39 /// `false` if the tests finished running before close was called.
40 var _closedBeforeDone;
41
34 /// A pool that limits the number of test suites running concurrently. 42 /// A pool that limits the number of test suites running concurrently.
35 final Pool _pool; 43 final Pool _pool;
36 44
37 /// An unmodifiable list of tests to run. 45 /// Whether all tests passed.
46 ///
47 /// This fires once all tests have completed and [suiteSink] has been closed.
48 /// This will be `null` if [close] was called before all the tests finished
49 /// running.
50 Future<bool> get success async {
51 await _group.future;
52 if (_closedBeforeDone) return null;
53 return liveTests.every((liveTest) =>
54 liveTest.state.result == Result.success);
55 }
56
57 /// A group of futures for each test suite.
58 final _group = new FutureGroup();
59
60 /// A sink used to pass [Suite]s in to the engine to run.
61 ///
62 /// Suites may be added as quickly as they're available; the Engine will only
63 /// run as many as necessary at a time based on its concurrency settings.
64 Sink<Suite> get suiteSink => new DelegatingSink(_suiteController.sink);
65 final _suiteController = new StreamController<Suite>();
66
67 /// All the currently-known tests that have run, are running, or will run.
38 /// 68 ///
39 /// These are [LiveTest]s, representing the in-progress state of each test. 69 /// These are [LiveTest]s, representing the in-progress state of each test.
40 /// Tests that have not yet begun running are marked [Status.pending]; tests 70 /// Tests that have not yet begun running are marked [Status.pending]; tests
41 /// that have finished are marked [Status.complete]. 71 /// that have finished are marked [Status.complete].
42 /// 72 ///
73 /// This is guaranteed to contain the same tests as the union of [passed],
74 /// [skipped], [failed], and [active].
75 ///
43 /// [LiveTest.run] must not be called on these tests. 76 /// [LiveTest.run] must not be called on these tests.
44 List<LiveTest> get liveTests => 77 List<LiveTest> get liveTests => new UnmodifiableListView(_liveTests);
45 new UnmodifiableListView(flatten(_liveTestsBySuite)); 78 final _liveTests = new List<LiveTest>();
46
47 /// The tests in [liveTests], organized by their original test suite.
48 ///
49 /// This allows test suites to be run in parallel without running multiple
50 /// tests in the same suite at once.
51 final List<List<LiveTest>> _liveTestsBySuite;
52 79
53 /// A stream that emits each [LiveTest] as it's about to start running. 80 /// A stream that emits each [LiveTest] as it's about to start running.
54 /// 81 ///
55 /// This is guaranteed to fire before [LiveTest.onStateChange] first fires. 82 /// This is guaranteed to fire before [LiveTest.onStateChange] first fires.
56 Stream<LiveTest> get onTestStarted => _onTestStartedController.stream; 83 Stream<LiveTest> get onTestStarted => _onTestStartedController.stream;
57 final _onTestStartedController = new StreamController<LiveTest>.broadcast(); 84 final _onTestStartedController = new StreamController<LiveTest>.broadcast();
58 85
59 /// The set of tests that have completed and been marked as passing. 86 /// The set of tests that have completed and been marked as passing.
60 Set<LiveTest> get passed => new UnmodifiableSetView(_passed); 87 Set<LiveTest> get passed => new UnmodifiableSetView(_passed);
61 final _passed = new Set<LiveTest>(); 88 final _passed = new Set<LiveTest>();
62 89
63 /// The set of tests that have completed and been marked as skipped. 90 /// The set of tests that have completed and been marked as skipped.
64 Set<LiveTest> get skipped => new UnmodifiableSetView(_skipped); 91 Set<LiveTest> get skipped => new UnmodifiableSetView(_skipped);
65 final _skipped = new Set<LiveTest>(); 92 final _skipped = new Set<LiveTest>();
66 93
67 /// The set of tests that have completed and been marked as failing or error. 94 /// The set of tests that have completed and been marked as failing or error.
68 Set<LiveTest> get failed => new UnmodifiableSetView(_failed); 95 Set<LiveTest> get failed => new UnmodifiableSetView(_failed);
69 final _failed = new Set<LiveTest>(); 96 final _failed = new Set<LiveTest>();
70 97
71 /// The tests that are still running, in the order they begain running. 98 /// The tests that are still running, in the order they begain running.
72 List<LiveTest> get active => new UnmodifiableListView(_active); 99 List<LiveTest> get active => new UnmodifiableListView(_active);
73 final _active = new List<LiveTest>(); 100 final _active = new List<LiveTest>();
74 101
75 /// Returns the tests in [suites] grouped by suite. 102 /// Creates an [Engine] that will run all tests provided via [suiteSink].
76 /// 103 ///
77 /// Also replaces tests marked as "skip" with dummy [LiveTest]s. 104 /// [concurrency] controls how many suites are run at once.
78 static List<List<LiveTest>> _computeLiveTestsBySuite(Iterable<Suite> suites) { 105 Engine({int concurrency})
79 return suites.map((suite) { 106 : _pool = new Pool(concurrency == null ? 1 : concurrency) {
80 return suite.tests.map((test) { 107 _group.future.then((_) {
81 return test.metadata.skip 108 if (_closedBeforeDone == null) _closedBeforeDone = false;
82 ? _skippedTest(suite, test) 109 }).catchError((_) {
83 : test.load(suite); 110 // Don't top-level errors. They'll be thrown via [success] anyway.
84 }).toList(); 111 });
85 }).toList();
86 }
87
88 /// Returns a dummy [LiveTest] for a test marked as "skip".
89 static LiveTest _skippedTest(Suite suite, Test test) {
90 var controller;
91 controller = new LiveTestController(suite, test, () {
92 controller.setState(const State(Status.running, Result.success));
93 controller.setState(const State(Status.complete, Result.success));
94 controller.completer.complete();
95 }, () {});
96 return controller.liveTest;
97 } 112 }
98 113
99 /// Creates an [Engine] that will run all tests in [suites]. 114 /// Creates an [Engine] that will run all tests in [suites].
100 /// 115 ///
101 /// [concurrency] controls how many suites are run at once. 116 /// [concurrency] controls how many suites are run at once. An engine
102 Engine(Iterable<Suite> suites, {int concurrency}) 117 /// constructed this way will automatically close its [suiteSink], meaning
103 : _liveTestsBySuite = _computeLiveTestsBySuite(suites), 118 /// that no further suites may be provided.
104 _pool = new Pool(concurrency == null ? 1 : concurrency); 119 factory Engine.withSuites(List<Suite> suites, {int concurrency}) {
120 var engine = new Engine(concurrency: concurrency);
121 for (var suite in suites) engine.suiteSink.add(suite);
122 engine.suiteSink.close();
123 return engine;
124 }
105 125
106 /// Runs all tests in all suites defined by this engine. 126 /// Runs all tests in all suites defined by this engine.
107 /// 127 ///
108 /// This returns `true` if all tests succeed, and `false` otherwise. It will 128 /// This returns `true` if all tests succeed, and `false` otherwise. It will
109 /// only return once all tests have finished running. 129 /// only return once all tests have finished running and [suiteSink] has been
110 Future<bool> run() async { 130 /// closed.
131 Future<bool> run() {
111 if (_runCalled) { 132 if (_runCalled) {
112 throw new StateError("Engine.run() may not be called more than once."); 133 throw new StateError("Engine.run() may not be called more than once.");
113 } 134 }
114 _runCalled = true; 135 _runCalled = true;
115 136
116 await Future.wait(_liveTestsBySuite.map((suite) { 137 _suiteController.stream.listen((suite) {
117 return _pool.withResource(() { 138 _group.add(_pool.withResource(() {
118 if (_closed) return null; 139 if (_closed) return null;
119 140
120 // TODO(nweiz): Use a real for loop when issue 23394 is fixed. 141 // TODO(nweiz): Use a real for loop when issue 23394 is fixed.
121 return Future.forEach(suite, (liveTest) async { 142 return Future.forEach(suite.tests, (test) async {
122 // TODO(nweiz): Just "return;" when issue 23200 is fixed. 143 if (_closed) return;
123 if (_closed) return null;
124 144
145 var liveTest = test.metadata.skip
146 ? _skippedTest(suite, test)
147 : test.load(suite);
148 _liveTests.add(liveTest);
125 _active.add(liveTest); 149 _active.add(liveTest);
126 150
127 liveTest.onStateChange.listen((state) { 151 liveTest.onStateChange.listen((state) {
128 if (state.status != Status.complete) return; 152 if (state.status != Status.complete) return;
129 _active.remove(liveTest); 153 _active.remove(liveTest);
130 154
131 if (state.result != Result.success) { 155 if (state.result != Result.success) {
132 _passed.remove(liveTest); 156 _passed.remove(liveTest);
133 _failed.add(liveTest); 157 _failed.add(liveTest);
134 } else if (liveTest.test.metadata.skip) { 158 } else if (liveTest.test.metadata.skip) {
135 _skipped.add(liveTest); 159 _skipped.add(liveTest);
136 } else { 160 } else {
137 _passed.add(liveTest); 161 _passed.add(liveTest);
138 } 162 }
139 }); 163 });
140 164
141 _onTestStartedController.add(liveTest); 165 _onTestStartedController.add(liveTest);
142 166
143 // First, schedule a microtask to ensure that [onTestStarted] fires 167 // First, schedule a microtask to ensure that [onTestStarted] fires
144 // before the first [LiveTest.onStateChange] event. Once the test 168 // before the first [LiveTest.onStateChange] event. Once the test
145 // finishes, use [new Future] to do a coarse-grained event loop pump 169 // finishes, use [new Future] to do a coarse-grained event loop pump
146 // to avoid starving the DOM or other non-microtask events. 170 // to avoid starving non-microtask events.
147 await new Future.microtask(liveTest.run); 171 await new Future.microtask(liveTest.run);
148 await new Future(() {}); 172 await new Future(() {});
149 }); 173 });
150 }); 174 }));
151 })); 175 }, onDone: _group.close);
152 176
153 return liveTests.every( 177 return success;
154 (liveTest) => liveTest.state.result == Result.success); 178 }
179
180 /// Returns a dummy [LiveTest] for a test marked as "skip".
181 LiveTest _skippedTest(Suite suite, Test test) {
182 var controller;
183 controller = new LiveTestController(suite, test, () {
184 controller.setState(const State(Status.running, Result.success));
185 controller.setState(const State(Status.complete, Result.success));
186 controller.completer.complete();
187 }, () {});
188 return controller.liveTest;
155 } 189 }
156 190
157 /// Signals that the caller is done paying attention to test results and the 191 /// Signals that the caller is done paying attention to test results and the
158 /// engine should release any resources it has allocated. 192 /// engine should release any resources it has allocated.
159 /// 193 ///
160 /// Any actively-running tests are also closed. VM tests are allowed to finish 194 /// Any actively-running tests are also closed. VM tests are allowed to finish
161 /// running so that any modifications they've made to the filesystem can be 195 /// running so that any modifications they've made to the filesystem can be
162 /// cleaned up. 196 /// cleaned up.
197 ///
198 /// **Note that closing the engine is not the same as closing [suiteSink].**
199 /// Closing [suiteSink] indicates that no more input will be provided, closing
200 /// the engine indicates that no more output should be emitted.
163 Future close() { 201 Future close() {
164 _closed = true; 202 _closed = true;
203 if (_closedBeforeDone == null) _closedBeforeDone = true;
204 _suiteController.close();
205
165 return Future.wait(liveTests.map((liveTest) => liveTest.close())); 206 return Future.wait(liveTests.map((liveTest) => liveTest.close()));
166 } 207 }
167 } 208 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698