| 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 |