| 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/live_test.dart'; | 14 import '../backend/live_test.dart'; |
| 15 import '../backend/live_test_controller.dart'; | 15 import '../backend/live_test_controller.dart'; |
| 16 import '../backend/state.dart'; | 16 import '../backend/state.dart'; |
| 17 import '../backend/suite.dart'; | 17 import '../backend/suite.dart'; |
| 18 import '../backend/test.dart'; | 18 import '../backend/test.dart'; |
| 19 import '../util/delegating_sink.dart'; | 19 import '../util/delegating_sink.dart'; |
| 20 import 'load_suite.dart'; |
| 20 | 21 |
| 21 /// An [Engine] manages a run that encompasses multiple test suites. | 22 /// An [Engine] manages a run that encompasses multiple test suites. |
| 22 /// | 23 /// |
| 24 /// Test suites are provided by passing them into [suiteSink]. Once all suites |
| 25 /// have been provided, the user should close [suiteSink] to indicate this. |
| 26 /// [run] won't terminate until [suiteSink] is closed. Suites will be run in the |
| 27 /// order they're provided to [suiteSink]. Tests within those suites will |
| 28 /// likewise be run in the order of [Suite.tests]. |
| 29 /// |
| 23 /// The current status of every test is visible via [liveTests]. [onTestStarted] | 30 /// The current status of every test is visible via [liveTests]. [onTestStarted] |
| 24 /// can also be used to be notified when a test is about to be run. | 31 /// can also be used to be notified when a test is about to be run. |
| 25 /// | 32 /// |
| 26 /// Suites will be run in the order they're provided to [new Engine]. Tests | 33 /// The engine has some special logic for [LoadSuite]s and the tests they |
| 27 /// within those suites will likewise be run in the order of [Suite.tests]. | 34 /// contain, referred to as "load tests". Load tests exist to provide visibility |
| 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 |
| 37 /// surfaces running load tests (that is, includes them in [liveTests] and other |
| 38 /// collections) under specific circumstances. |
| 39 /// |
| 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] |
| 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]. |
| 44 /// |
| 45 /// Load tests will always be emitted through [onTestStarted] so users can watch |
| 46 /// their event streams once they start running. |
| 28 class Engine { | 47 class Engine { |
| 29 /// Whether [run] has been called yet. | 48 /// Whether [run] has been called yet. |
| 30 var _runCalled = false; | 49 var _runCalled = false; |
| 31 | 50 |
| 32 /// Whether [close] has been called. | 51 /// Whether [close] has been called. |
| 33 var _closed = false; | 52 var _closed = false; |
| 34 | 53 |
| 35 /// Whether [close] was called before all the tests finished running. | 54 /// Whether [close] was called before all the tests finished running. |
| 36 /// | 55 /// |
| 37 /// This is `null` if close hasn't been called and the tests are still | 56 /// This is `null` if close hasn't been called and the tests are still |
| (...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 90 /// The set of tests that have completed and been marked as skipped. | 109 /// The set of tests that have completed and been marked as skipped. |
| 91 Set<LiveTest> get skipped => new UnmodifiableSetView(_skipped); | 110 Set<LiveTest> get skipped => new UnmodifiableSetView(_skipped); |
| 92 final _skipped = new Set<LiveTest>(); | 111 final _skipped = new Set<LiveTest>(); |
| 93 | 112 |
| 94 /// The set of tests that have completed and been marked as failing or error. | 113 /// The set of tests that have completed and been marked as failing or error. |
| 95 Set<LiveTest> get failed => new UnmodifiableSetView(_failed); | 114 Set<LiveTest> get failed => new UnmodifiableSetView(_failed); |
| 96 final _failed = new Set<LiveTest>(); | 115 final _failed = new Set<LiveTest>(); |
| 97 | 116 |
| 98 /// The tests that are still running, in the order they begain running. | 117 /// The tests that are still running, in the order they begain running. |
| 99 List<LiveTest> get active => new UnmodifiableListView(_active); | 118 List<LiveTest> get active => new UnmodifiableListView(_active); |
| 100 final _active = new List<LiveTest>(); | 119 final _active = new QueueList<LiveTest>(); |
| 120 |
| 121 /// The tests from [LoadSuite]s that are still running, in the order they |
| 122 /// began running. |
| 123 /// |
| 124 /// This is separate from [active] because load tests aren't always surfaced. |
| 125 final _activeLoadTests = new List<LiveTest>(); |
| 101 | 126 |
| 102 /// Creates an [Engine] that will run all tests provided via [suiteSink]. | 127 /// Creates an [Engine] that will run all tests provided via [suiteSink]. |
| 103 /// | 128 /// |
| 104 /// [concurrency] controls how many suites are run at once. | 129 /// [concurrency] controls how many suites are run at once. |
| 105 Engine({int concurrency}) | 130 Engine({int concurrency}) |
| 106 : _pool = new Pool(concurrency == null ? 1 : concurrency) { | 131 : _pool = new Pool(concurrency == null ? 1 : concurrency) { |
| 107 _group.future.then((_) { | 132 _group.future.then((_) { |
| 108 if (_closedBeforeDone == null) _closedBeforeDone = false; | 133 if (_closedBeforeDone == null) _closedBeforeDone = false; |
| 109 }).catchError((_) { | 134 }).catchError((_) { |
| 110 // Don't top-level errors. They'll be thrown via [success] anyway. | 135 // Don't top-level errors. They'll be thrown via [success] anyway. |
| (...skipping 17 matching lines...) Expand all Loading... |
| 128 /// This returns `true` if all tests succeed, and `false` otherwise. It will | 153 /// This returns `true` if all tests succeed, and `false` otherwise. It will |
| 129 /// only return once all tests have finished running and [suiteSink] has been | 154 /// only return once all tests have finished running and [suiteSink] has been |
| 130 /// closed. | 155 /// closed. |
| 131 Future<bool> run() { | 156 Future<bool> run() { |
| 132 if (_runCalled) { | 157 if (_runCalled) { |
| 133 throw new StateError("Engine.run() may not be called more than once."); | 158 throw new StateError("Engine.run() may not be called more than once."); |
| 134 } | 159 } |
| 135 _runCalled = true; | 160 _runCalled = true; |
| 136 | 161 |
| 137 _suiteController.stream.listen((suite) { | 162 _suiteController.stream.listen((suite) { |
| 163 if (suite is LoadSuite) { |
| 164 _group.add(_addLoadSuite(suite)); |
| 165 return; |
| 166 } |
| 167 |
| 138 _group.add(_pool.withResource(() { | 168 _group.add(_pool.withResource(() { |
| 139 if (_closed) return null; | 169 if (_closed) return null; |
| 140 | 170 |
| 141 // TODO(nweiz): Use a real for loop when issue 23394 is fixed. | 171 // TODO(nweiz): Use a real for loop when issue 23394 is fixed. |
| 142 return Future.forEach(suite.tests, (test) async { | 172 return Future.forEach(suite.tests, (test) async { |
| 143 if (_closed) return; | 173 if (_closed) return; |
| 144 | 174 |
| 145 var liveTest = test.metadata.skip | 175 var liveTest = test.metadata.skip |
| 146 ? _skippedTest(suite, test) | 176 ? _skippedTest(suite, test) |
| 147 : test.load(suite); | 177 : test.load(suite); |
| 148 _liveTests.add(liveTest); | 178 _liveTests.add(liveTest); |
| 149 _active.add(liveTest); | 179 _active.add(liveTest); |
| 150 | 180 |
| 181 // If there were no active non-load tests, the current active test |
| 182 // would have been a load test. In that case, remove it, since now we |
| 183 // have a non-load test to add. |
| 184 if (_active.isNotEmpty && _active.first.suite is LoadSuite) { |
| 185 _liveTests.remove(_active.removeFirst()); |
| 186 } |
| 187 |
| 151 liveTest.onStateChange.listen((state) { | 188 liveTest.onStateChange.listen((state) { |
| 152 if (state.status != Status.complete) return; | 189 if (state.status != Status.complete) return; |
| 153 _active.remove(liveTest); | 190 _active.remove(liveTest); |
| 154 | 191 |
| 192 // If we're out of non-load tests, surface a load test. |
| 193 if (_active.isEmpty && _activeLoadTests.isNotEmpty) { |
| 194 _active.add(_activeLoadTests.first); |
| 195 _liveTests.add(_activeLoadTests.first); |
| 196 } |
| 197 |
| 155 if (state.result != Result.success) { | 198 if (state.result != Result.success) { |
| 156 _passed.remove(liveTest); | 199 _passed.remove(liveTest); |
| 157 _failed.add(liveTest); | 200 _failed.add(liveTest); |
| 158 } else if (liveTest.test.metadata.skip) { | 201 } else if (liveTest.test.metadata.skip) { |
| 159 _skipped.add(liveTest); | 202 _skipped.add(liveTest); |
| 160 } else { | 203 } else { |
| 161 _passed.add(liveTest); | 204 _passed.add(liveTest); |
| 162 } | 205 } |
| 163 }); | 206 }); |
| 164 | 207 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 181 LiveTest _skippedTest(Suite suite, Test test) { | 224 LiveTest _skippedTest(Suite suite, Test test) { |
| 182 var controller; | 225 var controller; |
| 183 controller = new LiveTestController(suite, test, () { | 226 controller = new LiveTestController(suite, test, () { |
| 184 controller.setState(const State(Status.running, Result.success)); | 227 controller.setState(const State(Status.running, Result.success)); |
| 185 controller.setState(const State(Status.complete, Result.success)); | 228 controller.setState(const State(Status.complete, Result.success)); |
| 186 controller.completer.complete(); | 229 controller.completer.complete(); |
| 187 }, () {}); | 230 }, () {}); |
| 188 return controller.liveTest; | 231 return controller.liveTest; |
| 189 } | 232 } |
| 190 | 233 |
| 234 /// Adds listeners for [suite]. |
| 235 /// |
| 236 /// Load suites have specific logic apart from normal test suites. |
| 237 Future _addLoadSuite(LoadSuite suite) async { |
| 238 var liveTest = await suite.tests.single.load(suite); |
| 239 |
| 240 _activeLoadTests.add(liveTest); |
| 241 |
| 242 // Only surface the load test if there are no other tests currently running. |
| 243 if (_active.isEmpty) { |
| 244 _liveTests.add(liveTest); |
| 245 _active.add(liveTest); |
| 246 } |
| 247 |
| 248 liveTest.onStateChange.listen((state) { |
| 249 if (state.status != Status.complete) return; |
| 250 _activeLoadTests.remove(liveTest); |
| 251 |
| 252 // Only one load test will be active at any given time, and it will always |
| 253 // be the only active test. Remove it and, if possible, surface another |
| 254 // load test. |
| 255 if (_active.isNotEmpty && _active.first.suite == suite) { |
| 256 _active.remove(liveTest); |
| 257 _liveTests.remove(liveTest); |
| 258 |
| 259 if (_activeLoadTests.isNotEmpty) { |
| 260 _active.add(_activeLoadTests.last); |
| 261 _liveTests.add(_activeLoadTests.last); |
| 262 } |
| 263 } |
| 264 |
| 265 // Surface the load test if it fails so that the user can see the failure. |
| 266 if (state.result == Result.success) return; |
| 267 _failed.add(liveTest); |
| 268 _liveTests.add(liveTest); |
| 269 }); |
| 270 |
| 271 // Run the test immediately. We don't want loading to be blocked on suites |
| 272 // that are already running. |
| 273 _onTestStartedController.add(liveTest); |
| 274 await liveTest.run(); |
| 275 } |
| 276 |
| 191 /// Signals that the caller is done paying attention to test results and the | 277 /// Signals that the caller is done paying attention to test results and the |
| 192 /// engine should release any resources it has allocated. | 278 /// engine should release any resources it has allocated. |
| 193 /// | 279 /// |
| 194 /// Any actively-running tests are also closed. VM tests are allowed to finish | 280 /// Any actively-running tests are also closed. VM tests are allowed to finish |
| 195 /// running so that any modifications they've made to the filesystem can be | 281 /// running so that any modifications they've made to the filesystem can be |
| 196 /// cleaned up. | 282 /// cleaned up. |
| 197 /// | 283 /// |
| 198 /// **Note that closing the engine is not the same as closing [suiteSink].** | 284 /// **Note that closing the engine is not the same as closing [suiteSink].** |
| 199 /// Closing [suiteSink] indicates that no more input will be provided, closing | 285 /// Closing [suiteSink] indicates that no more input will be provided, closing |
| 200 /// the engine indicates that no more output should be emitted. | 286 /// the engine indicates that no more output should be emitted. |
| 201 Future close() { | 287 Future close() { |
| 202 _closed = true; | 288 _closed = true; |
| 203 if (_closedBeforeDone == null) _closedBeforeDone = true; | 289 if (_closedBeforeDone == null) _closedBeforeDone = true; |
| 204 _suiteController.close(); | 290 _suiteController.close(); |
| 205 | 291 |
| 206 return Future.wait(liveTests.map((liveTest) => liveTest.close())); | 292 var allLiveTests = liveTests.toSet()..addAll(_activeLoadTests); |
| 293 return Future.wait(allLiveTests.map((liveTest) => liveTest.close())); |
| 207 } | 294 } |
| 208 } | 295 } |
| OLD | NEW |