diff --git a/CHANGELOG.md b/CHANGELOG.md index e38fd1f378769187df61341ed8bbc08598266642..4a879652ca9a1ba796983d78888459488cced4e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 0.12.27 +* When `addTearDown()` is called within a call to `setUpAll()`, it runs its + callback after *all* tests instead of running it after the `setUpAll()` + callback. + * When running in an interactive terminal, the test runner now prints status lines as wide as the terminal and no wider. diff --git a/lib/src/backend/declarer.dart b/lib/src/backend/declarer.dart index 4e8851d4fde8c731e9ba7ac636bc2f387269bb14..29490ebf8ba918bbbf458cb6ba0f91a58bbe68ea 100644 --- a/lib/src/backend/declarer.dart +++ b/lib/src/backend/declarer.dart @@ -155,11 +155,15 @@ class Declarer { } } - await Invoker.current.waitForOutstandingCallbacks(() async { - await _runSetUps(); - await body(); - }); - }, trace: _collectTraces ? new Trace.current(2) : null)); + await runZoned( + () => Invoker.current.waitForOutstandingCallbacks(() async { + await _runSetUps(); + await body(); + }), + // Make the declarer visible to running tests so that they'll throw + // useful errors when calling `test()` and `group()` within a test. + zoneValues: {#test.declarer: this}); + }, trace: _collectTraces ? new Trace.current(2) : null, guarded: false)); } /// Creates a group of tests. @@ -224,7 +228,14 @@ class Declarer { _tearDownAlls.add(callback); } + /// Like [tearDownAll], but called from within a running [setUpAll] test to + /// dynamically add a [tearDownAll]. + void addTearDownAll(callback()) => _tearDownAlls.add(callback); + /// Finalizes and returns the group being declared. + /// + /// **Note**: The tests in this group must be run in a [Invoker.guard] + /// context; otherwise, test errors won't be captured. Group build() { _checkNotBuilt("build"); @@ -258,18 +269,30 @@ class Declarer { if (_setUpAlls.isEmpty) return null; return new LocalTest(_prefix("(setUpAll)"), _metadata, () { - return Future.forEach(_setUpAlls, (setUp) => setUp()); - }, trace: _setUpAllTrace); + return runZoned(() => Future.forEach(_setUpAlls, (setUp) => setUp()), + // Make the declarer visible to running scaffolds so they can add to + // the declarer's `tearDownAll()` list. + zoneValues: {#test.declarer: this}); + }, trace: _setUpAllTrace, guarded: false, isScaffoldAll: true); } /// Returns a [Test] that runs the callbacks in [_tearDownAll]. Test get _tearDownAll { - if (_tearDownAlls.isEmpty) return null; + // We have to create a tearDownAll if there's a setUpAll, since it might + // dynamically add tear-down code using [addTearDownAll]. + if (_setUpAlls.isEmpty && _tearDownAlls.isEmpty) return null; return new LocalTest(_prefix("(tearDownAll)"), _metadata, () { - return Invoker.current.unclosable(() { - return Future.forEach(_tearDownAlls.reversed, errorsDontStopTest); - }); - }, trace: _tearDownAllTrace); + return runZoned(() { + return Invoker.current.unclosable(() async { + while (_tearDownAlls.isNotEmpty) { + await errorsDontStopTest(_tearDownAlls.removeLast()); + } + }); + }, + // Make the declarer visible to running scaffolds so they can add to + // the declarer's `tearDownAll()` list. + zoneValues: {#test.declarer: this}); + }, trace: _tearDownAllTrace, guarded: false, isScaffoldAll: true); } } diff --git a/lib/src/backend/invoker.dart b/lib/src/backend/invoker.dart index 12295c59a5924fd7dbb45b19b0bdecc56eb0704e..2335d0893172bb2f42a9c2b17455799e53f43717 100644 --- a/lib/src/backend/invoker.dart +++ b/lib/src/backend/invoker.dart @@ -10,6 +10,7 @@ import '../frontend/expect.dart'; import '../runner/load_suite.dart'; import '../utils.dart'; import 'closed_exception.dart'; +import 'declarer.dart'; import 'group.dart'; import 'live_test.dart'; import 'live_test_controller.dart'; @@ -28,21 +29,38 @@ class LocalTest extends Test { final Metadata metadata; final Trace trace; + /// Whether this is a test defined using `setUpAll()` or `tearDownAll()`. + final bool isScaffoldAll; + /// The test body. - final AsyncFunction _body; + final Function() _body; + + /// Whether the test is run in its own error zone. + final bool _guarded; - LocalTest(this.name, this.metadata, body(), {this.trace}) : _body = body; + /// Creates a new [LocalTest]. + /// + /// If [guarded] is `true`, the test is run in its own error zone, and any + /// errors that escape that zone cause the test to fail. If it's `false`, it's + /// the caller's responsiblity to invoke [LiveTest.run] in the context of a + /// call to [Invoker.guard]. + LocalTest(this.name, this.metadata, this._body, + {this.trace, bool guarded: true, this.isScaffoldAll: false}) + : _guarded = guarded; + + LocalTest._(this.name, this.metadata, this._body, this.trace, this._guarded, + this.isScaffoldAll); /// Loads a single runnable instance of this test. LiveTest load(Suite suite, {Iterable<Group> groups}) { - var invoker = new Invoker._(suite, this, groups: groups); + var invoker = new Invoker._(suite, this, groups: groups, guarded: _guarded); return invoker.liveTest; } Test forPlatform(TestPlatform platform, {OperatingSystem os}) { if (!metadata.testOn.evaluate(platform, os: os)) return null; - return new LocalTest(name, metadata.forPlatform(platform, os: os), _body, - trace: trace); + return new LocalTest._(name, metadata.forPlatform(platform, os: os), _body, + trace, _guarded, isScaffoldAll); } } @@ -58,6 +76,9 @@ class Invoker { LiveTest get liveTest => _controller.liveTest; LiveTestController _controller; + /// Whether to run this test in its own error zone. + final bool _guarded; + /// Whether the test can be closed in the current zone. bool get _closable => Zone.current[_closableKey]; @@ -118,6 +139,22 @@ class Invoker { return Zone.current[#test.invoker]; } + /// Runs [callback] in a zone where unhandled errors from [LiveTest]s are + /// caught and dispatched to the appropriate [Invoker]. + static T guard<T>(T callback()) => + runZoned(callback, zoneSpecification: new ZoneSpecification( + // Use [handleUncaughtError] rather than [onError] so we can + // capture [zone] and with it the outstanding callback counter for + // the zone in which [error] was thrown. + handleUncaughtError: (self, _, zone, error, stackTrace) { + var invoker = zone[#test.invoker]; + if (invoker != null) { + self.parent.run(() => invoker._handleError(zone, error, stackTrace)); + } else { + self.parent.handleUncaughtError(error, stackTrace); + } + })); + /// The zone that the top level of [_test.body] is running in. /// /// Tracking this ensures that [_timeoutTimer] isn't created in a @@ -135,7 +172,9 @@ class Invoker { /// Messages to print if and when this test fails. final _printsOnFailure = <String>[]; - Invoker._(Suite suite, LocalTest test, {Iterable<Group> groups}) { + Invoker._(Suite suite, LocalTest test, + {Iterable<Group> groups, bool guarded: true}) + : _guarded = guarded { _controller = new LiveTestController( suite, test, _onRun, _onCloseCompleter.complete, groups: groups); @@ -147,7 +186,12 @@ class Invoker { /// run in the reverse of the order they're declared. void addTearDown(callback()) { if (closed) throw new ClosedException(); - _tearDowns.add(callback); + + if (_test.isScaffoldAll) { + Declarer.current.addTearDownAll(callback); + } else { + _tearDowns.add(callback); + } } /// Tells the invoker that there's a callback running that it should wait for @@ -282,7 +326,15 @@ class Invoker { void _handleError(Zone zone, error, [StackTrace stackTrace]) { // Ignore errors propagated from previous test runs if (_runCount != zone[#runCount]) return; - if (stackTrace == null) stackTrace = new Chain.current(); + + // Get the chain information from the zone in which the error was thrown. + zone.run(() { + if (stackTrace == null) { + stackTrace = new Chain.current(); + } else { + stackTrace = new Chain.forTrace(stackTrace); + } + }); // Store these here because they'll change when we set the state below. var shouldBeDone = liveTest.state.shouldBeDone; @@ -334,60 +386,68 @@ class Invoker { _runCount++; Chain.capture(() { - runZoned(() async { - _invokerZone = Zone.current; - _outstandingCallbackZones.add(Zone.current); - - // Run the test asynchronously so that the "running" state change has - // a chance to hit its event handler(s) before the test produces an - // error. If an error is emitted before the first state change is - // handled, we can end up with [onError] callbacks firing before the - // corresponding [onStateChkange], which violates the timing - // guarantees. - // - // Using [new Future] also avoids starving the DOM or other - // microtask-level events. - new Future(() async { - await _test._body(); - await unclosable(_runTearDowns); - removeOutstandingCallback(); - }); - - await _outstandingCallbacks.noOutstandingCallbacks; - if (_timeoutTimer != null) _timeoutTimer.cancel(); - - if (liveTest.state.result != Result.success && - _runCount < liveTest.test.metadata.retry + 1) { + _guardIfGuarded(() { + runZoned(() async { + _invokerZone = Zone.current; + _outstandingCallbackZones.add(Zone.current); + + // Run the test asynchronously so that the "running" state change + // has a chance to hit its event handler(s) before the test produces + // an error. If an error is emitted before the first state change is + // handled, we can end up with [onError] callbacks firing before the + // corresponding [onStateChkange], which violates the timing + // guarantees. + // + // Using [new Future] also avoids starving the DOM or other + // microtask-level events. + new Future(() async { + await _test._body(); + await unclosable(_runTearDowns); + removeOutstandingCallback(); + }); + + await _outstandingCallbacks.noOutstandingCallbacks; + if (_timeoutTimer != null) _timeoutTimer.cancel(); + + if (liveTest.state.result != Result.success && + _runCount < liveTest.test.metadata.retry + 1) { + _controller + .message(new Message.print("Retry: ${liveTest.test.name}")); + _onRun(); + return; + } + _controller - .message(new Message.print("Retry: ${liveTest.test.name}")); - _onRun(); - return; - } + .setState(new State(Status.complete, liveTest.state.result)); + + _controller.completer.complete(); + }, + zoneValues: { + #test.invoker: this, + // Use the invoker as a key so that multiple invokers can have + // different outstanding callback counters at once. + _counterKey: outstandingCallbacksForBody, + _closableKey: true, + #runCount: _runCount, + }, + zoneSpecification: new ZoneSpecification( + print: (_, __, ___, line) => _print(line))); + }); + }, when: liveTest.test.metadata.chainStackTraces, errorZone: false); + } - _controller.setState(new State(Status.complete, liveTest.state.result)); - - _controller.completer.complete(); - }, - zoneValues: { - #test.invoker: this, - // Use the invoker as a key so that multiple invokers can have different - // outstanding callback counters at once. - _counterKey: outstandingCallbacksForBody, - _closableKey: true, - #runCount: _runCount - }, - zoneSpecification: new ZoneSpecification( - print: (self, parent, zone, line) => - _controller.message(new Message.print(line)), - // Use [handleUncaughtError] rather than [onError] so we can - // capture [zone] and with it the outstanding callback counter for - // the zone in which [error] was thrown. - handleUncaughtError: (self, _, zone, error, stackTrace) => self - .parent - .run(() => _handleError(zone, error, stackTrace)))); - }, when: liveTest.test.metadata.chainStackTraces); + /// Runs [callback], in a [Invoker.guard] context if [_guarded] is `true`. + void _guardIfGuarded(void callback()) { + if (_guarded) { + Invoker.guard(callback); + } else { + callback(); + } } + /// Prints [text] as a message to [_controller]. + void _print(String text) => _controller.message(new Message.print(text)); + /// Run [_tearDowns] in reverse order. Future _runTearDowns() async { while (_tearDowns.isNotEmpty) { diff --git a/lib/src/runner/remote_listener.dart b/lib/src/runner/remote_listener.dart index 883101fa498e14023139f42058bdb887e449d2de..bf08c44f662d64cb73f2fc8ecbc9c34de54794bc 100644 --- a/lib/src/runner/remote_listener.dart +++ b/lib/src/runner/remote_listener.dart @@ -9,6 +9,7 @@ import 'package:term_glyph/term_glyph.dart' as glyph; import '../backend/declarer.dart'; import '../backend/group.dart'; +import '../backend/invoker.dart'; import '../backend/live_test.dart'; import '../backend/metadata.dart'; import '../backend/operating_system.dart'; @@ -97,7 +98,14 @@ class RemoteListener { : OperatingSystem.find(message['os']), path: message['path']); - new RemoteListener._(suite, printZone)._listen(channel); + runZoned(() { + Invoker.guard( + () => new RemoteListener._(suite, printZone)._listen(channel)); + }, + // Make the declarer visible to running tests so that they'll throw + // useful errors when calling `test()` and `group()` within a test, + // and so they can add to the declarer's `tearDownAll()` list. + zoneValues: {#test.declarer: declarer}); }, onError: (error, stackTrace) { _sendError(channel, error, stackTrace, verboseChain); }, zoneSpecification: new ZoneSpecification(print: (_, __, ___, line) { diff --git a/lib/test.dart b/lib/test.dart index 2550bef071a66f812370966fca41087e4018e363..8d159ad514b162ef81b6f65eae36ac1036e90b74 100644 --- a/lib/test.dart +++ b/lib/test.dart @@ -67,7 +67,8 @@ Declarer get _declarer { ExpandedReporter.watch(engine, color: true, printPath: false, printPlatform: false); - var success = await engine.run(); + var success = await runZoned(() => Invoker.guard(engine.run), + zoneValues: {#test.declarer: _globalDeclarer}); // TODO(nweiz): Set the exit code on the VM when issue 6943 is fixed. if (success) return null; print(''); @@ -252,6 +253,9 @@ void tearDown(callback()) => _declarer.tearDown(callback); /// /// The [callback] is run before any callbacks registered with [tearDown]. Like /// [tearDown], the most recently registered callback is run first. +/// +/// If this is called from within a [setUpAll] or [tearDownAll] callback, it +/// instead runs the function after *all* tests in the current test suite. void addTearDown(callback()) { if (Invoker.current == null) { throw new StateError("addTearDown() may only be called within a test."); diff --git a/pubspec.yaml b/pubspec.yaml index 5c20d16eb8e47078003ead732d571bd7c8457f46..a73064298acd9cf95c982a11b2ebb10f091230a2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: test -version: 0.12.27-dev +version: 0.12.27 author: Dart Team <misc@dartlang.org> description: A library for writing dart unit tests. homepage: https://github.com/dart-lang/test @@ -29,7 +29,7 @@ dependencies: source_map_stack_trace: '^1.1.4' source_maps: '^0.10.2' source_span: '^1.4.0' - stack_trace: '^1.6.0' + stack_trace: '^1.9.0' stream_channel: '^1.6.0' string_scanner: '>=0.1.1 <2.0.0' term_glyph: '^1.0.0' diff --git a/test/frontend/add_tear_down_test.dart b/test/frontend/add_tear_down_test.dart index 650f0fd476a51934e00bbb95a8995bed97156e44..3a63ff80c83cbc07ad7217dbbff3389aa965028f 100644 --- a/test/frontend/add_tear_down_test.dart +++ b/test/frontend/add_tear_down_test.dart @@ -7,203 +7,457 @@ import 'dart:async'; import 'package:async/async.dart'; import 'package:test/test.dart'; +import 'package:test/src/backend/declarer.dart'; + import '../utils.dart'; void main() { - test("runs after the test body", () { - return expectTestsPass(() { - var test1Run = false; - var tearDownRun = false; - test("test 1", () { - addTearDown(() { - expect(test1Run, isTrue); + group("in a test", () { + test("runs after the test body", () { + return expectTestsPass(() { + var test1Run = false; + var tearDownRun = false; + test("test 1", () { + addTearDown(() { + expect(test1Run, isTrue); + expect(tearDownRun, isFalse); + tearDownRun = true; + }); + expect(tearDownRun, isFalse); - tearDownRun = true; + test1Run = true; }); - expect(tearDownRun, isFalse); - test1Run = true; - }); - - test("test 2", () { - expect(tearDownRun, isTrue); + test("test 2", () { + expect(tearDownRun, isTrue); + }); }); }); - }); - test("multiples run in reverse order", () { - return expectTestsPass(() { - var tearDown1Run = false; - var tearDown2Run = false; - var tearDown3Run = false; + test("multiples run in reverse order", () { + return expectTestsPass(() { + var tearDown1Run = false; + var tearDown2Run = false; + var tearDown3Run = false; - test("test 1", () { - addTearDown(() { - expect(tearDown1Run, isFalse); - expect(tearDown2Run, isTrue); - expect(tearDown3Run, isTrue); - tearDown1Run = true; - }); + test("test 1", () { + addTearDown(() { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isTrue); + tearDown1Run = true; + }); - addTearDown(() { - expect(tearDown1Run, isFalse); - expect(tearDown2Run, isFalse); - expect(tearDown3Run, isTrue); - tearDown2Run = true; - }); + addTearDown(() { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isTrue); + tearDown2Run = true; + }); + + addTearDown(() { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + tearDown3Run = true; + }); - addTearDown(() { expect(tearDown1Run, isFalse); expect(tearDown2Run, isFalse); expect(tearDown3Run, isFalse); - tearDown3Run = true; }); - expect(tearDown1Run, isFalse); - expect(tearDown2Run, isFalse); - expect(tearDown3Run, isFalse); + test("test 2", () { + expect(tearDown1Run, isTrue); + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isTrue); + }); }); + }); - test("test 2", () { - expect(tearDown1Run, isTrue); - expect(tearDown2Run, isTrue); - expect(tearDown3Run, isTrue); + test("can be called in addTearDown", () { + return expectTestsPass(() { + var tearDown2Run = false; + var tearDown3Run = false; + + test("test 1", () { + addTearDown(() { + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isFalse); + tearDown3Run = true; + }); + + addTearDown(() { + addTearDown(() { + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + tearDown2Run = true; + }); + }); + }); + + test("test 2", () { + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isTrue); + }); }); }); - }); - test("can be called in addTearDown", () { - return expectTestsPass(() { - var tearDown2Run = false; - var tearDown3Run = false; + test("can be called in tearDown", () { + return expectTestsPass(() { + var tearDown2Run = false; + var tearDown3Run = false; - test("test 1", () { - addTearDown(() { + tearDown(() { expect(tearDown2Run, isTrue); expect(tearDown3Run, isFalse); tearDown3Run = true; }); - addTearDown(() { + tearDown(() { + tearDown2Run = false; + tearDown3Run = false; + addTearDown(() { expect(tearDown2Run, isFalse); expect(tearDown3Run, isFalse); tearDown2Run = true; }); }); + + test("test 1", () {}); + + test("test 2", () { + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isTrue); + }); }); + }); - test("test 2", () { - expect(tearDown2Run, isTrue); - expect(tearDown3Run, isTrue); + test("runs before a normal tearDown", () { + return expectTestsPass(() { + var groupTearDownRun = false; + var testTearDownRun = false; + group("group", () { + tearDown(() { + expect(testTearDownRun, isTrue); + expect(groupTearDownRun, isFalse); + groupTearDownRun = true; + }); + + test("test 1", () { + addTearDown(() { + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isFalse); + testTearDownRun = true; + }); + + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isFalse); + }); + }); + + test("test 2", () { + expect(groupTearDownRun, isTrue); + expect(testTearDownRun, isTrue); + }); }); }); - }); - test("can be called in tearDown", () { - return expectTestsPass(() { - var tearDown2Run = false; - var tearDown3Run = false; + test("runs in the same error zone as the test", () { + return expectTestsPass(() { + test("test", () { + var future = new Future.error("oh no"); + expect(future, throwsA("oh no")); - tearDown(() { - expect(tearDown2Run, isTrue); - expect(tearDown3Run, isFalse); - tearDown3Run = true; + addTearDown(() { + // If the tear-down is in a different error zone than the test, the + // error will try to cross the zone boundary and get top-leveled. + expect(future, throwsA("oh no")); + }); + }); }); + }); - tearDown(() { - tearDown2Run = false; - tearDown3Run = false; + group("asynchronously", () { + test("blocks additional test tearDowns on in-band async", () { + return expectTestsPass(() { + var tearDown1Run = false; + var tearDown2Run = false; + var tearDown3Run = false; + test("test", () { + addTearDown(() async { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isTrue); + await pumpEventQueue(); + tearDown1Run = true; + }); - addTearDown(() { - expect(tearDown2Run, isFalse); - expect(tearDown3Run, isFalse); - tearDown2Run = true; + addTearDown(() async { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isTrue); + await pumpEventQueue(); + tearDown2Run = true; + }); + + addTearDown(() async { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + await pumpEventQueue(); + tearDown3Run = true; + }); + + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + }); }); }); - test("test 1", () {}); + test("doesn't block additional test tearDowns on out-of-band async", () { + return expectTestsPass(() { + var tearDown1Run = false; + var tearDown2Run = false; + var tearDown3Run = false; + test("test", () { + addTearDown(() { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + + expect(new Future(() { + tearDown1Run = true; + }), completes); + }); + + addTearDown(() { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + + expect(new Future(() { + tearDown2Run = true; + }), completes); + }); + + addTearDown(() { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + + expect(new Future(() { + tearDown3Run = true; + }), completes); + }); - test("test 2", () { - expect(tearDown2Run, isTrue); - expect(tearDown3Run, isTrue); + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + }); + }); }); - }); - }); - test("runs before a normal tearDown", () { - return expectTestsPass(() { - var groupTearDownRun = false; - var testTearDownRun = false; - group("group", () { - tearDown(() { - expect(testTearDownRun, isTrue); - expect(groupTearDownRun, isFalse); - groupTearDownRun = true; + test("blocks additional group tearDowns on in-band async", () { + return expectTestsPass(() { + var groupTearDownRun = false; + var testTearDownRun = false; + tearDown(() async { + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isTrue); + await pumpEventQueue(); + groupTearDownRun = true; + }); + + test("test", () { + addTearDown(() async { + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isFalse); + await pumpEventQueue(); + testTearDownRun = true; + }); + + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isFalse); + }); }); + }); - test("test 1", () { - addTearDown(() { + test("doesn't block additional group tearDowns on out-of-band async", () { + return expectTestsPass(() { + var groupTearDownRun = false; + var testTearDownRun = false; + tearDown(() { expect(groupTearDownRun, isFalse); expect(testTearDownRun, isFalse); - testTearDownRun = true; + + expect(new Future(() { + groupTearDownRun = true; + }), completes); }); - expect(groupTearDownRun, isFalse); - expect(testTearDownRun, isFalse); + test("test", () { + addTearDown(() { + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isFalse); + + expect(new Future(() { + testTearDownRun = true; + }), completes); + }); + + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isFalse); + }); }); }); - test("test 2", () { - expect(groupTearDownRun, isTrue); - expect(testTearDownRun, isTrue); + test("blocks further tests on in-band async", () { + return expectTestsPass(() { + var tearDownRun = false; + test("test 1", () { + addTearDown(() async { + expect(tearDownRun, isFalse); + await pumpEventQueue(); + tearDownRun = true; + }); + }); + + test("test 2", () { + expect(tearDownRun, isTrue); + }); + }); + }); + + test("blocks further tests on out-of-band async", () { + return expectTestsPass(() { + var tearDownRun = false; + test("test 1", () { + addTearDown(() async { + expect(tearDownRun, isFalse); + expect( + pumpEventQueue().then((_) { + tearDownRun = true; + }), + completes); + }); + }); + + test("after", () { + expect(tearDownRun, isTrue); + }); + }); }); }); - }); - test("runs in the same error zone as the test", () { - return expectTestsPass(() { - test("test", () { - var future = new Future.error("oh no"); - expect(future, throwsA("oh no")); + group("with an error", () { + test("reports the error", () async { + var engine = declareEngine(() { + test("test", () { + addTearDown(() => throw new TestFailure("fail")); + }); + }); - addTearDown(() { - // If the tear-down is in a different error zone than the test, the - // error will try to cross the zone boundary and get top-leveled. - expect(future, throwsA("oh no")); + var queue = new StreamQueue(engine.onTestStarted); + var liveTestFuture = queue.next; + + expect(await engine.run(), isFalse); + + var liveTest = await liveTestFuture; + expect(liveTest.test.name, equals("test")); + expectTestFailed(liveTest, "fail"); + }); + + test("runs further test tearDowns", () async { + // Declare this in the outer test so if it doesn't run, the outer test + // will fail. + var shouldRun = expectAsync0(() {}); + + var engine = declareEngine(() { + test("test", () { + addTearDown(() => throw "error"); + addTearDown(shouldRun); + }); }); + + expect(await engine.run(), isFalse); + }); + + test("runs further group tearDowns", () async { + // Declare this in the outer test so if it doesn't run, the outer test + // will fail. + var shouldRun = expectAsync0(() {}); + + var engine = declareEngine(() { + tearDown(shouldRun); + + test("test", () { + addTearDown(() => throw "error"); + }); + }); + + expect(await engine.run(), isFalse); }); }); }); - group("asynchronously", () { - test("blocks additional test tearDowns on in-band async", () { - return expectTestsPass(() { - var tearDown1Run = false; - var tearDown2Run = false; - var tearDown3Run = false; - test("test", () { - addTearDown(() async { + group("in setUpAll()", () { + test("runs after all tests", () async { + var test1Run = false; + var test2Run = false; + var tearDownRun = false; + await expectTestsPass(() { + setUpAll(() { + addTearDown(() { + expect(test1Run, isTrue); + expect(test2Run, isTrue); + expect(tearDownRun, isFalse); + tearDownRun = true; + }); + }); + + test("test 1", () { + test1Run = true; + expect(tearDownRun, isFalse); + }); + + test("test 2", () { + test2Run = true; + expect(tearDownRun, isFalse); + }); + }); + + expect(test1Run, isTrue); + expect(test2Run, isTrue); + expect(tearDownRun, isTrue); + }); + + test("multiples run in reverse order", () async { + var tearDown1Run = false; + var tearDown2Run = false; + var tearDown3Run = false; + await expectTestsPass(() { + setUpAll(() { + addTearDown(() { expect(tearDown1Run, isFalse); expect(tearDown2Run, isTrue); expect(tearDown3Run, isTrue); - await pumpEventQueue(); tearDown1Run = true; }); - addTearDown(() async { + addTearDown(() { expect(tearDown1Run, isFalse); expect(tearDown2Run, isFalse); expect(tearDown3Run, isTrue); - await pumpEventQueue(); tearDown2Run = true; }); - addTearDown(() async { + addTearDown(() { expect(tearDown1Run, isFalse); expect(tearDown2Run, isFalse); expect(tearDown3Run, isFalse); - await pumpEventQueue(); tearDown3Run = true; }); @@ -211,191 +465,337 @@ void main() { expect(tearDown2Run, isFalse); expect(tearDown3Run, isFalse); }); + + test("test", () { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + }); }); + + expect(tearDown1Run, isTrue); + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isTrue); }); - test("doesn't block additional test tearDowns on out-of-band async", () { - return expectTestsPass(() { - var tearDown1Run = false; - var tearDown2Run = false; - var tearDown3Run = false; - test("test", () { + test("can be called in addTearDown", () async { + var tearDown2Run = false; + var tearDown3Run = false; + await expectTestsPass(() { + setUpAll(() { addTearDown(() { - expect(tearDown1Run, isFalse); - expect(tearDown2Run, isFalse); + expect(tearDown2Run, isTrue); expect(tearDown3Run, isFalse); - - expect(new Future(() { - tearDown1Run = true; - }), completes); + tearDown3Run = true; }); addTearDown(() { - expect(tearDown1Run, isFalse); - expect(tearDown2Run, isFalse); - expect(tearDown3Run, isFalse); - - expect(new Future(() { + addTearDown(() { + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); tearDown2Run = true; - }), completes); + }); }); + }); + + test("test", () { + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + }); + }); + + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isTrue); + }); + + test("can be called in tearDownAll", () async { + var tearDown2Run = false; + var tearDown3Run = false; + await expectTestsPass(() { + tearDownAll(() { + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isFalse); + tearDown3Run = true; + }); + + tearDownAll(() { + tearDown2Run = false; + tearDown3Run = false; addTearDown(() { - expect(tearDown1Run, isFalse); expect(tearDown2Run, isFalse); expect(tearDown3Run, isFalse); - - expect(new Future(() { - tearDown3Run = true; - }), completes); + tearDown2Run = true; }); - - expect(tearDown1Run, isFalse); - expect(tearDown2Run, isFalse); - expect(tearDown3Run, isFalse); }); + + test("test", () {}); }); + + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isTrue); }); - test("blocks additional group tearDowns on in-band async", () { - return expectTestsPass(() { - var groupTearDownRun = false; - var testTearDownRun = false; - tearDown(() async { - expect(groupTearDownRun, isFalse); + test("runs before a normal tearDownAll", () async { + var groupTearDownRun = false; + var testTearDownRun = false; + await expectTestsPass(() { + tearDownAll(() { expect(testTearDownRun, isTrue); - await pumpEventQueue(); + expect(groupTearDownRun, isFalse); groupTearDownRun = true; }); - test("test", () { - addTearDown(() async { + setUpAll(() { + addTearDown(() { expect(groupTearDownRun, isFalse); expect(testTearDownRun, isFalse); - await pumpEventQueue(); testTearDownRun = true; }); + }); + test("test", () { expect(groupTearDownRun, isFalse); expect(testTearDownRun, isFalse); }); }); + + expect(groupTearDownRun, isTrue); + expect(testTearDownRun, isTrue); }); - test("doesn't block additional group tearDowns on out-of-band async", () { + test("runs in the same error zone as the setUpAll", () async { return expectTestsPass(() { + setUpAll(() { + var future = new Future.error("oh no"); + expect(future, throwsA("oh no")); + + addTearDown(() { + // If the tear-down is in a different error zone than the setUpAll, + // the error will try to cross the zone boundary and get + // top-leveled. + expect(future, throwsA("oh no")); + }); + }); + + test("test", () {}); + }); + }); + + group("asynchronously", () { + test("blocks additional tearDowns on in-band async", () async { + var tearDown1Run = false; + var tearDown2Run = false; + var tearDown3Run = false; + await expectTestsPass(() { + setUpAll(() { + addTearDown(() async { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isTrue); + await pumpEventQueue(); + tearDown1Run = true; + }); + + addTearDown(() async { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isTrue); + await pumpEventQueue(); + tearDown2Run = true; + }); + + addTearDown(() async { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + await pumpEventQueue(); + tearDown3Run = true; + }); + }); + + test("test", () { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + }); + }); + + expect(tearDown1Run, isTrue); + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isTrue); + }); + + test("doesn't block additional tearDowns on out-of-band async", () async { + var tearDown1Run = false; + var tearDown2Run = false; + var tearDown3Run = false; + await expectTestsPass(() { + setUpAll(() { + addTearDown(() { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + + expect(new Future(() { + tearDown1Run = true; + }), completes); + }); + + addTearDown(() { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + + expect(new Future(() { + tearDown2Run = true; + }), completes); + }); + + addTearDown(() { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + + expect(new Future(() { + tearDown3Run = true; + }), completes); + }); + }); + + test("test", () { + expect(tearDown1Run, isFalse); + expect(tearDown2Run, isFalse); + expect(tearDown3Run, isFalse); + }); + }); + + expect(tearDown1Run, isTrue); + expect(tearDown2Run, isTrue); + expect(tearDown3Run, isTrue); + }); + + test("blocks additional tearDownAlls on in-band async", () async { var groupTearDownRun = false; var testTearDownRun = false; - tearDown(() { - expect(groupTearDownRun, isFalse); - expect(testTearDownRun, isFalse); - - expect(new Future(() { + await expectTestsPass(() { + tearDownAll(() async { + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isTrue); + await pumpEventQueue(); groupTearDownRun = true; - }), completes); + }); + + setUpAll(() { + addTearDown(() async { + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isFalse); + await pumpEventQueue(); + testTearDownRun = true; + }); + }); + + test("test", () { + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isFalse); + }); }); - test("test", () { - addTearDown(() { + expect(groupTearDownRun, isTrue); + expect(testTearDownRun, isTrue); + }); + + test("doesn't block additional tearDownAlls on out-of-band async", + () async { + var groupTearDownRun = false; + var testTearDownRun = false; + await expectTestsPass(() { + tearDownAll(() { expect(groupTearDownRun, isFalse); expect(testTearDownRun, isFalse); expect(new Future(() { - testTearDownRun = true; + groupTearDownRun = true; }), completes); }); - expect(groupTearDownRun, isFalse); - expect(testTearDownRun, isFalse); - }); - }); - }); + setUpAll(() { + addTearDown(() { + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isFalse); - test("blocks further tests on in-band async", () { - return expectTestsPass(() { - var tearDownRun = false; - test("test 1", () { - addTearDown(() async { - expect(tearDownRun, isFalse); - await pumpEventQueue(); - tearDownRun = true; + expect(new Future(() { + testTearDownRun = true; + }), completes); + }); }); - }); - test("test 2", () { - expect(tearDownRun, isTrue); + test("test", () { + expect(groupTearDownRun, isFalse); + expect(testTearDownRun, isFalse); + }); }); + + expect(groupTearDownRun, isTrue); + expect(testTearDownRun, isTrue); }); }); - test("blocks further tests on out-of-band async", () { - return expectTestsPass(() { - var tearDownRun = false; - test("test 1", () { - addTearDown(() async { - expect(tearDownRun, isFalse); - expect( - pumpEventQueue().then((_) { - tearDownRun = true; - }), - completes); + group("with an error", () { + test("reports the error", () async { + var engine = declareEngine(() { + setUpAll(() { + addTearDown(() => throw new TestFailure("fail")); }); - }); - test("after", () { - expect(tearDownRun, isTrue); + test("test", () {}); }); - }); - }); - }); - group("with an error", () { - test("reports the error", () async { - var engine = declareEngine(() { - test("test", () { - addTearDown(() => throw new TestFailure("fail")); - }); - }); + var queue = new StreamQueue(engine.onTestStarted); + queue.skip(2); + var liveTestFuture = queue.next; - var queue = new StreamQueue(engine.onTestStarted); - var liveTestFuture = queue.next; + expect(await engine.run(), isFalse); - expect(await engine.run(), isFalse); + var liveTest = await liveTestFuture; + expect(liveTest.test.name, equals("(tearDownAll)")); + expectTestFailed(liveTest, "fail"); + }); - var liveTest = await liveTestFuture; - expect(liveTest.test.name, equals("test")); - expectTestFailed(liveTest, "fail"); - }); + test("runs further tearDowns", () async { + // Declare this in the outer test so if it doesn't run, the outer test + // will fail. + var shouldRun = expectAsync0(() {}); - test("runs further test tearDowns", () async { - // Declare this in the outer test so if it doesn't run, the outer test - // will fail. - var shouldRun = expectAsync0(() {}); + var engine = declareEngine(() { + setUpAll(() { + addTearDown(() => throw "error"); + addTearDown(shouldRun); + }); - var engine = declareEngine(() { - test("test", () { - addTearDown(() => throw "error"); - addTearDown(shouldRun); + test("test", () {}); }); + + expect(await engine.run(), isFalse); }); - expect(await engine.run(), isFalse); - }); + test("runs further tearDownAlls", () async { + // Declare this in the outer test so if it doesn't run, the outer test + // will fail. + var shouldRun = expectAsync0(() {}); - test("runs further group tearDowns", () async { - // Declare this in the outer test so if it doesn't run, the outer test - // will fail. - var shouldRun = expectAsync0(() {}); + var engine = declareEngine(() { + tearDownAll(shouldRun); - var engine = declareEngine(() { - tearDown(shouldRun); + setUpAll(() { + addTearDown(() => throw "error"); + }); - test("test", () { - addTearDown(() => throw "error"); + test("test", () {}); }); - }); - expect(await engine.run(), isFalse); + expect(await engine.run(), isFalse); + }); }); }); } diff --git a/test/runner/json_reporter_test.dart b/test/runner/json_reporter_test.dart index e6cb35a5e8dd53576c9de53bb2502013dec5ce14..daa936d3e73d087fc29483f45a4db69145474f93 100644 --- a/test/runner/json_reporter_test.dart +++ b/test/runner/json_reporter_test.dart @@ -447,6 +447,8 @@ void main() { _testDone(3, hidden: true), _testStart(4, "success", line: 9, column: 9), _testDone(4), + _testStart(5, "(tearDownAll)"), + _testDone(5, hidden: true), _done() ]); });