| 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; |
| (...skipping 24 matching lines...) Expand all Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 } |
| OLD | NEW |