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 |