| 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 import 'dart:async'; | 5 import 'dart:async'; |
| 6 import 'dart:collection'; | 6 import 'dart:collection'; |
| 7 | 7 |
| 8 import 'package:async/async.dart' hide Result; | 8 import 'package:async/async.dart' hide Result; |
| 9 import 'package:collection/collection.dart'; | 9 import 'package:collection/collection.dart'; |
| 10 import 'package:pool/pool.dart'; | 10 import 'package:pool/pool.dart'; |
| 11 | 11 |
| 12 import '../backend/group.dart'; | 12 import '../backend/group.dart'; |
| 13 import '../backend/group_entry.dart'; | 13 import '../backend/group_entry.dart'; |
| 14 import '../backend/invoker.dart'; | 14 import '../backend/invoker.dart'; |
| 15 import '../backend/live_test.dart'; | 15 import '../backend/live_test.dart'; |
| 16 import '../backend/live_test_controller.dart'; | 16 import '../backend/live_test_controller.dart'; |
| 17 import '../backend/state.dart'; | 17 import '../backend/state.dart'; |
| 18 import '../backend/test.dart'; | 18 import '../backend/test.dart'; |
| 19 import '../util/iterable_set.dart'; |
| 20 import 'live_suite.dart'; |
| 21 import 'live_suite_controller.dart'; |
| 19 import 'load_suite.dart'; | 22 import 'load_suite.dart'; |
| 20 import 'runner_suite.dart'; | 23 import 'runner_suite.dart'; |
| 21 | 24 |
| 22 /// An [Engine] manages a run that encompasses multiple test suites. | 25 /// An [Engine] manages a run that encompasses multiple test suites. |
| 23 /// | 26 /// |
| 24 /// Test suites are provided by passing them into [suiteSink]. Once all suites | 27 /// Test suites are provided by passing them into [suiteSink]. Once all suites |
| 25 /// have been provided, the user should close [suiteSink] to indicate this. | 28 /// 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 | 29 /// [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 | 30 /// order they're provided to [suiteSink]. Tests within those suites will |
| 28 /// likewise be run in the order they're declared. | 31 /// likewise be run in the order they're declared. |
| (...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 95 Sink<RunnerSuite> get suiteSink => new DelegatingSink(_suiteController.sink); | 98 Sink<RunnerSuite> get suiteSink => new DelegatingSink(_suiteController.sink); |
| 96 final _suiteController = new StreamController<RunnerSuite>(); | 99 final _suiteController = new StreamController<RunnerSuite>(); |
| 97 | 100 |
| 98 /// All the [RunnerSuite]s added to [suiteSink] so far. | 101 /// All the [RunnerSuite]s added to [suiteSink] so far. |
| 99 /// | 102 /// |
| 100 /// Note that if a [LoadSuite] is added, this will only contain that suite, | 103 /// Note that if a [LoadSuite] is added, this will only contain that suite, |
| 101 /// not the suite it loads. | 104 /// not the suite it loads. |
| 102 Set<RunnerSuite> get addedSuites => new UnmodifiableSetView(_addedSuites); | 105 Set<RunnerSuite> get addedSuites => new UnmodifiableSetView(_addedSuites); |
| 103 final _addedSuites = new Set<RunnerSuite>(); | 106 final _addedSuites = new Set<RunnerSuite>(); |
| 104 | 107 |
| 105 /// A broadcast that emits each [RunnerSuite] as it's added to the engine via | 108 /// A broadcast stream that emits each [RunnerSuite] as it's added to the |
| 106 /// [suiteSink]. | 109 /// engine via [suiteSink]. |
| 107 /// | 110 /// |
| 108 /// Note that if a [LoadSuite] is added, this will only return that suite, not | 111 /// Note that if a [LoadSuite] is added, this will only return that suite, not |
| 109 /// the suite it loads. | 112 /// the suite it loads. |
| 110 /// | 113 /// |
| 111 /// This is guaranteed to fire after the suite is added to [addedSuites]. | 114 /// This is guaranteed to fire after the suite is added to [addedSuites]. |
| 112 Stream<RunnerSuite> get onSuiteAdded => _onSuiteAddedController.stream; | 115 Stream<RunnerSuite> get onSuiteAdded => _onSuiteAddedController.stream; |
| 113 final _onSuiteAddedController = new StreamController<RunnerSuite>.broadcast(); | 116 final _onSuiteAddedController = new StreamController<RunnerSuite>.broadcast(); |
| 114 | 117 |
| 115 /// All the currently-known tests that have run, are running, or will run. | 118 /// All the currently-known suites that have run or are running. |
| 119 /// |
| 120 /// These are [LiveSuite]s, representing the in-progress state of each suite |
| 121 /// as its component tests are being run. |
| 122 /// |
| 123 /// Note that unlike [addedSuites], for suites that are loaded using |
| 124 /// [LoadSuite]s, both the [LoadSuite] and the suite it loads will eventually |
| 125 /// be in this set. |
| 126 Set<LiveSuite> get liveSuites => new UnmodifiableSetView(_liveSuites); |
| 127 final _liveSuites = new Set<LiveSuite>(); |
| 128 |
| 129 /// A broadcast stream that emits each [LiveSuite] as it's loaded. |
| 130 /// |
| 131 /// Note that unlike [onSuiteAdded], for suites that are loaded using |
| 132 /// [LoadSuite]s, both the [LoadSuite] and the suite it loads will eventually |
| 133 /// be emitted by this stream. |
| 134 Stream<LiveSuite> get onSuiteStarted => _onSuiteStartedController.stream; |
| 135 final _onSuiteStartedController = new StreamController<LiveSuite>.broadcast(); |
| 136 |
| 137 /// All the currently-known tests that have run or are running. |
| 116 /// | 138 /// |
| 117 /// These are [LiveTest]s, representing the in-progress state of each test. | 139 /// These are [LiveTest]s, representing the in-progress state of each test. |
| 118 /// Tests that have not yet begun running are marked [Status.pending]; tests | 140 /// Tests that have not yet begun running are marked [Status.pending]; tests |
| 119 /// that have finished are marked [Status.complete]. | 141 /// that have finished are marked [Status.complete]. |
| 120 /// | 142 /// |
| 121 /// This is guaranteed to contain the same tests as the union of [passed], | 143 /// This is guaranteed to contain the same tests as the union of [passed], |
| 122 /// [skipped], [failed], and [active]. | 144 /// [skipped], [failed], and [active]. |
| 123 /// | 145 /// |
| 124 /// [LiveTest.run] must not be called on these tests. | 146 /// [LiveTest.run] must not be called on these tests. |
| 125 List<LiveTest> get liveTests => new UnmodifiableListView(_liveTests); | 147 Set<LiveTest> get liveTests => new GroupSet.from( |
| 126 final _liveTests = new List<LiveTest>(); | 148 [passed, skipped, failed, new IterableSet(active)], |
| 149 disjoint: true); |
| 127 | 150 |
| 128 /// A stream that emits each [LiveTest] as it's about to start running. | 151 /// A stream that emits each [LiveTest] as it's about to start running. |
| 129 /// | 152 /// |
| 130 /// This is guaranteed to fire before [LiveTest.onStateChange] first fires. | 153 /// This is guaranteed to fire before [LiveTest.onStateChange] first fires. |
| 131 Stream<LiveTest> get onTestStarted => _onTestStartedController.stream; | 154 Stream<LiveTest> get onTestStarted => _onTestStartedGroup.stream; |
| 132 final _onTestStartedController = new StreamController<LiveTest>.broadcast(); | 155 final _onTestStartedGroup = new StreamGroup<LiveTest>.broadcast(); |
| 133 | 156 |
| 134 /// The set of tests that have completed and been marked as passing. | 157 /// The set of tests that have completed and been marked as passing. |
| 135 Set<LiveTest> get passed => new UnmodifiableSetView(_passed); | 158 Set<LiveTest> get passed => _passedGroup.set; |
| 136 final _passed = new Set<LiveTest>(); | 159 final _passedGroup = new SetGroup<LiveTest>(disjoint: true); |
| 137 | 160 |
| 138 /// The set of tests that have completed and been marked as skipped. | 161 /// The set of tests that have completed and been marked as skipped. |
| 139 Set<LiveTest> get skipped => new UnmodifiableSetView(_skipped); | 162 Set<LiveTest> get skipped => _skippedGroup.set; |
| 140 final _skipped = new Set<LiveTest>(); | 163 final _skippedGroup = new SetGroup<LiveTest>(disjoint: true); |
| 141 | 164 |
| 142 /// The set of tests that have completed and been marked as failing or error. | 165 /// The set of tests that have completed and been marked as failing or error. |
| 143 Set<LiveTest> get failed => new UnmodifiableSetView(_failed); | 166 Set<LiveTest> get failed => _failedGroup.set; |
| 144 final _failed = new Set<LiveTest>(); | 167 final _failedGroup = new SetGroup<LiveTest>(disjoint: true); |
| 145 | 168 |
| 146 /// The tests that are still running, in the order they begain running. | 169 /// The tests that are still running, in the order they begain running. |
| 147 List<LiveTest> get active => new UnmodifiableListView(_active); | 170 List<LiveTest> get active => new UnmodifiableListView(_active); |
| 148 final _active = new QueueList<LiveTest>(); | 171 final _active = new QueueList<LiveTest>(); |
| 149 | 172 |
| 150 /// The set of tests that have been marked for restarting. | 173 /// The set of tests that have been marked for restarting. |
| 151 /// | 174 /// |
| 152 /// This is always a subset of [active]. Once a test in here has finished | 175 /// This is always a subset of [active]. Once a test in here has finished |
| 153 /// running, it's run again. | 176 /// running, it's run again. |
| 154 final _restarted = new Set<LiveTest>(); | 177 final _restarted = new Set<LiveTest>(); |
| (...skipping 15 matching lines...) Expand all Loading... |
| 170 /// | 193 /// |
| 171 /// [concurrency] controls how many suites are run at once, and defaults to 1. | 194 /// [concurrency] controls how many suites are run at once, and defaults to 1. |
| 172 /// [maxSuites] controls how many suites are *loaded* at once, and defaults to | 195 /// [maxSuites] controls how many suites are *loaded* at once, and defaults to |
| 173 /// four times [concurrency]. | 196 /// four times [concurrency]. |
| 174 Engine({int concurrency, int maxSuites}) | 197 Engine({int concurrency, int maxSuites}) |
| 175 : _runPool = new Pool(concurrency == null ? 1 : concurrency), | 198 : _runPool = new Pool(concurrency == null ? 1 : concurrency), |
| 176 _loadPool = new Pool(maxSuites == null | 199 _loadPool = new Pool(maxSuites == null |
| 177 ? (concurrency == null ? 2 : concurrency * 2) | 200 ? (concurrency == null ? 2 : concurrency * 2) |
| 178 : maxSuites) { | 201 : maxSuites) { |
| 179 _group.future.then((_) { | 202 _group.future.then((_) { |
| 203 _onTestStartedGroup.close(); |
| 204 _onSuiteStartedController.close(); |
| 180 if (_closedBeforeDone == null) _closedBeforeDone = false; | 205 if (_closedBeforeDone == null) _closedBeforeDone = false; |
| 181 }).catchError((_) { | 206 }).catchError((_) { |
| 182 // Don't top-level errors. They'll be thrown via [success] anyway. | 207 // Don't top-level errors. They'll be thrown via [success] anyway. |
| 183 }); | 208 }); |
| 184 } | 209 } |
| 185 | 210 |
| 186 /// Creates an [Engine] that will run all tests in [suites]. | 211 /// Creates an [Engine] that will run all tests in [suites]. |
| 187 /// | 212 /// |
| 188 /// [concurrency] controls how many suites are run at once. An engine | 213 /// [concurrency] controls how many suites are run at once. An engine |
| 189 /// constructed this way will automatically close its [suiteSink], meaning | 214 /// constructed this way will automatically close its [suiteSink], meaning |
| (...skipping 16 matching lines...) Expand all Loading... |
| 206 } | 231 } |
| 207 _runCalled = true; | 232 _runCalled = true; |
| 208 | 233 |
| 209 _suiteController.stream.listen((suite) { | 234 _suiteController.stream.listen((suite) { |
| 210 _addedSuites.add(suite); | 235 _addedSuites.add(suite); |
| 211 _onSuiteAddedController.add(suite); | 236 _onSuiteAddedController.add(suite); |
| 212 | 237 |
| 213 _group.add(new Future.sync(() async { | 238 _group.add(new Future.sync(() async { |
| 214 var loadResource = await _loadPool.request(); | 239 var loadResource = await _loadPool.request(); |
| 215 | 240 |
| 241 var controller; |
| 216 if (suite is LoadSuite) { | 242 if (suite is LoadSuite) { |
| 217 suite = await _addLoadSuite(suite); | 243 controller = await _addLoadSuite(suite); |
| 218 if (suite == null) { | 244 if (controller == null) { |
| 219 loadResource.release(); | 245 loadResource.release(); |
| 220 return; | 246 return; |
| 221 } | 247 } |
| 248 } else { |
| 249 controller = new LiveSuiteController(suite); |
| 222 } | 250 } |
| 223 | 251 |
| 252 _addLiveSuite(controller.liveSuite); |
| 253 |
| 224 await _runPool.withResource(() async { | 254 await _runPool.withResource(() async { |
| 225 if (_closed) return; | 255 if (_closed) return; |
| 226 await _runGroup(suite, suite.group, []); | 256 await _runGroup(controller, controller.liveSuite.suite.group, []); |
| 227 loadResource.allowRelease(() => suite.close()); | 257 loadResource.allowRelease(() => controller.close()); |
| 228 }); | 258 }); |
| 229 })); | 259 })); |
| 230 }, onDone: () { | 260 }, onDone: () { |
| 231 _onSuiteAddedController.close(); | 261 _onSuiteAddedController.close(); |
| 232 _group.close(); | 262 _group.close(); |
| 233 }); | 263 }); |
| 234 | 264 |
| 235 return success; | 265 return success; |
| 236 } | 266 } |
| 237 | 267 |
| 238 /// Runs all the entries in [entries] in sequence. | 268 /// Runs all the entries in [group] in sequence. |
| 239 /// | 269 /// |
| 270 /// [suiteController] is the controller fo the suite that contains [group]. |
| 240 /// [parents] is a list of groups that contain [group]. It may be modified, | 271 /// [parents] is a list of groups that contain [group]. It may be modified, |
| 241 /// but it's guaranteed to be in its original state once this function has | 272 /// but it's guaranteed to be in its original state once this function has |
| 242 /// finished. | 273 /// finished. |
| 243 Future _runGroup(RunnerSuite suite, Group group, List<Group> parents) async { | 274 Future _runGroup(LiveSuiteController suiteController, Group group, |
| 275 List<Group> parents) async { |
| 244 parents.add(group); | 276 parents.add(group); |
| 245 try { | 277 try { |
| 246 if (group.metadata.skip) { | 278 if (group.metadata.skip) { |
| 247 await _runLiveTest(_skippedTest(suite, group, parents)); | 279 await _runSkippedTest(suiteController, group, parents); |
| 248 return; | 280 return; |
| 249 } | 281 } |
| 250 | 282 |
| 251 var setUpAllSucceeded = true; | 283 var setUpAllSucceeded = true; |
| 252 if (group.setUpAll != null) { | 284 if (group.setUpAll != null) { |
| 253 var liveTest = group.setUpAll.load(suite, groups: parents); | 285 var liveTest = group.setUpAll.load(suiteController.liveSuite.suite, |
| 254 await _runLiveTest(liveTest, countSuccess: false); | 286 groups: parents); |
| 287 await _runLiveTest(suiteController, liveTest, countSuccess: false); |
| 255 setUpAllSucceeded = liveTest.state.result == Result.success; | 288 setUpAllSucceeded = liveTest.state.result == Result.success; |
| 256 } | 289 } |
| 257 | 290 |
| 258 if (!_closed && setUpAllSucceeded) { | 291 if (!_closed && setUpAllSucceeded) { |
| 259 for (var entry in group.entries) { | 292 for (var entry in group.entries) { |
| 260 if (_closed) return; | 293 if (_closed) return; |
| 261 | 294 |
| 262 if (entry is Group) { | 295 if (entry is Group) { |
| 263 await _runGroup(suite, entry, parents); | 296 await _runGroup(suiteController, entry, parents); |
| 264 } else if (entry.metadata.skip) { | 297 } else if (entry.metadata.skip) { |
| 265 await _runLiveTest(_skippedTest(suite, entry, parents)); | 298 await _runSkippedTest(suiteController, entry, parents); |
| 266 } else { | 299 } else { |
| 267 var test = entry as Test; | 300 var test = entry as Test; |
| 268 await _runLiveTest(test.load(suite, groups: parents)); | 301 await _runLiveTest( |
| 302 suiteController, |
| 303 test.load(suiteController.liveSuite.suite, groups: parents)); |
| 269 } | 304 } |
| 270 } | 305 } |
| 271 } | 306 } |
| 272 | 307 |
| 273 // Even if we're closed or setUpAll failed, we want to run all the | 308 // Even if we're closed or setUpAll failed, we want to run all the |
| 274 // teardowns to ensure that any state is properly cleaned up. | 309 // teardowns to ensure that any state is properly cleaned up. |
| 275 if (group.tearDownAll != null) { | 310 if (group.tearDownAll != null) { |
| 276 var liveTest = group.tearDownAll.load(suite, groups: parents); | 311 var liveTest = group.tearDownAll.load(suiteController.liveSuite.suite, |
| 277 await _runLiveTest(liveTest, countSuccess: false); | 312 groups: parents); |
| 313 await _runLiveTest(suiteController, liveTest, countSuccess: false); |
| 278 if (_closed) await liveTest.close(); | 314 if (_closed) await liveTest.close(); |
| 279 } | 315 } |
| 280 } finally { | 316 } finally { |
| 281 parents.remove(group); | 317 parents.remove(group); |
| 282 } | 318 } |
| 283 } | 319 } |
| 284 | 320 |
| 285 /// Returns a dummy [LiveTest] for a test or group marked as "skip". | 321 /// Runs [liveTest] using [suiteController]. |
| 286 /// | |
| 287 /// [parents] is a list of groups that contain [entry]. | |
| 288 LiveTest _skippedTest(RunnerSuite suite, GroupEntry entry, | |
| 289 List<Group> parents) { | |
| 290 // The netry name will be `null` for the root group. | |
| 291 var test = new LocalTest(entry.name ?? "(suite)", entry.metadata, () {}); | |
| 292 | |
| 293 var controller; | |
| 294 controller = new LiveTestController(suite, test, () { | |
| 295 controller.setState(const State(Status.running, Result.success)); | |
| 296 controller.setState(const State(Status.complete, Result.success)); | |
| 297 controller.completer.complete(); | |
| 298 }, () {}, groups: parents); | |
| 299 return controller.liveTest; | |
| 300 } | |
| 301 | |
| 302 /// Runs [liveTest]. | |
| 303 /// | 322 /// |
| 304 /// If [countSuccess] is `true` (the default), the test is put into [passed] | 323 /// If [countSuccess] is `true` (the default), the test is put into [passed] |
| 305 /// if it succeeds. Otherwise, it's removed from [liveTests] entirely. | 324 /// if it succeeds. Otherwise, it's removed from [liveTests] entirely. |
| 306 Future _runLiveTest(LiveTest liveTest, {bool countSuccess: true}) async { | 325 Future _runLiveTest(LiveSuiteController suiteController, LiveTest liveTest, |
| 307 _liveTests.add(liveTest); | 326 {bool countSuccess: true}) async { |
| 308 _active.add(liveTest); | 327 _active.add(liveTest); |
| 309 | 328 |
| 310 // If there were no active non-load tests, the current active test would | 329 // If there were no active non-load tests, the current active test would |
| 311 // have been a load test. In that case, remove it, since now we have a | 330 // have been a load test. In that case, remove it, since now we have a |
| 312 // non-load test to add. | 331 // non-load test to add. |
| 313 if (_active.isNotEmpty && _active.first.suite is LoadSuite) { | 332 if (_active.first.suite is LoadSuite) _active.removeFirst(); |
| 314 _liveTests.remove(_active.removeFirst()); | |
| 315 } | |
| 316 | 333 |
| 317 liveTest.onStateChange.listen((state) { | 334 liveTest.onStateChange.listen((state) { |
| 318 if (state.status != Status.complete) return; | 335 if (state.status != Status.complete) return; |
| 319 _active.remove(liveTest); | 336 _active.remove(liveTest); |
| 320 | 337 |
| 321 // If we're out of non-load tests, surface a load test. | 338 // If we're out of non-load tests, surface a load test. |
| 322 if (_active.isEmpty && _activeLoadTests.isNotEmpty) { | 339 if (_active.isEmpty && _activeLoadTests.isNotEmpty) { |
| 323 _active.add(_activeLoadTests.first); | 340 _active.add(_activeLoadTests.first); |
| 324 _liveTests.add(_activeLoadTests.first); | |
| 325 } | |
| 326 | |
| 327 if (state.result != Result.success) { | |
| 328 _passed.remove(liveTest); | |
| 329 _failed.add(liveTest); | |
| 330 } else if (liveTest.test.metadata.skip) { | |
| 331 _skipped.add(liveTest); | |
| 332 } else if (countSuccess) { | |
| 333 _passed.add(liveTest); | |
| 334 } else { | |
| 335 _liveTests.remove(liveTest); | |
| 336 } | 341 } |
| 337 }); | 342 }); |
| 338 | 343 |
| 339 _onTestStartedController.add(liveTest); | 344 suiteController.reportLiveTest(liveTest, countSuccess: countSuccess); |
| 340 | 345 |
| 341 // First, schedule a microtask to ensure that [onTestStarted] fires before | 346 // Schedule a microtask to ensure that [onTestStarted] fires before the |
| 342 // the first [LiveTest.onStateChange] event. Once the test finishes, use | 347 // first [LiveTest.onStateChange] event. |
| 343 // [new Future] to do a coarse-grained event loop pump to avoid starving | |
| 344 // non-microtask events. | |
| 345 await new Future.microtask(liveTest.run); | 348 await new Future.microtask(liveTest.run); |
| 349 |
| 350 // Once the test finishes, use [new Future] to do a coarse-grained event |
| 351 // loop pump to avoid starving non-microtask events. |
| 346 await new Future(() {}); | 352 await new Future(() {}); |
| 347 | 353 |
| 348 if (!_restarted.contains(liveTest)) return; | 354 if (!_restarted.contains(liveTest)) return; |
| 349 await _runLiveTest(liveTest.copy(), countSuccess: countSuccess); | 355 await _runLiveTest(suiteController, liveTest.copy(), |
| 356 countSuccess: countSuccess); |
| 350 _restarted.remove(liveTest); | 357 _restarted.remove(liveTest); |
| 351 } | 358 } |
| 352 | 359 |
| 360 /// Runs a dummy [LiveTest] for a test or group marked as "skip". |
| 361 /// |
| 362 /// [suiteController] is the controller for the suite that contains [entry]. |
| 363 /// [parents] is a list of groups that contain [entry]. |
| 364 Future _runSkippedTest(LiveSuiteController suiteController, GroupEntry entry, |
| 365 List<Group> parents) { |
| 366 // The netry name will be `null` for the root group. |
| 367 var test = new LocalTest(entry.name ?? "(suite)", entry.metadata, () {}); |
| 368 |
| 369 var controller; |
| 370 controller = new LiveTestController( |
| 371 suiteController.liveSuite.suite, test, () { |
| 372 controller.setState(const State(Status.running, Result.success)); |
| 373 controller.setState(const State(Status.complete, Result.success)); |
| 374 controller.completer.complete(); |
| 375 }, () {}, groups: parents); |
| 376 |
| 377 return _runLiveTest(suiteController, controller.liveTest); |
| 378 } |
| 379 |
| 353 /// Closes [liveTest] and tells the engine to re-run it once it's done | 380 /// Closes [liveTest] and tells the engine to re-run it once it's done |
| 354 /// running. | 381 /// running. |
| 355 /// | 382 /// |
| 356 /// Returns the same future as [LiveTest.close]. | 383 /// Returns the same future as [LiveTest.close]. |
| 357 Future restartTest(LiveTest liveTest) async { | 384 Future restartTest(LiveTest liveTest) async { |
| 358 if (_activeLoadTests.contains(liveTest)) { | 385 if (_activeLoadTests.contains(liveTest)) { |
| 359 throw new ArgumentError("Can't restart a load test."); | 386 throw new ArgumentError("Can't restart a load test."); |
| 360 } | 387 } |
| 361 | 388 |
| 362 if (!_active.contains(liveTest)) { | 389 if (!_active.contains(liveTest)) { |
| 363 throw new StateError("Can't restart inactive test " | 390 throw new StateError("Can't restart inactive test " |
| 364 "\"${liveTest.test.name}\"."); | 391 "\"${liveTest.test.name}\"."); |
| 365 } | 392 } |
| 366 | 393 |
| 367 _restarted.add(liveTest); | 394 _restarted.add(liveTest); |
| 368 _active.remove(liveTest); | 395 _active.remove(liveTest); |
| 369 await liveTest.close(); | 396 await liveTest.close(); |
| 370 } | 397 } |
| 371 | 398 |
| 372 /// Adds listeners for [suite]. | 399 /// Runs [suite] and returns the [LiveSuiteController] for the suite it loads. |
| 373 /// | 400 /// |
| 374 /// Load suites have specific logic apart from normal test suites. | 401 /// Returns `null` if the suite fails to load. |
| 375 Future<RunnerSuite> _addLoadSuite(LoadSuite suite) async { | 402 Future<LiveSuiteController> _addLoadSuite(LoadSuite suite) async { |
| 403 var controller = new LiveSuiteController(suite); |
| 404 _addLiveSuite(controller.liveSuite); |
| 405 |
| 376 var liveTest = await suite.test.load(suite); | 406 var liveTest = await suite.test.load(suite); |
| 377 | |
| 378 _activeLoadTests.add(liveTest); | 407 _activeLoadTests.add(liveTest); |
| 379 | 408 |
| 380 // Only surface the load test if there are no other tests currently running. | 409 // Only surface the load test if there are no other tests currently running. |
| 381 if (_active.isEmpty) { | 410 if (_active.isEmpty) _active.add(liveTest); |
| 382 _liveTests.add(liveTest); | |
| 383 _active.add(liveTest); | |
| 384 } | |
| 385 | 411 |
| 386 liveTest.onStateChange.listen((state) { | 412 liveTest.onStateChange.listen((state) { |
| 387 if (state.status != Status.complete) return; | 413 if (state.status != Status.complete) return; |
| 388 _activeLoadTests.remove(liveTest); | 414 _activeLoadTests.remove(liveTest); |
| 389 | 415 |
| 390 // Only one load test will be active at any given time, and it will always | 416 // Only one load test will be active at any given time, and it will always |
| 391 // be the only active test. Remove it and, if possible, surface another | 417 // be the only active test. Remove it and, if possible, surface another |
| 392 // load test. | 418 // load test. |
| 393 if (_active.isNotEmpty && _active.first.suite == suite) { | 419 if (_active.isNotEmpty && _active.first.suite == suite) { |
| 394 _active.remove(liveTest); | 420 _active.remove(liveTest); |
| 395 _liveTests.remove(liveTest); | 421 if (_activeLoadTests.isNotEmpty) _active.add(_activeLoadTests.last); |
| 396 | |
| 397 if (_activeLoadTests.isNotEmpty) { | |
| 398 _active.add(_activeLoadTests.last); | |
| 399 _liveTests.add(_activeLoadTests.last); | |
| 400 } | |
| 401 } | 422 } |
| 402 | |
| 403 // Surface the load test if it fails so that the user can see the failure. | |
| 404 if (state.result == Result.success) return; | |
| 405 _failed.add(liveTest); | |
| 406 _liveTests.add(liveTest); | |
| 407 }); | 423 }); |
| 408 | 424 |
| 409 // Run the test immediately. We don't want loading to be blocked on suites | 425 controller.reportLiveTest(liveTest, countSuccess: false); |
| 410 // that are already running. | 426 controller.noMoreLiveTests(); |
| 411 _onTestStartedController.add(liveTest); | |
| 412 await liveTest.run(); | |
| 413 | 427 |
| 414 return suite.suite; | 428 // Schedule a microtask to ensure that [onTestStarted] fires before the |
| 429 // first [LiveTest.onStateChange] event. |
| 430 new Future.microtask(liveTest.run); |
| 431 |
| 432 var innerSuite = await suite.suite; |
| 433 if (innerSuite == null) return null; |
| 434 |
| 435 var innerController = new LiveSuiteController(innerSuite); |
| 436 innerController.liveSuite.onClose.then((_) { |
| 437 // When the main suite is closed, close the load suite and its test as |
| 438 // well. This doesn't release any resources, but it does close streams |
| 439 // which indicates that the load test won't experience an error in the |
| 440 // future. |
| 441 liveTest.close(); |
| 442 controller.close(); |
| 443 }); |
| 444 |
| 445 return innerController; |
| 446 } |
| 447 |
| 448 /// Add [liveSuite] and the information it exposes to the engine's |
| 449 /// informational streams and collections. |
| 450 void _addLiveSuite(LiveSuite liveSuite) { |
| 451 _liveSuites.add(liveSuite); |
| 452 _onSuiteStartedController.add(liveSuite); |
| 453 |
| 454 _onTestStartedGroup.add(liveSuite.onTestStarted); |
| 455 _passedGroup.add(liveSuite.passed); |
| 456 _skippedGroup.add(liveSuite.skipped); |
| 457 _failedGroup.add(liveSuite.failed); |
| 415 } | 458 } |
| 416 | 459 |
| 417 /// Signals that the caller is done paying attention to test results and the | 460 /// Signals that the caller is done paying attention to test results and the |
| 418 /// engine should release any resources it has allocated. | 461 /// engine should release any resources it has allocated. |
| 419 /// | 462 /// |
| 420 /// Any actively-running tests are also closed. VM tests are allowed to finish | 463 /// Any actively-running tests are also closed. VM tests are allowed to finish |
| 421 /// running so that any modifications they've made to the filesystem can be | 464 /// running so that any modifications they've made to the filesystem can be |
| 422 /// cleaned up. | 465 /// cleaned up. |
| 423 /// | 466 /// |
| 424 /// **Note that closing the engine is not the same as closing [suiteSink].** | 467 /// **Note that closing the engine is not the same as closing [suiteSink].** |
| (...skipping 11 matching lines...) Expand all Loading... |
| 436 var futures = allLiveTests.map((liveTest) => liveTest.close()).toList(); | 479 var futures = allLiveTests.map((liveTest) => liveTest.close()).toList(); |
| 437 | 480 |
| 438 // Closing the load pool will close the test suites as soon as their tests | 481 // Closing the load pool will close the test suites as soon as their tests |
| 439 // are done. For browser suites this is effectively immediate since their | 482 // are done. For browser suites this is effectively immediate since their |
| 440 // tests shut down as soon as they're closed, but for VM suites we may need | 483 // tests shut down as soon as they're closed, but for VM suites we may need |
| 441 // to wait for tearDowns or tearDownAlls to run. | 484 // to wait for tearDowns or tearDownAlls to run. |
| 442 futures.add(_loadPool.close()); | 485 futures.add(_loadPool.close()); |
| 443 await Future.wait(futures, eagerError: true); | 486 await Future.wait(futures, eagerError: true); |
| 444 } | 487 } |
| 445 } | 488 } |
| OLD | NEW |