| 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'; | |
| 14 import '../backend/invoker.dart'; | 13 import '../backend/invoker.dart'; |
| 15 import '../backend/live_test.dart'; | 14 import '../backend/live_test.dart'; |
| 16 import '../backend/live_test_controller.dart'; | 15 import '../backend/live_test_controller.dart'; |
| 17 import '../backend/message.dart'; | 16 import '../backend/message.dart'; |
| 18 import '../backend/state.dart'; | 17 import '../backend/state.dart'; |
| 19 import '../backend/test.dart'; | 18 import '../backend/test.dart'; |
| 20 import '../util/iterable_set.dart'; | 19 import '../util/iterable_set.dart'; |
| 21 import 'live_suite.dart'; | 20 import 'live_suite.dart'; |
| 22 import 'live_suite_controller.dart'; | 21 import 'live_suite_controller.dart'; |
| 23 import 'load_suite.dart'; | 22 import 'load_suite.dart'; |
| (...skipping 21 matching lines...) Expand all Loading... |
| 45 /// and [liveTests]. If this test passes, it will be removed from both [active] | 44 /// and [liveTests]. If this test passes, it will be removed from both [active] |
| 46 /// and [liveTests] and *will not* be added to [passed]. If at any point a load | 45 /// and [liveTests] and *will not* be added to [passed]. If at any point a load |
| 47 /// test fails, it will be added to [failed] and [liveTests]. | 46 /// test fails, it will be added to [failed] and [liveTests]. |
| 48 /// | 47 /// |
| 49 /// The test suite loaded by a load suite will be automatically be run by the | 48 /// The test suite loaded by a load suite will be automatically be run by the |
| 50 /// engine; it doesn't need to be added to [suiteSink] manually. | 49 /// engine; it doesn't need to be added to [suiteSink] manually. |
| 51 /// | 50 /// |
| 52 /// Load tests will always be emitted through [onTestStarted] so users can watch | 51 /// Load tests will always be emitted through [onTestStarted] so users can watch |
| 53 /// their event streams once they start running. | 52 /// their event streams once they start running. |
| 54 class Engine { | 53 class Engine { |
| 54 /// Whether to run skipped tests. |
| 55 final bool _runSkipped; |
| 56 |
| 55 /// Whether [run] has been called yet. | 57 /// Whether [run] has been called yet. |
| 56 var _runCalled = false; | 58 var _runCalled = false; |
| 57 | 59 |
| 58 /// Whether [close] has been called. | 60 /// Whether [close] has been called. |
| 59 var _closed = false; | 61 var _closed = false; |
| 60 | 62 |
| 61 /// Whether [close] was called before all the tests finished running. | 63 /// Whether [close] was called before all the tests finished running. |
| 62 /// | 64 /// |
| 63 /// This is `null` if close hasn't been called and the tests are still | 65 /// This is `null` if close hasn't been called and the tests are still |
| 64 /// running, `true` if close was called before the tests finished running, and | 66 /// running, `true` if close was called before the tests finished running, and |
| (...skipping 117 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 182 /// This is separate from [active] because load tests aren't always surfaced. | 184 /// This is separate from [active] because load tests aren't always surfaced. |
| 183 final _activeLoadTests = new List<LiveTest>(); | 185 final _activeLoadTests = new List<LiveTest>(); |
| 184 | 186 |
| 185 /// Whether this engine is idle—that is, not currently executing a test. | 187 /// Whether this engine is idle—that is, not currently executing a test. |
| 186 bool get isIdle => _group.isIdle; | 188 bool get isIdle => _group.isIdle; |
| 187 | 189 |
| 188 /// A broadcast stream that fires an event whenever [isIdle] switches from | 190 /// A broadcast stream that fires an event whenever [isIdle] switches from |
| 189 /// `false` to `true`. | 191 /// `false` to `true`. |
| 190 Stream get onIdle => _group.onIdle; | 192 Stream get onIdle => _group.onIdle; |
| 191 | 193 |
| 194 // TODO(nweiz): Use interface libraries to take a Configuration even when |
| 195 // dart:io is unavailable. |
| 192 /// Creates an [Engine] that will run all tests provided via [suiteSink]. | 196 /// Creates an [Engine] that will run all tests provided via [suiteSink]. |
| 193 /// | 197 /// |
| 194 /// [concurrency] controls how many suites are run at once, and defaults to 1. | 198 /// [concurrency] controls how many suites are run at once, and defaults to 1. |
| 195 /// [maxSuites] controls how many suites are *loaded* at once, and defaults to | 199 /// [maxSuites] controls how many suites are *loaded* at once, and defaults to |
| 196 /// four times [concurrency]. | 200 /// four times [concurrency]. If [runSkipped] is `true`, skipped tests will be |
| 197 Engine({int concurrency, int maxSuites}) | 201 /// run as though they weren't skipped. |
| 198 : _runPool = new Pool(concurrency == null ? 1 : concurrency), | 202 Engine({int concurrency, int maxSuites, bool runSkipped: false}) |
| 199 _loadPool = new Pool(maxSuites == null | 203 : _runPool = new Pool(concurrency ?? 1), |
| 200 ? (concurrency == null ? 2 : concurrency * 2) | 204 _loadPool = new Pool(maxSuites ?? (concurrency ?? 1) * 2), |
| 201 : maxSuites) { | 205 _runSkipped = runSkipped { |
| 202 _group.future.then((_) { | 206 _group.future.then((_) { |
| 203 _onTestStartedGroup.close(); | 207 _onTestStartedGroup.close(); |
| 204 _onSuiteStartedController.close(); | 208 _onSuiteStartedController.close(); |
| 205 if (_closedBeforeDone == null) _closedBeforeDone = false; | 209 if (_closedBeforeDone == null) _closedBeforeDone = false; |
| 206 }).catchError((_) { | 210 }).catchError((_) { |
| 207 // Don't top-level errors. They'll be thrown via [success] anyway. | 211 // Don't top-level errors. They'll be thrown via [success] anyway. |
| 208 }); | 212 }); |
| 209 } | 213 } |
| 210 | 214 |
| 211 /// Creates an [Engine] that will run all tests in [suites]. | 215 /// Creates an [Engine] that will run all tests in [suites]. |
| 212 /// | 216 /// |
| 213 /// [concurrency] controls how many suites are run at once. An engine | 217 /// An engine constructed this way will automatically close its [suiteSink], |
| 214 /// constructed this way will automatically close its [suiteSink], meaning | 218 /// meaning that no further suites may be provided. |
| 215 /// that no further suites may be provided. | 219 /// |
| 216 factory Engine.withSuites(List<RunnerSuite> suites, {int concurrency}) { | 220 /// [concurrency] controls how many suites are run at once. If [runSkipped] is |
| 217 var engine = new Engine(concurrency: concurrency); | 221 /// `true`, skipped tests will be run as though they weren't skipped. |
| 222 factory Engine.withSuites(List<RunnerSuite> suites, {int concurrency, |
| 223 bool runSkipped: false}) { |
| 224 var engine = new Engine(concurrency: concurrency, runSkipped: runSkipped); |
| 218 for (var suite in suites) engine.suiteSink.add(suite); | 225 for (var suite in suites) engine.suiteSink.add(suite); |
| 219 engine.suiteSink.close(); | 226 engine.suiteSink.close(); |
| 220 return engine; | 227 return engine; |
| 221 } | 228 } |
| 222 | 229 |
| 223 /// Runs all tests in all suites defined by this engine. | 230 /// Runs all tests in all suites defined by this engine. |
| 224 /// | 231 /// |
| 225 /// This returns `true` if all tests succeed, and `false` otherwise. It will | 232 /// This returns `true` if all tests succeed, and `false` otherwise. It will |
| 226 /// only return once all tests have finished running and [suiteSink] has been | 233 /// only return once all tests have finished running and [suiteSink] has been |
| 227 /// closed. | 234 /// closed. |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 269 /// Runs all the entries in [group] in sequence. | 276 /// Runs all the entries in [group] in sequence. |
| 270 /// | 277 /// |
| 271 /// [suiteController] is the controller fo the suite that contains [group]. | 278 /// [suiteController] is the controller fo the suite that contains [group]. |
| 272 /// [parents] is a list of groups that contain [group]. It may be modified, | 279 /// [parents] is a list of groups that contain [group]. It may be modified, |
| 273 /// but it's guaranteed to be in its original state once this function has | 280 /// but it's guaranteed to be in its original state once this function has |
| 274 /// finished. | 281 /// finished. |
| 275 Future _runGroup(LiveSuiteController suiteController, Group group, | 282 Future _runGroup(LiveSuiteController suiteController, Group group, |
| 276 List<Group> parents) async { | 283 List<Group> parents) async { |
| 277 parents.add(group); | 284 parents.add(group); |
| 278 try { | 285 try { |
| 279 if (group.metadata.skip) { | 286 var skipGroup = !_runSkipped && group.metadata.skip; |
| 280 await _runSkippedTest(suiteController, group, parents); | |
| 281 return; | |
| 282 } | |
| 283 | |
| 284 var setUpAllSucceeded = true; | 287 var setUpAllSucceeded = true; |
| 285 if (group.setUpAll != null) { | 288 if (!skipGroup && group.setUpAll != null) { |
| 286 var liveTest = group.setUpAll.load(suiteController.liveSuite.suite, | 289 var liveTest = group.setUpAll.load(suiteController.liveSuite.suite, |
| 287 groups: parents); | 290 groups: parents); |
| 288 await _runLiveTest(suiteController, liveTest, countSuccess: false); | 291 await _runLiveTest(suiteController, liveTest, countSuccess: false); |
| 289 setUpAllSucceeded = liveTest.state.result.isPassing; | 292 setUpAllSucceeded = liveTest.state.result.isPassing; |
| 290 } | 293 } |
| 291 | 294 |
| 292 if (!_closed && setUpAllSucceeded) { | 295 if (!_closed && setUpAllSucceeded) { |
| 293 for (var entry in group.entries) { | 296 for (var entry in group.entries) { |
| 294 if (_closed) return; | 297 if (_closed) return; |
| 295 | 298 |
| 296 if (entry is Group) { | 299 if (entry is Group) { |
| 297 await _runGroup(suiteController, entry, parents); | 300 await _runGroup(suiteController, entry, parents); |
| 298 } else if (entry.metadata.skip) { | 301 } else if (!_runSkipped && entry.metadata.skip) { |
| 299 await _runSkippedTest(suiteController, entry, parents); | 302 await _runSkippedTest(suiteController, entry, parents); |
| 300 } else { | 303 } else { |
| 301 var test = entry as Test; | 304 var test = entry as Test; |
| 302 await _runLiveTest( | 305 await _runLiveTest( |
| 303 suiteController, | 306 suiteController, |
| 304 test.load(suiteController.liveSuite.suite, groups: parents)); | 307 test.load(suiteController.liveSuite.suite, groups: parents)); |
| 305 } | 308 } |
| 306 } | 309 } |
| 307 } | 310 } |
| 308 | 311 |
| 309 // Even if we're closed or setUpAll failed, we want to run all the | 312 // Even if we're closed or setUpAll failed, we want to run all the |
| 310 // teardowns to ensure that any state is properly cleaned up. | 313 // teardowns to ensure that any state is properly cleaned up. |
| 311 if (group.tearDownAll != null) { | 314 if (!skipGroup && group.tearDownAll != null) { |
| 312 var liveTest = group.tearDownAll.load(suiteController.liveSuite.suite, | 315 var liveTest = group.tearDownAll.load(suiteController.liveSuite.suite, |
| 313 groups: parents); | 316 groups: parents); |
| 314 await _runLiveTest(suiteController, liveTest, countSuccess: false); | 317 await _runLiveTest(suiteController, liveTest, countSuccess: false); |
| 315 if (_closed) await liveTest.close(); | 318 if (_closed) await liveTest.close(); |
| 316 } | 319 } |
| 317 } finally { | 320 } finally { |
| 318 parents.remove(group); | 321 parents.remove(group); |
| 319 } | 322 } |
| 320 } | 323 } |
| 321 | 324 |
| (...skipping 29 matching lines...) Expand all Loading... |
| 351 // Once the test finishes, use [new Future] to do a coarse-grained event | 354 // Once the test finishes, use [new Future] to do a coarse-grained event |
| 352 // loop pump to avoid starving non-microtask events. | 355 // loop pump to avoid starving non-microtask events. |
| 353 await new Future(() {}); | 356 await new Future(() {}); |
| 354 | 357 |
| 355 if (!_restarted.contains(liveTest)) return; | 358 if (!_restarted.contains(liveTest)) return; |
| 356 await _runLiveTest(suiteController, liveTest.copy(), | 359 await _runLiveTest(suiteController, liveTest.copy(), |
| 357 countSuccess: countSuccess); | 360 countSuccess: countSuccess); |
| 358 _restarted.remove(liveTest); | 361 _restarted.remove(liveTest); |
| 359 } | 362 } |
| 360 | 363 |
| 361 /// Runs a dummy [LiveTest] for a test or group marked as "skip". | 364 /// Runs a dummy [LiveTest] for a test marked as "skip". |
| 362 /// | 365 /// |
| 363 /// [suiteController] is the controller for the suite that contains [entry]. | 366 /// [suiteController] is the controller for the suite that contains [test]. |
| 364 /// [parents] is a list of groups that contain [entry]. | 367 /// [parents] is a list of groups that contain [test]. |
| 365 Future _runSkippedTest(LiveSuiteController suiteController, GroupEntry entry, | 368 Future _runSkippedTest(LiveSuiteController suiteController, Test test, |
| 366 List<Group> parents) { | 369 List<Group> parents) { |
| 367 // The netry name will be `null` for the root group. | 370 var skipped = new LocalTest(test.name, test.metadata, () {}, |
| 368 var test = new LocalTest(entry.name ?? "(suite)", entry.metadata, () {}, | 371 trace: test.trace); |
| 369 trace: entry.trace); | |
| 370 | 372 |
| 371 var controller; | 373 var controller; |
| 372 controller = new LiveTestController( | 374 controller = new LiveTestController( |
| 373 suiteController.liveSuite.suite, test, () { | 375 suiteController.liveSuite.suite, skipped, () { |
| 374 controller.setState(const State(Status.running, Result.success)); | 376 controller.setState(const State(Status.running, Result.success)); |
| 375 controller.setState(const State(Status.running, Result.skipped)); | 377 controller.setState(const State(Status.running, Result.skipped)); |
| 376 | 378 |
| 377 if (entry.metadata.skipReason != null) { | 379 if (skipped.metadata.skipReason != null) { |
| 378 controller.message( | 380 controller.message( |
| 379 new Message.skip("Skip: ${entry.metadata.skipReason}")); | 381 new Message.skip("Skip: ${skipped.metadata.skipReason}")); |
| 380 } | 382 } |
| 381 | 383 |
| 382 controller.setState(const State(Status.complete, Result.skipped)); | 384 controller.setState(const State(Status.complete, Result.skipped)); |
| 383 controller.completer.complete(); | 385 controller.completer.complete(); |
| 384 }, () {}, groups: parents); | 386 }, () {}, groups: parents); |
| 385 | 387 |
| 386 return _runLiveTest(suiteController, controller.liveTest); | 388 return _runLiveTest(suiteController, controller.liveTest); |
| 387 } | 389 } |
| 388 | 390 |
| 389 /// Closes [liveTest] and tells the engine to re-run it once it's done | 391 /// Closes [liveTest] and tells the engine to re-run it once it's done |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 488 var futures = allLiveTests.map((liveTest) => liveTest.close()).toList(); | 490 var futures = allLiveTests.map((liveTest) => liveTest.close()).toList(); |
| 489 | 491 |
| 490 // Closing the load pool will close the test suites as soon as their tests | 492 // Closing the load pool will close the test suites as soon as their tests |
| 491 // are done. For browser suites this is effectively immediate since their | 493 // are done. For browser suites this is effectively immediate since their |
| 492 // tests shut down as soon as they're closed, but for VM suites we may need | 494 // tests shut down as soon as they're closed, but for VM suites we may need |
| 493 // to wait for tearDowns or tearDownAlls to run. | 495 // to wait for tearDowns or tearDownAlls to run. |
| 494 futures.add(_loadPool.close()); | 496 futures.add(_loadPool.close()); |
| 495 await Future.wait(futures, eagerError: true); | 497 await Future.wait(futures, eagerError: true); |
| 496 } | 498 } |
| 497 } | 499 } |
| OLD | NEW |