OLD | NEW |
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; |
11 import 'package:collection/collection.dart'; | 11 import 'package:collection/collection.dart'; |
12 import 'package:pool/pool.dart'; | 12 import 'package:pool/pool.dart'; |
13 | 13 |
| 14 import '../backend/group.dart'; |
| 15 import '../backend/invoker.dart'; |
14 import '../backend/live_test.dart'; | 16 import '../backend/live_test.dart'; |
15 import '../backend/live_test_controller.dart'; | 17 import '../backend/live_test_controller.dart'; |
16 import '../backend/state.dart'; | 18 import '../backend/state.dart'; |
| 19 import '../backend/suite_entry.dart'; |
17 import '../backend/test.dart'; | 20 import '../backend/test.dart'; |
18 import 'load_suite.dart'; | 21 import 'load_suite.dart'; |
19 import 'runner_suite.dart'; | 22 import 'runner_suite.dart'; |
20 | 23 |
21 /// An [Engine] manages a run that encompasses multiple test suites. | 24 /// An [Engine] manages a run that encompasses multiple test suites. |
22 /// | 25 /// |
23 /// Test suites are provided by passing them into [suiteSink]. Once all suites | 26 /// Test suites are provided by passing them into [suiteSink]. Once all suites |
24 /// have been provided, the user should close [suiteSink] to indicate this. | 27 /// have been provided, the user should close [suiteSink] to indicate this. |
25 /// [run] won't terminate until [suiteSink] is closed. Suites will be run in the | 28 /// [run] won't terminate until [suiteSink] is closed. Suites will be run in the |
26 /// order they're provided to [suiteSink]. Tests within those suites will | 29 /// order they're provided to [suiteSink]. Tests within those suites will |
27 /// likewise be run in the order of [Suite.tests]. | 30 /// likewise be run in the order they're declared. |
28 /// | 31 /// |
29 /// The current status of every test is visible via [liveTests]. [onTestStarted] | 32 /// The current status of every test is visible via [liveTests]. [onTestStarted] |
30 /// can also be used to be notified when a test is about to be run. | 33 /// can also be used to be notified when a test is about to be run. |
31 /// | 34 /// |
32 /// The engine has some special logic for [LoadSuite]s and the tests they | 35 /// The engine has some special logic for [LoadSuite]s and the tests they |
33 /// contain, referred to as "load tests". Load tests exist to provide visibility | 36 /// contain, referred to as "load tests". Load tests exist to provide visibility |
34 /// into the process of loading test files, but as long as that process is | 37 /// into the process of loading test files, but as long as that process is |
35 /// proceeding normally users usually don't care about it, so the engine only | 38 /// proceeding normally users usually don't care about it, so the engine only |
36 /// surfaces running load tests (that is, includes them in [liveTests] and other | 39 /// surfaces running load tests (that is, includes them in [liveTests] and other |
37 /// collections) under specific circumstances. | 40 /// collections) under specific circumstances. |
(...skipping 150 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
188 | 191 |
189 if (suite is LoadSuite) { | 192 if (suite is LoadSuite) { |
190 suite = await _addLoadSuite(suite); | 193 suite = await _addLoadSuite(suite); |
191 if (suite == null) { | 194 if (suite == null) { |
192 loadResource.release(); | 195 loadResource.release(); |
193 return; | 196 return; |
194 } | 197 } |
195 } | 198 } |
196 | 199 |
197 await _runPool.withResource(() async { | 200 await _runPool.withResource(() async { |
198 if (_closed) return null; | 201 if (_closed) return; |
199 | 202 await _runEntries(suite, suite.entries); |
200 // TODO(nweiz): Use a real for loop when issue 23394 is fixed. | |
201 await Future.forEach(suite.tests, (test) async { | |
202 if (_closed) return; | |
203 | |
204 var liveTest = test.metadata.skip | |
205 ? _skippedTest(suite, test) | |
206 : test.load(suite); | |
207 _liveTests.add(liveTest); | |
208 _active.add(liveTest); | |
209 | |
210 // If there were no active non-load tests, the current active test | |
211 // would have been a load test. In that case, remove it, since now w
e | |
212 // have a non-load test to add. | |
213 if (_active.isNotEmpty && _active.first.suite is LoadSuite) { | |
214 _liveTests.remove(_active.removeFirst()); | |
215 } | |
216 | |
217 liveTest.onStateChange.listen((state) { | |
218 if (state.status != Status.complete) return; | |
219 _active.remove(liveTest); | |
220 | |
221 // If we're out of non-load tests, surface a load test. | |
222 if (_active.isEmpty && _activeLoadTests.isNotEmpty) { | |
223 _active.add(_activeLoadTests.first); | |
224 _liveTests.add(_activeLoadTests.first); | |
225 } | |
226 | |
227 if (state.result != Result.success) { | |
228 _passed.remove(liveTest); | |
229 _failed.add(liveTest); | |
230 } else if (liveTest.test.metadata.skip) { | |
231 _skipped.add(liveTest); | |
232 } else { | |
233 _passed.add(liveTest); | |
234 } | |
235 }); | |
236 | |
237 _onTestStartedController.add(liveTest); | |
238 | |
239 // First, schedule a microtask to ensure that [onTestStarted] fires | |
240 // before the first [LiveTest.onStateChange] event. Once the test | |
241 // finishes, use [new Future] to do a coarse-grained event loop pump | |
242 // to avoid starving non-microtask events. | |
243 await new Future.microtask(liveTest.run); | |
244 await new Future(() {}); | |
245 }); | |
246 | |
247 loadResource.allowRelease(() => suite.close()); | 203 loadResource.allowRelease(() => suite.close()); |
248 }); | 204 }); |
249 })); | 205 })); |
250 }, onDone: _group.close); | 206 }, onDone: _group.close); |
251 | 207 |
252 return success; | 208 return success; |
253 } | 209 } |
254 | 210 |
255 /// Returns a dummy [LiveTest] for a test marked as "skip". | 211 /// Runs all the entries in [entries] in sequence. |
256 LiveTest _skippedTest(RunnerSuite suite, Test test) { | 212 Future _runEntries(RunnerSuite suite, List<SuiteEntry> entries) async { |
| 213 for (var entry in entries) { |
| 214 if (_closed) return; |
| 215 |
| 216 if (entry.metadata.skip) { |
| 217 await _runLiveTest(_skippedTest(suite, entry)); |
| 218 } else if (entry is Test) { |
| 219 await _runLiveTest(entry.load(suite)); |
| 220 } else { |
| 221 var group = entry as Group; |
| 222 await _runEntries(suite, group.entries); |
| 223 } |
| 224 } |
| 225 } |
| 226 |
| 227 /// Returns a dummy [LiveTest] for a test or group marked as "skip". |
| 228 LiveTest _skippedTest(RunnerSuite suite, SuiteEntry entry) { |
| 229 var test = new LocalTest(entry.name, entry.metadata, () {}); |
257 var controller; | 230 var controller; |
258 controller = new LiveTestController(suite, test, () { | 231 controller = new LiveTestController(suite, test, () { |
259 controller.setState(const State(Status.running, Result.success)); | 232 controller.setState(const State(Status.running, Result.success)); |
260 controller.setState(const State(Status.complete, Result.success)); | 233 controller.setState(const State(Status.complete, Result.success)); |
261 controller.completer.complete(); | 234 controller.completer.complete(); |
262 }, () {}); | 235 }, () {}); |
263 return controller.liveTest; | 236 return controller.liveTest; |
264 } | 237 } |
265 | 238 |
| 239 /// Runs [liveTest]. |
| 240 Future _runLiveTest(LiveTest liveTest) async { |
| 241 _liveTests.add(liveTest); |
| 242 _active.add(liveTest); |
| 243 |
| 244 // If there were no active non-load tests, the current active test would |
| 245 // have been a load test. In that case, remove it, since now we have a |
| 246 // non-load test to add. |
| 247 if (_active.isNotEmpty && _active.first.suite is LoadSuite) { |
| 248 _liveTests.remove(_active.removeFirst()); |
| 249 } |
| 250 |
| 251 liveTest.onStateChange.listen((state) { |
| 252 if (state.status != Status.complete) return; |
| 253 _active.remove(liveTest); |
| 254 |
| 255 // If we're out of non-load tests, surface a load test. |
| 256 if (_active.isEmpty && _activeLoadTests.isNotEmpty) { |
| 257 _active.add(_activeLoadTests.first); |
| 258 _liveTests.add(_activeLoadTests.first); |
| 259 } |
| 260 |
| 261 if (state.result != Result.success) { |
| 262 _passed.remove(liveTest); |
| 263 _failed.add(liveTest); |
| 264 } else if (liveTest.test.metadata.skip) { |
| 265 _skipped.add(liveTest); |
| 266 } else { |
| 267 _passed.add(liveTest); |
| 268 } |
| 269 }); |
| 270 |
| 271 _onTestStartedController.add(liveTest); |
| 272 |
| 273 // First, schedule a microtask to ensure that [onTestStarted] fires before |
| 274 // the first [LiveTest.onStateChange] event. Once the test finishes, use |
| 275 // [new Future] to do a coarse-grained event loop pump to avoid starving |
| 276 // non-microtask events. |
| 277 await new Future.microtask(liveTest.run); |
| 278 await new Future(() {}); |
| 279 } |
| 280 |
266 /// Adds listeners for [suite]. | 281 /// Adds listeners for [suite]. |
267 /// | 282 /// |
268 /// Load suites have specific logic apart from normal test suites. | 283 /// Load suites have specific logic apart from normal test suites. |
269 Future<RunnerSuite> _addLoadSuite(LoadSuite suite) async { | 284 Future<RunnerSuite> _addLoadSuite(LoadSuite suite) async { |
270 var liveTest = await suite.tests.single.load(suite); | 285 var liveTest = await suite.test.load(suite); |
271 | 286 |
272 _activeLoadTests.add(liveTest); | 287 _activeLoadTests.add(liveTest); |
273 | 288 |
274 // Only surface the load test if there are no other tests currently running. | 289 // Only surface the load test if there are no other tests currently running. |
275 if (_active.isEmpty) { | 290 if (_active.isEmpty) { |
276 _liveTests.add(liveTest); | 291 _liveTests.add(liveTest); |
277 _active.add(liveTest); | 292 _active.add(liveTest); |
278 } | 293 } |
279 | 294 |
280 liveTest.onStateChange.listen((state) { | 295 liveTest.onStateChange.listen((state) { |
(...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
325 | 340 |
326 // Close the running tests first so that we're sure to wait for them to | 341 // Close the running tests first so that we're sure to wait for them to |
327 // finish before we close their suites and cause them to become unloaded. | 342 // finish before we close their suites and cause them to become unloaded. |
328 var allLiveTests = liveTests.toSet()..addAll(_activeLoadTests); | 343 var allLiveTests = liveTests.toSet()..addAll(_activeLoadTests); |
329 await Future.wait(allLiveTests.map((liveTest) => liveTest.close())); | 344 await Future.wait(allLiveTests.map((liveTest) => liveTest.close())); |
330 | 345 |
331 var allSuites = allLiveTests.map((liveTest) => liveTest.suite).toSet(); | 346 var allSuites = allLiveTests.map((liveTest) => liveTest.suite).toSet(); |
332 await Future.wait(allSuites.map((suite) => suite.close())); | 347 await Future.wait(allSuites.map((suite) => suite.close())); |
333 } | 348 } |
334 } | 349 } |
OLD | NEW |