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

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

Issue 1206033004: Only load a certain number of test suites at once. (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 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:async/async.dart' hide Result;
(...skipping 24 matching lines...) Expand all
35 /// into the process of loading test files, but as long as that process is 35 /// into the process of loading test files, but as long as that process is
36 /// proceeding normally users usually don't care about it, so the engine only 36 /// proceeding normally users usually don't care about it, so the engine only
37 /// surfaces running load tests (that is, includes them in [liveTests] and other 37 /// surfaces running load tests (that is, includes them in [liveTests] and other
38 /// collections) under specific circumstances. 38 /// collections) under specific circumstances.
39 /// 39 ///
40 /// If only load tests are running, exactly one load test will be in [active] 40 /// If only load tests are running, exactly one load test will be in [active]
41 /// and [liveTests]. If this test passes, it will be removed from both [active] 41 /// and [liveTests]. If this test passes, it will be removed from both [active]
42 /// and [liveTests] and *will not* be added to [passed]. If at any point a load 42 /// and [liveTests] and *will not* be added to [passed]. If at any point a load
43 /// test fails, it will be added to [failed] and [liveTests]. 43 /// test fails, it will be added to [failed] and [liveTests].
44 /// 44 ///
45 /// The test suite loaded by a load suite will be automatically be run by the
46 /// engine; it doesn't need to be added to [suiteSink] manually.
47 ///
45 /// Load tests will always be emitted through [onTestStarted] so users can watch 48 /// Load tests will always be emitted through [onTestStarted] so users can watch
46 /// their event streams once they start running. 49 /// their event streams once they start running.
47 class Engine { 50 class Engine {
48 /// Whether [run] has been called yet. 51 /// Whether [run] has been called yet.
49 var _runCalled = false; 52 var _runCalled = false;
50 53
51 /// Whether [close] has been called. 54 /// Whether [close] has been called.
52 var _closed = false; 55 var _closed = false;
53 56
54 /// Whether [close] was called before all the tests finished running. 57 /// Whether [close] was called before all the tests finished running.
55 /// 58 ///
56 /// This is `null` if close hasn't been called and the tests are still 59 /// This is `null` if close hasn't been called and the tests are still
57 /// running, `true` if close was called before the tests finished running, and 60 /// running, `true` if close was called before the tests finished running, and
58 /// `false` if the tests finished running before close was called. 61 /// `false` if the tests finished running before close was called.
59 var _closedBeforeDone; 62 var _closedBeforeDone;
60 63
61 /// A pool that limits the number of test suites running concurrently. 64 /// A pool that limits the number of test suites running concurrently.
62 final Pool _pool; 65 final Pool _runPool;
66
67 /// A pool that limits the number of test suites loaded concurrently.
68 ///
69 /// Once this reaches its limit, loading any additional test suites will cause
70 /// previous suites to be unloaded in the order they completed.
71 final Pool _loadPool;
63 72
64 /// Whether all tests passed. 73 /// Whether all tests passed.
65 /// 74 ///
66 /// This fires once all tests have completed and [suiteSink] has been closed. 75 /// This fires once all tests have completed and [suiteSink] has been closed.
67 /// This will be `null` if [close] was called before all the tests finished 76 /// This will be `null` if [close] was called before all the tests finished
68 /// running. 77 /// running.
69 Future<bool> get success async { 78 Future<bool> get success async {
70 await _group.future; 79 await _group.future;
71 if (_closedBeforeDone) return null; 80 if (_closedBeforeDone) return null;
72 return liveTests.every((liveTest) => 81 return liveTests.every((liveTest) =>
73 liveTest.state.result == Result.success); 82 liveTest.state.result == Result.success);
74 } 83 }
75 84
76 /// A group of futures for each test suite. 85 /// A group of futures for each test suite.
77 final _group = new FutureGroup(); 86 final _group = new FutureGroup();
78 87
79 /// A sink used to pass [Suite]s in to the engine to run. 88 /// A sink used to pass [Suite]s in to the engine to run.
80 /// 89 ///
81 /// Suites may be added as quickly as they're available; the Engine will only 90 /// Suites may be added as quickly as they're available; the Engine will only
82 /// run as many as necessary at a time based on its concurrency settings. 91 /// run as many as necessary at a time based on its concurrency settings.
92 ///
93 /// Suites added to the sink will be closed by the engine based on its
94 /// internal logic.
83 Sink<Suite> get suiteSink => new DelegatingSink(_suiteController.sink); 95 Sink<Suite> get suiteSink => new DelegatingSink(_suiteController.sink);
84 final _suiteController = new StreamController<Suite>(); 96 final _suiteController = new StreamController<Suite>();
85 97
86 /// All the currently-known tests that have run, are running, or will run. 98 /// All the currently-known tests that have run, are running, or will run.
87 /// 99 ///
88 /// These are [LiveTest]s, representing the in-progress state of each test. 100 /// These are [LiveTest]s, representing the in-progress state of each test.
89 /// Tests that have not yet begun running are marked [Status.pending]; tests 101 /// Tests that have not yet begun running are marked [Status.pending]; tests
90 /// that have finished are marked [Status.complete]. 102 /// that have finished are marked [Status.complete].
91 /// 103 ///
92 /// This is guaranteed to contain the same tests as the union of [passed], 104 /// This is guaranteed to contain the same tests as the union of [passed],
(...skipping 26 matching lines...) Expand all
119 final _active = new QueueList<LiveTest>(); 131 final _active = new QueueList<LiveTest>();
120 132
121 /// The tests from [LoadSuite]s that are still running, in the order they 133 /// The tests from [LoadSuite]s that are still running, in the order they
122 /// began running. 134 /// began running.
123 /// 135 ///
124 /// This is separate from [active] because load tests aren't always surfaced. 136 /// This is separate from [active] because load tests aren't always surfaced.
125 final _activeLoadTests = new List<LiveTest>(); 137 final _activeLoadTests = new List<LiveTest>();
126 138
127 /// Creates an [Engine] that will run all tests provided via [suiteSink]. 139 /// Creates an [Engine] that will run all tests provided via [suiteSink].
128 /// 140 ///
129 /// [concurrency] controls how many suites are run at once. 141 /// [concurrency] controls how many suites are run at once, and defaults to 1.
130 Engine({int concurrency}) 142 /// [maxSuites] controls how many suites are *loaded* at once, and defaults to
131 : _pool = new Pool(concurrency == null ? 1 : concurrency) { 143 /// four times [concurrency].
144 Engine({int concurrency, int maxSuites})
145 : _runPool = new Pool(concurrency == null ? 1 : concurrency),
146 _loadPool = new Pool(maxSuites == null
147 ? (concurrency == null ? 4 : concurrency * 4)
148 : maxSuites) {
132 _group.future.then((_) { 149 _group.future.then((_) {
133 if (_closedBeforeDone == null) _closedBeforeDone = false; 150 if (_closedBeforeDone == null) _closedBeforeDone = false;
134 }).catchError((_) { 151 }).catchError((_) {
135 // Don't top-level errors. They'll be thrown via [success] anyway. 152 // Don't top-level errors. They'll be thrown via [success] anyway.
136 }); 153 });
137 } 154 }
138 155
139 /// Creates an [Engine] that will run all tests in [suites]. 156 /// Creates an [Engine] that will run all tests in [suites].
140 /// 157 ///
141 /// [concurrency] controls how many suites are run at once. An engine 158 /// [concurrency] controls how many suites are run at once. An engine
(...skipping 11 matching lines...) Expand all
153 /// This returns `true` if all tests succeed, and `false` otherwise. It will 170 /// This returns `true` if all tests succeed, and `false` otherwise. It will
154 /// only return once all tests have finished running and [suiteSink] has been 171 /// only return once all tests have finished running and [suiteSink] has been
155 /// closed. 172 /// closed.
156 Future<bool> run() { 173 Future<bool> run() {
157 if (_runCalled) { 174 if (_runCalled) {
158 throw new StateError("Engine.run() may not be called more than once."); 175 throw new StateError("Engine.run() may not be called more than once.");
159 } 176 }
160 _runCalled = true; 177 _runCalled = true;
161 178
162 _suiteController.stream.listen((suite) { 179 _suiteController.stream.listen((suite) {
163 if (suite is LoadSuite) { 180 _group.add(new Future.sync(() async {
164 _group.add(_addLoadSuite(suite)); 181 var loadResource = await _loadPool.request();
165 return;
166 }
167 182
168 _group.add(_pool.withResource(() { 183 if (suite is LoadSuite) {
169 if (_closed) return null; 184 suite = await _addLoadSuite(suite);
185 if (suite == null) {
186 loadResource.release();
187 return;
188 }
189 }
170 190
171 // TODO(nweiz): Use a real for loop when issue 23394 is fixed. 191 await _runPool.withResource(() async {
172 return Future.forEach(suite.tests, (test) async { 192 if (_closed) return null;
173 if (_closed) return;
174 193
175 var liveTest = test.metadata.skip 194 // TODO(nweiz): Use a real for loop when issue 23394 is fixed.
176 ? _skippedTest(suite, test) 195 await Future.forEach(suite.tests, (test) async {
177 : test.load(suite); 196 if (_closed) return;
178 _liveTests.add(liveTest);
179 _active.add(liveTest);
180 197
181 // If there were no active non-load tests, the current active test 198 var liveTest = test.metadata.skip
182 // would have been a load test. In that case, remove it, since now we 199 ? _skippedTest(suite, test)
183 // have a non-load test to add. 200 : test.load(suite);
184 if (_active.isNotEmpty && _active.first.suite is LoadSuite) { 201 _liveTests.add(liveTest);
185 _liveTests.remove(_active.removeFirst()); 202 _active.add(liveTest);
186 }
187 203
188 liveTest.onStateChange.listen((state) { 204 // If there were no active non-load tests, the current active test
189 if (state.status != Status.complete) return; 205 // would have been a load test. In that case, remove it, since now w e
190 _active.remove(liveTest); 206 // have a non-load test to add.
191 207 if (_active.isNotEmpty && _active.first.suite is LoadSuite) {
192 // If we're out of non-load tests, surface a load test. 208 _liveTests.remove(_active.removeFirst());
193 if (_active.isEmpty && _activeLoadTests.isNotEmpty) {
194 _active.add(_activeLoadTests.first);
195 _liveTests.add(_activeLoadTests.first);
196 } 209 }
197 210
198 if (state.result != Result.success) { 211 liveTest.onStateChange.listen((state) {
199 _passed.remove(liveTest); 212 if (state.status != Status.complete) return;
200 _failed.add(liveTest); 213 _active.remove(liveTest);
201 } else if (liveTest.test.metadata.skip) { 214
202 _skipped.add(liveTest); 215 // If we're out of non-load tests, surface a load test.
203 } else { 216 if (_active.isEmpty && _activeLoadTests.isNotEmpty) {
204 _passed.add(liveTest); 217 _active.add(_activeLoadTests.first);
205 } 218 _liveTests.add(_activeLoadTests.first);
219 }
220
221 if (state.result != Result.success) {
222 _passed.remove(liveTest);
223 _failed.add(liveTest);
224 } else if (liveTest.test.metadata.skip) {
225 _skipped.add(liveTest);
226 } else {
227 _passed.add(liveTest);
228 }
229 });
230
231 _onTestStartedController.add(liveTest);
232
233 // First, schedule a microtask to ensure that [onTestStarted] fires
234 // before the first [LiveTest.onStateChange] event. Once the test
235 // finishes, use [new Future] to do a coarse-grained event loop pump
236 // to avoid starving non-microtask events.
237 await new Future.microtask(liveTest.run);
238 await new Future(() {});
206 }); 239 });
207 240
208 _onTestStartedController.add(liveTest); 241 loadResource.allowRelease(() => suite.close());
209
210 // First, schedule a microtask to ensure that [onTestStarted] fires
211 // before the first [LiveTest.onStateChange] event. Once the test
212 // finishes, use [new Future] to do a coarse-grained event loop pump
213 // to avoid starving non-microtask events.
214 await new Future.microtask(liveTest.run);
215 await new Future(() {});
216 }); 242 });
217 })); 243 }));
218 }, onDone: _group.close); 244 }, onDone: _group.close);
219 245
220 return success; 246 return success;
221 } 247 }
222 248
223 /// Returns a dummy [LiveTest] for a test marked as "skip". 249 /// Returns a dummy [LiveTest] for a test marked as "skip".
224 LiveTest _skippedTest(Suite suite, Test test) { 250 LiveTest _skippedTest(Suite suite, Test test) {
225 var controller; 251 var controller;
226 controller = new LiveTestController(suite, test, () { 252 controller = new LiveTestController(suite, test, () {
227 controller.setState(const State(Status.running, Result.success)); 253 controller.setState(const State(Status.running, Result.success));
228 controller.setState(const State(Status.complete, Result.success)); 254 controller.setState(const State(Status.complete, Result.success));
229 controller.completer.complete(); 255 controller.completer.complete();
230 }, () {}); 256 }, () {});
231 return controller.liveTest; 257 return controller.liveTest;
232 } 258 }
233 259
234 /// Adds listeners for [suite]. 260 /// Adds listeners for [suite].
235 /// 261 ///
236 /// Load suites have specific logic apart from normal test suites. 262 /// Load suites have specific logic apart from normal test suites.
237 Future _addLoadSuite(LoadSuite suite) async { 263 Future<Suite> _addLoadSuite(LoadSuite suite) async {
238 var liveTest = await suite.tests.single.load(suite); 264 var liveTest = await suite.tests.single.load(suite);
239 265
240 _activeLoadTests.add(liveTest); 266 _activeLoadTests.add(liveTest);
241 267
242 // Only surface the load test if there are no other tests currently running. 268 // Only surface the load test if there are no other tests currently running.
243 if (_active.isEmpty) { 269 if (_active.isEmpty) {
244 _liveTests.add(liveTest); 270 _liveTests.add(liveTest);
245 _active.add(liveTest); 271 _active.add(liveTest);
246 } 272 }
247 273
(...skipping 17 matching lines...) Expand all
265 // Surface the load test if it fails so that the user can see the failure. 291 // Surface the load test if it fails so that the user can see the failure.
266 if (state.result == Result.success) return; 292 if (state.result == Result.success) return;
267 _failed.add(liveTest); 293 _failed.add(liveTest);
268 _liveTests.add(liveTest); 294 _liveTests.add(liveTest);
269 }); 295 });
270 296
271 // Run the test immediately. We don't want loading to be blocked on suites 297 // Run the test immediately. We don't want loading to be blocked on suites
272 // that are already running. 298 // that are already running.
273 _onTestStartedController.add(liveTest); 299 _onTestStartedController.add(liveTest);
274 await liveTest.run(); 300 await liveTest.run();
301
302 return suite.suite;
275 } 303 }
276 304
277 /// Signals that the caller is done paying attention to test results and the 305 /// Signals that the caller is done paying attention to test results and the
278 /// engine should release any resources it has allocated. 306 /// engine should release any resources it has allocated.
279 /// 307 ///
280 /// Any actively-running tests are also closed. VM tests are allowed to finish 308 /// Any actively-running tests are also closed. VM tests are allowed to finish
281 /// running so that any modifications they've made to the filesystem can be 309 /// running so that any modifications they've made to the filesystem can be
282 /// cleaned up. 310 /// cleaned up.
283 /// 311 ///
284 /// **Note that closing the engine is not the same as closing [suiteSink].** 312 /// **Note that closing the engine is not the same as closing [suiteSink].**
285 /// Closing [suiteSink] indicates that no more input will be provided, closing 313 /// Closing [suiteSink] indicates that no more input will be provided, closing
286 /// the engine indicates that no more output should be emitted. 314 /// the engine indicates that no more output should be emitted.
287 Future close() { 315 Future close() async {
288 _closed = true; 316 _closed = true;
289 if (_closedBeforeDone == null) _closedBeforeDone = true; 317 if (_closedBeforeDone == null) _closedBeforeDone = true;
290 _suiteController.close(); 318 _suiteController.close();
291 319
320 // Close the running tests first so that we're sure to wait for them to
321 // finish before we close their suites and cause them to become unloaded.
292 var allLiveTests = liveTests.toSet()..addAll(_activeLoadTests); 322 var allLiveTests = liveTests.toSet()..addAll(_activeLoadTests);
293 return Future.wait(allLiveTests.map((liveTest) => liveTest.close())); 323 await Future.wait(allLiveTests.map((liveTest) => liveTest.close()));
324
325 var allSuites = allLiveTests.map((liveTest) => liveTest.suite).toSet();
326 await Future.wait(allSuites.map((suite) => suite.close()));
294 } 327 }
295 } 328 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698