Add support for setUpAll and tearDownAll.
Closes #18
R=kevmoo@google.com
Review URL: https://codereview.chromium.org//1400743002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa737e7..299d220 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
## 0.12.5
+* Add `setUpAll()` and `tearDownAll()` methods that run callbacks before and
+ after all tests in a group or suite. **Note that these methods are for special
+ cases and should be avoided**—they make it very easy to accidentally introduce
+ dependencies between tests. Use `setUp()` and `tearDown()` instead if
+ possible.
+
* Allow `setUp()` and `tearDown()` to be called multiple times within the same
group.
diff --git a/lib/src/backend/declarer.dart b/lib/src/backend/declarer.dart
index f8dae47..46a52d9 100644
--- a/lib/src/backend/declarer.dart
+++ b/lib/src/backend/declarer.dart
@@ -9,9 +9,10 @@
import '../frontend/timeout.dart';
import '../utils.dart';
import 'group.dart';
+import 'group_entry.dart';
import 'invoker.dart';
import 'metadata.dart';
-import 'group_entry.dart';
+import 'test.dart';
/// A class that manages the state of tests as they're declared.
///
@@ -34,12 +35,18 @@
/// and of the test suite.
final Metadata _metadata;
- /// The set-up functions for this group.
+ /// The set-up functions to run for each test in this group.
final _setUps = new List<AsyncFunction>();
- /// The tear-down functions for this group.
+ /// The tear-down functions to run for each test in this group.
final _tearDowns = new List<AsyncFunction>();
+ /// The set-up functions to run once for this group.
+ final _setUpAlls = new List<AsyncFunction>();
+
+ /// The tear-down functions to run once for this group.
+ final _tearDownAlls = new List<AsyncFunction>();
+
/// The children of this group, either tests or sub-groups.
final _entries = new List<GroupEntry>();
@@ -67,9 +74,7 @@
/// Defines a test case with the given name and body.
void test(String name, body(), {String testOn, Timeout timeout, skip,
Map<String, dynamic> onPlatform}) {
- if (_built) {
- throw new StateError("Can't call test() once tests have begun running.");
- }
+ _checkNotBuilt("test");
var metadata = _metadata.merge(new Metadata.parse(
testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform));
@@ -90,9 +95,7 @@
/// Creates a group of tests.
void group(String name, void body(), {String testOn, Timeout timeout, skip,
Map<String, dynamic> onPlatform}) {
- if (_built) {
- throw new StateError("Can't call group() once tests have begun running.");
- }
+ _checkNotBuilt("group");
var metadata = _metadata.merge(new Metadata.parse(
testOn: testOn, timeout: timeout, skip: skip, onPlatform: onPlatform));
@@ -113,31 +116,45 @@
/// Registers a function to be run before each test in this group.
void setUp(callback()) {
- if (_built) {
- throw new StateError("Can't call setUp() once tests have begun running.");
- }
-
+ _checkNotBuilt("setUp");
_setUps.add(callback);
}
/// Registers a function to be run after each test in this group.
void tearDown(callback()) {
- if (_built) {
- throw new StateError(
- "Can't call tearDown() once tests have begun running.");
- }
-
+ _checkNotBuilt("tearDown");
_tearDowns.add(callback);
}
+ /// Registers a function to be run once before all tests.
+ void setUpAll(callback()) {
+ _checkNotBuilt("setUpAll");
+ _setUpAlls.add(callback);
+ }
+
+ /// Registers a function to be run once after all tests.
+ void tearDownAll(callback()) {
+ _checkNotBuilt("tearDownAll");
+ _tearDownAlls.add(callback);
+ }
+
/// Finalizes and returns the group being declared.
Group build() {
- if (_built) {
- throw new StateError("Can't call Declarer.build() more than once.");
- }
+ _checkNotBuilt("build");
_built = true;
- return new Group(_name, _entries.toList(), metadata: _metadata);
+ return new Group(_name, _entries.toList(),
+ metadata: _metadata,
+ setUpAll: _setUpAll,
+ tearDownAll: _tearDownAll);
+ }
+
+ /// Throws a [StateError] if [build] has been called.
+ ///
+ /// [name] should be the name of the method being called.
+ void _checkNotBuilt(String name) {
+ if (!_built) return;
+ throw new StateError("Can't call $name() once tests have begun running.");
}
/// Run the set-up functions for this and any parent groups.
@@ -173,6 +190,26 @@
});
}
+ /// Returns a [Test] that runs the callbacks in [_setUpAll].
+ Test get _setUpAll {
+ if (_setUpAlls.isEmpty) return null;
+
+ return new LocalTest(_prefix("(setUpAll)"), _metadata, () {
+ return Future.forEach(_setUpAlls, (setUp) => setUp());
+ });
+ }
+
+ /// Returns a [Test] that runs the callbacks in [_tearDownAll].
+ Test get _tearDownAll {
+ if (_tearDownAlls.isEmpty) return null;
+
+ return new LocalTest(_prefix("(tearDownAll)"), _metadata, () {
+ return Invoker.current.unclosable(() {
+ return Future.forEach(_tearDownAlls.reversed, _errorsDontStopTest);
+ });
+ });
+ }
+
/// Runs [body] with special error-handling behavior.
///
/// Errors emitted [body] will still cause the current test to fail, but they
diff --git a/lib/src/backend/group.dart b/lib/src/backend/group.dart
index bcc7e77..dc7e6bd 100644
--- a/lib/src/backend/group.dart
+++ b/lib/src/backend/group.dart
@@ -25,7 +25,18 @@
Group.root(Iterable<GroupEntry> entries, {Metadata metadata})
: this(null, entries, metadata: metadata);
- Group(this.name, Iterable<GroupEntry> entries, {Metadata metadata})
+ /// A test to run before all tests in the group.
+ ///
+ /// This is `null` if no `setUpAll` callbacks were declared.
+ final Test setUpAll;
+
+ /// A test to run after all tests in the group.
+ ///
+ /// This is `null` if no `tearDown` callbacks were declared.
+ final Test tearDownAll;
+
+ Group(this.name, Iterable<GroupEntry> entries, {Metadata metadata,
+ Test this.setUpAll, Test this.tearDownAll})
: entries = new List<GroupEntry>.unmodifiable(entries),
metadata = metadata == null ? new Metadata() : metadata;
@@ -34,13 +45,15 @@
var newMetadata = metadata.forPlatform(platform, os: os);
var filtered = _map((entry) => entry.forPlatform(platform, os: os));
if (filtered.isEmpty) return null;
- return new Group(name, filtered, metadata: newMetadata);
+ return new Group(name, filtered,
+ metadata: newMetadata, setUpAll: setUpAll, tearDownAll: tearDownAll);
}
Group filter(bool callback(Test test)) {
var filtered = _map((entry) => entry.filter(callback));
if (filtered.isEmpty) return null;
- return new Group(name, filtered, metadata: metadata);
+ return new Group(name, filtered,
+ metadata: metadata, setUpAll: setUpAll, tearDownAll: tearDownAll);
}
/// Returns the entries of this group mapped using [callback].
diff --git a/lib/src/runner/browser/browser_manager.dart b/lib/src/runner/browser/browser_manager.dart
index 925dddc..fc570d1 100644
--- a/lib/src/runner/browser/browser_manager.dart
+++ b/lib/src/runner/browser/browser_manager.dart
@@ -13,6 +13,7 @@
import '../../backend/group.dart';
import '../../backend/metadata.dart';
+import '../../backend/test.dart';
import '../../backend/test_platform.dart';
import '../../util/cancelable_future.dart';
import '../../util/multi_channel.dart';
@@ -259,11 +260,25 @@
return _deserializeGroup(suiteChannel, mapper, entry);
}
- var testMetadata = new Metadata.deserialize(entry['metadata']);
- var testChannel = suiteChannel.virtualChannel(entry['channel']);
- return new IframeTest(entry['name'], testMetadata, testChannel,
- mapper: mapper);
- }), metadata: metadata);
+ return _deserializeTest(suiteChannel, mapper, entry);
+ }),
+ metadata: metadata,
+ setUpAll: _deserializeTest(suiteChannel, mapper, group['setUpAll']),
+ tearDownAll:
+ _deserializeTest(suiteChannel, mapper, group['tearDownAll']));
+ }
+
+ /// Deserializes [test] into a concrete [Test] class.
+ ///
+ /// Returns `null` if [test] is `null`.
+ Test _deserializeTest(MultiChannel suiteChannel, StackTraceMapper mapper,
+ Map test) {
+ if (test == null) return null;
+
+ var metadata = new Metadata.deserialize(test['metadata']);
+ var testChannel = suiteChannel.virtualChannel(test['channel']);
+ return new IframeTest(test['name'], metadata, testChannel,
+ mapper: mapper);
}
/// An implementation of [Environment.displayPause].
diff --git a/lib/src/runner/browser/iframe_listener.dart b/lib/src/runner/browser/iframe_listener.dart
index 815a37e..834f3e1 100644
--- a/lib/src/runner/browser/iframe_listener.dart
+++ b/lib/src/runner/browser/iframe_listener.dart
@@ -131,32 +131,42 @@
});
}
- /// Serializes [entries] into a JSON-safe map.
+ /// Serializes [group] into a JSON-safe map.
Map _serializeGroup(MultiChannel channel, Group group) {
return {
"type": "group",
"name": group.name,
"metadata": group.metadata.serialize(),
+ "setUpAll": _serializeTest(channel, group.setUpAll),
+ "tearDownAll": _serializeTest(channel, group.tearDownAll),
"entries": group.entries.map((entry) {
- if (entry is Group) return _serializeGroup(channel, entry);
-
- var test = entry as Test;
- var testChannel = channel.virtualChannel();
- testChannel.stream.listen((message) {
- assert(message['command'] == 'run');
- _runTest(test, channel.virtualChannel(message['channel']));
- });
-
- return {
- "type": "test",
- "name": test.name,
- "metadata": test.metadata.serialize(),
- "channel": testChannel.id
- };
+ return entry is Group
+ ? _serializeGroup(channel, entry)
+ : _serializeTest(channel, entry);
}).toList()
};
}
+ /// Serializes [test] into a JSON-safe map.
+ ///
+ /// Returns `null` if [test] is `null`.
+ Map _serializeTest(MultiChannel channel, Test test) {
+ if (test == null) return null;
+
+ var testChannel = channel.virtualChannel();
+ testChannel.stream.listen((message) {
+ assert(message['command'] == 'run');
+ _runTest(test, channel.virtualChannel(message['channel']));
+ });
+
+ return {
+ "type": "test",
+ "name": test.name,
+ "metadata": test.metadata.serialize(),
+ "channel": testChannel.id
+ };
+ }
+
/// Runs [test] and sends the results across [channel].
void _runTest(Test test, MultiChannel channel) {
var liveTest = test.load(_suite);
diff --git a/lib/src/runner/engine.dart b/lib/src/runner/engine.dart
index 6bd51bf..ed88544 100644
--- a/lib/src/runner/engine.dart
+++ b/lib/src/runner/engine.dart
@@ -215,18 +215,35 @@
return;
}
- for (var entry in group.entries) {
- if (_closed) return;
+ var setUpAllSucceeded = true;
+ if (group.setUpAll != null) {
+ var liveTest = group.setUpAll.load(suite);
+ await _runLiveTest(liveTest, countSuccess: false);
+ setUpAllSucceeded = liveTest.state.result == Result.success;
+ }
- if (entry is Group) {
- await _runGroup(suite, entry);
- } else if (entry.metadata.skip) {
- await _runLiveTest(_skippedTest(suite, entry));
- } else {
- var test = entry as Test;
- await _runLiveTest(test.load(suite));
+ if (!_closed && setUpAllSucceeded) {
+ for (var entry in group.entries) {
+ if (_closed) return;
+
+ if (entry is Group) {
+ await _runGroup(suite, entry);
+ } else if (entry.metadata.skip) {
+ await _runLiveTest(_skippedTest(suite, entry));
+ } else {
+ var test = entry as Test;
+ await _runLiveTest(test.load(suite));
+ }
}
}
+
+ // Even if we're closed or setUpAll failed, we want to run all the teardowns
+ // to ensure that any state is properly cleaned up.
+ if (group.tearDownAll != null) {
+ var liveTest = group.tearDownAll.load(suite);
+ await _runLiveTest(liveTest, countSuccess: false);
+ if (_closed) await liveTest.close();
+ }
}
/// Returns a dummy [LiveTest] for a test or group marked as "skip".
@@ -244,7 +261,10 @@
}
/// Runs [liveTest].
- Future _runLiveTest(LiveTest liveTest) async {
+ ///
+ /// If [countSuccess] is `true` (the default), the test is put into [passed]
+ /// if it succeeds. Otherwise, it's removed from [liveTests] entirely.
+ Future _runLiveTest(LiveTest liveTest, {bool countSuccess: true}) async {
_liveTests.add(liveTest);
_active.add(liveTest);
@@ -270,8 +290,10 @@
_failed.add(liveTest);
} else if (liveTest.test.metadata.skip) {
_skipped.add(liveTest);
- } else {
+ } else if (countSuccess) {
_passed.add(liveTest);
+ } else {
+ _liveTests.remove(liveTest);
}
});
@@ -342,15 +364,19 @@
/// the engine indicates that no more output should be emitted.
Future close() async {
_closed = true;
- if (_closedBeforeDone == null) _closedBeforeDone = true;
+ if (_closedBeforeDone != null) _closedBeforeDone = true;
_suiteController.close();
// Close the running tests first so that we're sure to wait for them to
// finish before we close their suites and cause them to become unloaded.
var allLiveTests = liveTests.toSet()..addAll(_activeLoadTests);
- await Future.wait(allLiveTests.map((liveTest) => liveTest.close()));
+ var futures = allLiveTests.map((liveTest) => liveTest.close()).toList();
- var allSuites = allLiveTests.map((liveTest) => liveTest.suite).toSet();
- await Future.wait(allSuites.map((suite) => suite.close()));
+ // Closing the load pool will close the test suites as soon as their tests
+ // are done. For browser suites this is effectively immediate since their
+ // tests shut down as soon as they're closed, but for VM suites we may need
+ // to wait for tearDowns or tearDownAlls to run.
+ futures.add(_loadPool.close());
+ await Future.wait(futures, eagerError: true);
}
}
diff --git a/lib/src/runner/loader.dart b/lib/src/runner/loader.dart
index 1585078..62fb46b 100644
--- a/lib/src/runner/loader.dart
+++ b/lib/src/runner/loader.dart
@@ -15,6 +15,7 @@
import '../backend/group.dart';
import '../backend/metadata.dart';
+import '../backend/test.dart';
import '../backend/test_platform.dart';
import '../util/dart.dart' as dart;
import '../util/io.dart';
@@ -244,9 +245,21 @@
var metadata = new Metadata.deserialize(group['metadata']);
return new Group(group['name'], group['entries'].map((entry) {
if (entry['type'] == 'group') return _deserializeGroup(entry);
- var testMetadata = new Metadata.deserialize(entry['metadata']);
- return new IsolateTest(entry['name'], testMetadata, entry['sendPort']);
- }), metadata: metadata);
+ return _deserializeTest(entry);
+ }),
+ metadata: metadata,
+ setUpAll: _deserializeTest(group['setUpAll']),
+ tearDownAll: _deserializeTest(group['tearDownAll']));
+ }
+
+ /// Deserializes [test] into a concrete [Test] class.
+ ///
+ /// Returns `null` if [test] is `null`.
+ Test _deserializeTest(Map test) {
+ if (test == null) return null;
+
+ var metadata = new Metadata.deserialize(test['metadata']);
+ return new IsolateTest(test['name'], metadata, test['sendPort']);
}
/// Closes the loader and releases all resources allocated by it.
diff --git a/lib/src/runner/vm/isolate_listener.dart b/lib/src/runner/vm/isolate_listener.dart
index 493ade4..23f7b73 100644
--- a/lib/src/runner/vm/isolate_listener.dart
+++ b/lib/src/runner/vm/isolate_listener.dart
@@ -117,26 +117,34 @@
"type": "group",
"name": group.name,
"metadata": group.metadata.serialize(),
+ "setUpAll": _serializeTest(group.setUpAll),
+ "tearDownAll": _serializeTest(group.tearDownAll),
"entries": group.entries.map((entry) {
- if (entry is Group) return _serializeGroup(entry);
-
- var test = entry as Test;
- var receivePort = new ReceivePort();
- receivePort.listen((message) {
- assert(message['command'] == 'run');
- _runTest(test, message['reply']);
- });
-
- return {
- "type": "test",
- "name": test.name,
- "metadata": test.metadata.serialize(),
- "sendPort": receivePort.sendPort
- };
+ return entry is Group ? _serializeGroup(entry) : _serializeTest(entry);
}).toList()
};
}
+ /// Serializes [test] into a JSON-safe map.
+ ///
+ /// Returns `null` if [test] is `null`.
+ Map _serializeTest(Test test) {
+ if (test == null) return null;
+
+ var receivePort = new ReceivePort();
+ receivePort.listen((message) {
+ assert(message['command'] == 'run');
+ _runTest(test, message['reply']);
+ });
+
+ return {
+ "type": "test",
+ "name": test.name,
+ "metadata": test.metadata.serialize(),
+ "sendPort": receivePort.sendPort
+ };
+ }
+
/// Runs [test] and sends the results across [sendPort].
void _runTest(Test test, SendPort sendPort) {
var liveTest = test.load(_suite);
diff --git a/lib/test.dart b/lib/test.dart
index e662d1e..48235ff 100644
--- a/lib/test.dart
+++ b/lib/test.dart
@@ -187,6 +187,34 @@
/// reverse of the order they were declared.
void tearDown(callback()) => _declarer.tearDown(callback);
+/// Registers a function to be run once before all tests.
+///
+/// [callback] may be asynchronous; if so, it must return a [Future].
+///
+/// If this is called within a test group, [callback] will run before all tests
+/// in that group. It will be run after any [setUpAll] callbacks in parent
+/// groups or at the top level. It won't be run if none of the tests in the
+/// group are run.
+///
+/// **Note**: This function makes it very easy to accidentally introduce hidden
+/// dependencies between tests that should be isolated. In general, you should
+/// prefer [setUp], and only use [setUpAll] if the callback is prohibitively
+/// slow.
+void setUpAll(callback()) => _declarer.setUpAll(callback);
+
+/// Registers a function to be run once after all tests.
+///
+/// If this is called within a test group, [callback] will run after all tests
+/// in that group. It will be run before any [tearDownAll] callbacks in parent
+/// groups or at the top level. It won't be run if none of the tests in the
+/// group are run.
+///
+/// **Note**: This function makes it very easy to accidentally introduce hidden
+/// dependencies between tests that should be isolated. In general, you should
+/// prefer [tearDown], and only use [tearDOwnAll] if the callback is
+/// prohibitively slow.
+void tearDownAll(callback()) => _declarer.tearDownAll(callback);
+
/// Registers an exception that was caught for the current test.
void registerException(error, [StackTrace stackTrace]) {
// This will usually forward directly to [Invoker.current.handleError], but
diff --git a/pubspec.yaml b/pubspec.yaml
index 89683f2..61854b5 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: test
-version: 0.12.4+9
+version: 0.12.5
author: Dart Team <misc@dartlang.org>
description: A library for writing dart unit tests.
homepage: https://github.com/dart-lang/test
@@ -16,7 +16,7 @@
http_multi_server: '^1.0.0'
http_parser: '>=0.0.2 <2.0.0'
path: '^1.2.0'
- pool: '^1.1.0'
+ pool: '^1.2.0'
pub_semver: '^1.0.0'
shelf: '>=0.6.0 <0.7.0'
shelf_static: '^0.2.0'
diff --git a/test/frontend/set_up_all_test.dart b/test/frontend/set_up_all_test.dart
new file mode 100644
index 0000000..7cfc274
--- /dev/null
+++ b/test/frontend/set_up_all_test.dart
@@ -0,0 +1,324 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import '../utils.dart';
+
+void main() {
+ test("runs once before all tests", () {
+ return expectTestsPass(() {
+ var setUpAllRun = false;
+ setUpAll(() {
+ expect(setUpAllRun, isFalse);
+ setUpAllRun = true;
+ });
+
+ test("test 1", () {
+ expect(setUpAllRun, isTrue);
+ });
+
+ test("test 2", () {
+ expect(setUpAllRun, isTrue);
+ });
+ });
+ });
+
+ test("runs once per group, outside-in", () {
+ return expectTestsPass(() {
+ var setUpAll1Run = false;
+ var setUpAll2Run = false;
+ var setUpAll3Run = false;
+ setUpAll(() {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ setUpAll1Run = true;
+ });
+
+ group("mid", () {
+ setUpAll(() {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ setUpAll2Run = true;
+ });
+
+ group("inner", () {
+ setUpAll(() {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isFalse);
+ setUpAll3Run = true;
+ });
+
+ test("test", () {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isTrue);
+ });
+ });
+ });
+ });
+ });
+
+ test("runs before setUps", () {
+ return expectTestsPass(() {
+ var setUpAllRun = false;
+ setUp(() {
+ expect(setUpAllRun, isTrue);
+ });
+
+ setUpAll(() {
+ expect(setUpAllRun, isFalse);
+ setUpAllRun = true;
+ });
+
+ setUp(() {
+ expect(setUpAllRun, isTrue);
+ });
+
+ test("test", () {
+ expect(setUpAllRun, isTrue);
+ });
+ });
+ });
+
+ test("multiples run in order", () {
+ return expectTestsPass(() {
+ var setUpAll1Run = false;
+ var setUpAll2Run = false;
+ var setUpAll3Run = false;
+ setUpAll(() {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ setUpAll1Run = true;
+ });
+
+ setUpAll(() {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ setUpAll2Run = true;
+ });
+
+ setUpAll(() {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isFalse);
+ setUpAll3Run = true;
+ });
+
+ test("test", () {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isTrue);
+ });
+ });
+ });
+
+ group("asynchronously", () {
+ test("blocks additional setUpAlls on in-band async", () {
+ return expectTestsPass(() {
+ var setUpAll1Run = false;
+ var setUpAll2Run = false;
+ var setUpAll3Run = false;
+ setUpAll(() async {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ await pumpEventQueue();
+ setUpAll1Run = true;
+ });
+
+ setUpAll(() async {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+ await pumpEventQueue();
+ setUpAll2Run = true;
+ });
+
+ setUpAll(() async {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isFalse);
+ await pumpEventQueue();
+ setUpAll3Run = true;
+ });
+
+ test("test", () {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isTrue);
+ });
+ });
+ });
+
+ test("doesn't block additional setUpAlls on out-of-band async", () {
+ return expectTestsPass(() {
+ var setUpAll1Run = false;
+ var setUpAll2Run = false;
+ var setUpAll3Run = false;
+ setUpAll(() {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+
+ expect(pumpEventQueue().then((_) {
+ setUpAll1Run = true;
+ }), completes);
+ });
+
+ setUpAll(() {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+
+ expect(pumpEventQueue().then((_) {
+ setUpAll2Run = true;
+ }), completes);
+ });
+
+ setUpAll(() {
+ expect(setUpAll1Run, isFalse);
+ expect(setUpAll2Run, isFalse);
+ expect(setUpAll3Run, isFalse);
+
+ expect(pumpEventQueue().then((_) {
+ setUpAll3Run = true;
+ }), completes);
+ });
+
+ test("test", () {
+ expect(setUpAll1Run, isTrue);
+ expect(setUpAll2Run, isTrue);
+ expect(setUpAll3Run, isTrue);
+ });
+ });
+ });
+ });
+
+ test("isn't run for a skipped group", () async {
+ // Declare this in the outer test so if it runs, the outer test will fail.
+ var shouldNotRun = expectAsync(() {}, count: 0);
+
+ var engine = declareEngine(() {
+ group("skipped", () {
+ setUpAll(shouldNotRun);
+
+ test("test", () {});
+ }, skip: true);
+ });
+
+ await engine.run();
+ expect(engine.liveTests, hasLength(1));
+ expect(engine.skipped, hasLength(1));
+ expect(engine.liveTests, equals(engine.skipped));
+ });
+
+ test("is emitted through Engine.onTestStarted", () async {
+ var engine = declareEngine(() {
+ setUpAll(() {});
+
+ test("test", () {});
+ });
+
+ var queue = new StreamQueue(engine.onTestStarted);
+ var setUpAllFuture = queue.next;
+ var liveTestFuture = queue.next;
+
+ await engine.run();
+
+ var setUpAllLiveTest = await setUpAllFuture;
+ expect(setUpAllLiveTest.test.name, equals("(setUpAll)"));
+ expectTestPassed(setUpAllLiveTest);
+
+ // The fake test for setUpAll should be removed from the engine's live
+ // test list so that reporters don't display it as a passed test.
+ expect(engine.liveTests, isNot(contains(setUpAllLiveTest)));
+ expect(engine.passed, isNot(contains(setUpAllLiveTest)));
+ expect(engine.failed, isNot(contains(setUpAllLiveTest)));
+ expect(engine.skipped, isNot(contains(setUpAllLiveTest)));
+ expect(engine.active, isNot(contains(setUpAllLiveTest)));
+
+ var liveTest = await liveTestFuture;
+ expectTestPassed(await liveTestFuture);
+ expect(engine.liveTests, contains(liveTest));
+ expect(engine.passed, contains(liveTest));
+ });
+
+ group("with an error", () {
+ test("reports the error and remains in Engine.liveTests", () async {
+ var engine = declareEngine(() {
+ setUpAll(() => throw new TestFailure("fail"));
+
+ test("test", () {});
+ });
+
+ var queue = new StreamQueue(engine.onTestStarted);
+ var setUpAllFuture = queue.next;
+
+ expect(await engine.run(), isFalse);
+
+ var setUpAllLiveTest = await setUpAllFuture;
+ expect(setUpAllLiveTest.test.name, equals("(setUpAll)"));
+ expectTestFailed(setUpAllLiveTest, "fail");
+
+ // The fake test for setUpAll should be removed from the engine's live
+ // test list so that reporters don't display it as a passed test.
+ expect(engine.liveTests, contains(setUpAllLiveTest));
+ expect(engine.failed, contains(setUpAllLiveTest));
+ expect(engine.passed, isNot(contains(setUpAllLiveTest)));
+ expect(engine.skipped, isNot(contains(setUpAllLiveTest)));
+ expect(engine.active, isNot(contains(setUpAllLiveTest)));
+ });
+
+ test("doesn't run tests in the group", () async {
+ // Declare this in the outer test so if it runs, the outer test will fail.
+ var shouldNotRun = expectAsync(() {}, count: 0);
+
+ var engine = declareEngine(() {
+ setUpAll(() => throw "error");
+
+ test("test", shouldNotRun);
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+
+ test("doesn't run inner groups", () async {
+ // Declare this in the outer test so if it runs, the outer test will fail.
+ var shouldNotRun = expectAsync(() {}, count: 0);
+
+ var engine = declareEngine(() {
+ setUpAll(() => throw "error");
+
+ group("group", () {
+ test("test", shouldNotRun);
+ });
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+
+ test("doesn't run further setUpAlls", () async {
+ // Declare this in the outer test so if it runs, the outer test will fail.
+ var shouldNotRun = expectAsync(() {}, count: 0);
+
+ var engine = declareEngine(() {
+ setUpAll(() => throw "error");
+ setUpAll(shouldNotRun);
+
+ test("test", shouldNotRun);
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+ });
+}
diff --git a/test/frontend/tear_down_all_test.dart b/test/frontend/tear_down_all_test.dart
new file mode 100644
index 0000000..a5d9e0b
--- /dev/null
+++ b/test/frontend/tear_down_all_test.dart
@@ -0,0 +1,372 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+import 'package:test/test.dart';
+
+import '../utils.dart';
+
+void main() {
+ test("runs once after all tests", () {
+ return expectTestsPass(() {
+ var test1Run = false;
+ var test2Run = false;
+ var tearDownAllRun = false;
+ tearDownAll(() {
+ expect(test1Run, isTrue);
+ expect(test2Run, isTrue);
+ expect(tearDownAllRun, isFalse);
+ tearDownAllRun = true;
+ });
+
+ test("test 1", () {
+ expect(tearDownAllRun, isFalse);
+ test1Run = true;
+ });
+
+ test("test 2", () {
+ expect(tearDownAllRun, isFalse);
+ test2Run = true;
+ });
+ });
+ });
+
+ test("runs once per group, inside-out", () {
+ return expectTestsPass(() {
+ var tearDownAll1Run = false;
+ var tearDownAll2Run = false;
+ var tearDownAll3Run = false;
+ var testRun = false;
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isTrue);
+ expect(tearDownAll3Run, isTrue);
+ expect(testRun, isTrue);
+ tearDownAll1Run = true;
+ });
+
+ group("mid", () {
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isTrue);
+ expect(testRun, isTrue);
+ tearDownAll2Run = true;
+ });
+
+ group("inner", () {
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ expect(testRun, isTrue);
+ tearDownAll3Run = true;
+ });
+
+ test("test", () {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ testRun = true;
+ });
+ });
+ });
+ });
+ });
+
+ test("runs after tearDowns", () {
+ return expectTestsPass(() {
+ var tearDown1Run = false;
+ var tearDown2Run = false;
+ var tearDownAllRun = false;
+ tearDown(() {
+ expect(tearDownAllRun, isFalse);
+ tearDown1Run = true;
+ });
+
+ tearDownAll(() {
+ expect(tearDown1Run, isTrue);
+ expect(tearDown2Run, isTrue);
+ expect(tearDownAllRun, isFalse);
+ tearDownAllRun = true;
+ });
+
+ tearDown(() {
+ expect(tearDownAllRun, isFalse);
+ tearDown2Run = true;
+ });
+
+ test("test", () {
+ expect(tearDownAllRun, isFalse);
+ });
+ });
+ });
+
+ test("multiples run in reverse order", () {
+ return expectTestsPass(() {
+ var tearDownAll1Run = false;
+ var tearDownAll2Run = false;
+ var tearDownAll3Run = false;
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isTrue);
+ expect(tearDownAll3Run, isTrue);
+ tearDownAll1Run = true;
+ });
+
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isTrue);
+ tearDownAll2Run = true;
+ });
+
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ tearDownAll3Run = true;
+ });
+
+ test("test", () {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ });
+ });
+ });
+
+ group("asynchronously", () {
+ test("blocks additional tearDownAlls on in-band async", () {
+ return expectTestsPass(() {
+ var tearDownAll1Run = false;
+ var tearDownAll2Run = false;
+ var tearDownAll3Run = false;
+ tearDownAll(() async {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isTrue);
+ expect(tearDownAll3Run, isTrue);
+ await pumpEventQueue();
+ tearDownAll1Run = true;
+ });
+
+ tearDownAll(() async {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isTrue);
+ await pumpEventQueue();
+ tearDownAll2Run = true;
+ });
+
+ tearDownAll(() async {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ await pumpEventQueue();
+ tearDownAll3Run = true;
+ });
+
+ test("test", () {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ });
+ });
+ });
+
+ test("doesn't block additional tearDownAlls on out-of-band async", () {
+ return expectTestsPass(() {
+ var tearDownAll1Run = false;
+ var tearDownAll2Run = false;
+ var tearDownAll3Run = false;
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+
+ expect(new Future(() {
+ tearDownAll1Run = true;
+ }), completes);
+ });
+
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+
+ expect(new Future(() {
+ tearDownAll2Run = true;
+ }), completes);
+ });
+
+ tearDownAll(() {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+
+ expect(new Future(() {
+ tearDownAll3Run = true;
+ }), completes);
+ });
+
+ test("test", () {
+ expect(tearDownAll1Run, isFalse);
+ expect(tearDownAll2Run, isFalse);
+ expect(tearDownAll3Run, isFalse);
+ });
+ });
+ });
+
+ test("blocks further tests on in-band async", () {
+ return expectTestsPass(() {
+ var tearDownAllRun = false;
+ group("group", () {
+ tearDownAll(() async {
+ expect(tearDownAllRun, isFalse);
+ await pumpEventQueue();
+ tearDownAllRun = true;
+ });
+
+ test("test", () {});
+ });
+
+ test("after", () {
+ expect(tearDownAllRun, isTrue);
+ });
+ });
+ });
+
+ test("blocks further tests on out-of-band async", () {
+ return expectTestsPass(() {
+ var tearDownAllRun = false;
+ group("group", () {
+ tearDownAll(() async {
+ expect(tearDownAllRun, isFalse);
+ expect(pumpEventQueue().then((_) {
+ tearDownAllRun = true;
+ }), completes);
+ });
+
+ test("test", () {});
+ });
+
+ test("after", () {
+ expect(tearDownAllRun, isTrue);
+ });
+ });
+ });
+ });
+
+ test("isn't run for a skipped group", () async {
+ // Declare this in the outer test so if it runs, the outer test will fail.
+ var shouldNotRun = expectAsync(() {}, count: 0);
+
+ var engine = declareEngine(() {
+ group("skipped", () {
+ tearDownAll(shouldNotRun);
+
+ test("test", () {});
+ }, skip: true);
+ });
+
+ await engine.run();
+ expect(engine.liveTests, hasLength(1));
+ expect(engine.skipped, hasLength(1));
+ expect(engine.liveTests, equals(engine.skipped));
+ });
+
+ test("is emitted through Engine.onTestStarted", () async {
+ var engine = declareEngine(() {
+ tearDownAll(() {});
+
+ test("test", () {});
+ });
+
+ var queue = new StreamQueue(engine.onTestStarted);
+ var liveTestFuture = queue.next;
+ var tearDownAllFuture = queue.next;
+
+ await engine.run();
+
+ var tearDownAllLiveTest = await tearDownAllFuture;
+ expect(tearDownAllLiveTest.test.name, equals("(tearDownAll)"));
+ expectTestPassed(tearDownAllLiveTest);
+
+ // The fake test for tearDownAll should be removed from the engine's live
+ // test list so that reporters don't display it as a passed test.
+ expect(engine.liveTests, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.passed, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.failed, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.skipped, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.active, isNot(contains(tearDownAllLiveTest)));
+
+ var liveTest = await liveTestFuture;
+ expectTestPassed(await liveTestFuture);
+ expect(engine.liveTests, contains(liveTest));
+ expect(engine.passed, contains(liveTest));
+ });
+
+ group("with an error", () {
+ test("reports the error and remains in Engine.liveTests", () async {
+ var engine = declareEngine(() {
+ tearDownAll(() => throw new TestFailure("fail"));
+
+ test("test", () {});
+ });
+
+ var queue = new StreamQueue(engine.onTestStarted);
+ expect(queue.next, completes);
+ var tearDownAllFuture = queue.next;
+
+ expect(await engine.run(), isFalse);
+
+ var tearDownAllLiveTest = await tearDownAllFuture;
+ expect(tearDownAllLiveTest.test.name, equals("(tearDownAll)"));
+ expectTestFailed(tearDownAllLiveTest, "fail");
+
+ // The fake test for tearDownAll should be removed from the engine's live
+ // test list so that reporters don't display it as a passed test.
+ expect(engine.liveTests, contains(tearDownAllLiveTest));
+ expect(engine.failed, contains(tearDownAllLiveTest));
+ expect(engine.passed, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.skipped, isNot(contains(tearDownAllLiveTest)));
+ expect(engine.active, isNot(contains(tearDownAllLiveTest)));
+ });
+
+ test("runs further tearDownAlls", () async {
+ // Declare this in the outer test so if it doesn't runs, the outer test
+ // will fail.
+ var shouldRun = expectAsync(() {});
+
+ var engine = declareEngine(() {
+ tearDownAll(() => throw "error");
+ tearDownAll(shouldRun);
+
+ test("test", () {});
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+
+ test("runs outer tearDownAlls", () async {
+ // Declare this in the outer test so if it doesn't runs, the outer test
+ // will fail.
+ var shouldRun = expectAsync(() {});
+
+ var engine = declareEngine(() {
+ tearDownAll(shouldRun);
+
+ group("group", () {
+ tearDownAll(() => throw "error");
+
+ test("test", () {});
+ });
+ });
+
+ expect(await engine.run(), isFalse);
+ });
+ });
+}
diff --git a/test/runner/browser/runner_test.dart b/test/runner/browser/runner_test.dart
index 14ad58d..dcd18c6 100644
--- a/test/runner/browser/runner_test.dart
+++ b/test/runner/browser/runner_test.dart
@@ -211,6 +211,40 @@
test.shouldExit(0);
});
+ test("with setUpAll", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ setUpAll(() => print("in setUpAll"));
+
+ test("test", () {});
+ }
+ """).create();
+
+ var test = runTest(["-p", "content-shell", "test.dart"]);
+ test.stdout.expect(consumeThrough(contains('+0: (setUpAll)')));
+ test.stdout.expect('in setUpAll');
+ test.shouldExit(0);
+ });
+
+ test("with tearDownAll", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ tearDownAll(() => print("in tearDownAll"));
+
+ test("test", () {});
+ }
+ """).create();
+
+ var test = runTest(["-p", "content-shell", "test.dart"]);
+ test.stdout.expect(consumeThrough(contains('+1: (tearDownAll)')));
+ test.stdout.expect('in tearDownAll');
+ test.shouldExit(0);
+ });
+
// Regression test; this broke in 0.12.0-beta.9.
test("on a file in a subdirectory", () {
d.dir("dir", [d.file("test.dart", _success)]).create();
diff --git a/test/runner/engine_test.dart b/test/runner/engine_test.dart
index bb6a7db..37d1213 100644
--- a/test/runner/engine_test.dart
+++ b/test/runner/engine_test.dart
@@ -57,11 +57,11 @@
test("emits each test before it starts running and after the previous test "
"finished", () {
var testsRun = 0;
- var engine = withTests(declare(() {
+ var engine = declareEngine(() {
for (var i = 0; i < 3; i++) {
test("test ${i + 1}", expectAsync(() => testsRun++, max: 1));
}
- }));
+ });
engine.onTestStarted.listen(expectAsync((liveTest) {
// [testsRun] should be one less than the test currently running.
@@ -77,33 +77,33 @@
});
test(".run() returns true if every test passes", () {
- var engine = withTests(declare(() {
+ var engine = declareEngine(() {
for (var i = 0; i < 2; i++) {
test("test ${i + 1}", () {});
}
- }));
+ });
expect(engine.run(), completion(isTrue));
});
test(".run() returns false if any test fails", () {
- var engine = withTests(declare(() {
+ var engine = declareEngine(() {
for (var i = 0; i < 2; i++) {
test("test ${i + 1}", () {});
}
test("failure", () => throw new TestFailure("oh no"));
- }));
+ });
expect(engine.run(), completion(isFalse));
});
test(".run() returns false if any test errors", () {
- var engine = withTests(declare(() {
+ var engine = declareEngine(() {
for (var i = 0; i < 2; i++) {
test("test ${i + 1}", () {});
}
test("failure", () => throw "oh no");
- }));
+ });
expect(engine.run(), completion(isFalse));
});
@@ -117,9 +117,9 @@
group("for a skipped test", () {
test("doesn't run the test's body", () async {
var bodyRun = false;
- var engine = withTests(declare(() {
+ var engine = declareEngine(() {
test("test", () => bodyRun = true, skip: true);
- }));
+ });
await engine.run();
expect(bodyRun, isFalse);
@@ -130,7 +130,9 @@
test("test", () {}, skip: true);
});
- var engine = withTests(tests);
+ var engine = new Engine.withSuites([
+ new RunnerSuite(const VMEnvironment(), new Group.root(tests))
+ ]);
engine.onTestStarted.listen(expectAsync((liveTest) {
expect(liveTest, same(engine.liveTests.single));
@@ -151,10 +153,3 @@
});
});
}
-
-/// Returns an engine that will run [tests].
-Engine withTests(List<Test> tests) {
- return new Engine.withSuites([
- new RunnerSuite(const VMEnvironment(), new Group.root(tests))
- ]);
-}
diff --git a/test/runner/set_up_all_test.dart b/test/runner/set_up_all_test.dart
new file mode 100644
index 0000000..2301eb4
--- /dev/null
+++ b/test/runner/set_up_all_test.dart
@@ -0,0 +1,120 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@TestOn("vm")
+
+import 'package:scheduled_test/descriptor.dart' as d;
+import 'package:scheduled_test/scheduled_stream.dart';
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../io.dart';
+
+void main() {
+ useSandbox();
+
+ test("an error causes the run to fail", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ setUpAll(() => throw "oh no");
+
+ test("test", () {});
+ }
+ """).create();
+
+ var test = runTest(["test.dart"]);
+ test.stdout.expect(consumeThrough(contains("-1: (setUpAll)")));
+ test.stdout.expect(consumeThrough(contains("-1: Some tests failed.")));
+ test.shouldExit(1);
+ });
+
+ test("doesn't run if no tests in the group are selected", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ group("with setUpAll", () {
+ setUpAll(() => throw "oh no");
+
+ test("test", () {});
+ });
+
+ group("without setUpAll", () {
+ test("test", () {});
+ });
+ }
+ """).create();
+
+ var test = runTest(["test.dart", "--name", "without"]);
+ test.stdout.expect(never(contains("(setUpAll)")));
+ test.shouldExit(0);
+ });
+
+ test("doesn't run if no tests in the group are selected", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group", () {
+ setUpAll(() => throw "oh no");
+
+ test("with", () {});
+ });
+
+ group("group", () {
+ test("without", () {});
+ });
+ }
+ """).create();
+
+ var test = runTest(["test.dart", "--name", "without"]);
+ test.stdout.expect(never(contains("(setUpAll)")));
+ test.shouldExit(0);
+ });
+
+ test("doesn't run if no tests in the group match the platform", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group", () {
+ setUpAll(() => throw "oh no");
+
+ test("with", () {}, testOn: "browser");
+ });
+
+ group("group", () {
+ test("without", () {});
+ });
+ }
+ """).create();
+
+ var test = runTest(["test.dart"]);
+ test.stdout.expect(never(contains("(setUpAll)")));
+ test.shouldExit(0);
+ });
+
+ test("doesn't run if the group doesn't match the platform", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group", () {
+ setUpAll(() => throw "oh no");
+
+ test("with", () {});
+ }, testOn: "browser");
+
+ group("group", () {
+ test("without", () {});
+ });
+ }
+ """).create();
+
+ var test = runTest(["test.dart"]);
+ test.stdout.expect(never(contains("(setUpAll)")));
+ test.shouldExit(0);
+ });
+}
diff --git a/test/runner/signal_test.dart b/test/runner/signal_test.dart
index 5397d07..63e8e7b 100644
--- a/test/runner/signal_test.dart
+++ b/test/runner/signal_test.dart
@@ -82,7 +82,11 @@
import 'package:test/test.dart';
void main() {
- tearDown(() => new File("output").writeAsStringSync("ran teardown"));
+ tearDownAll(() {
+ new File("output_all").writeAsStringSync("ran tearDownAll");
+ });
+
+ tearDown(() => new File("output").writeAsStringSync("ran tearDown"));
test("test", () {
print("running test");
@@ -95,7 +99,34 @@
test.stdout.expect(consumeThrough("running test"));
signalAndQuit(test);
- d.file("output", "ran teardown").validate();
+ d.file("output", "ran tearDown").validate();
+ d.file("output_all", "ran tearDownAll").validate();
+ expectTempDirEmpty();
+ });
+
+ test("waits for an active tearDownAll to finish running", () {
+ d.file("test.dart", """
+import 'dart:async';
+import 'dart:io';
+
+import 'package:test/test.dart';
+
+void main() {
+ tearDownAll(() async {
+ print("running tearDownAll");
+ await new Future.delayed(new Duration(seconds: 1));
+ new File("output").writeAsStringSync("ran tearDownAll");
+ });
+
+ test("test", () {});
+}
+""").create();
+
+ var test = _runTest(["test.dart"]);
+ test.stdout.expect(consumeThrough("running tearDownAll"));
+ signalAndQuit(test);
+
+ d.file("output", "ran tearDownAll").validate();
expectTempDirEmpty();
});
diff --git a/test/runner/tear_down_all_test.dart b/test/runner/tear_down_all_test.dart
new file mode 100644
index 0000000..9b192c3
--- /dev/null
+++ b/test/runner/tear_down_all_test.dart
@@ -0,0 +1,120 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+@TestOn("vm")
+
+import 'package:scheduled_test/descriptor.dart' as d;
+import 'package:scheduled_test/scheduled_stream.dart';
+import 'package:scheduled_test/scheduled_test.dart';
+
+import '../io.dart';
+
+void main() {
+ useSandbox();
+
+ test("an error causes the run to fail", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ tearDownAll(() => throw "oh no");
+
+ test("test", () {});
+ }
+ """).create();
+
+ var test = runTest(["test.dart"]);
+ test.stdout.expect(consumeThrough(contains("-1: (tearDownAll)")));
+ test.stdout.expect(consumeThrough(contains("-1: Some tests failed.")));
+ test.shouldExit(1);
+ });
+
+ test("doesn't run if no tests in the group are selected", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ group("with tearDownAll", () {
+ tearDownAll(() => throw "oh no");
+
+ test("test", () {});
+ });
+
+ group("without tearDownAll", () {
+ test("test", () {});
+ });
+ }
+ """).create();
+
+ var test = runTest(["test.dart", "--name", "without"]);
+ test.stdout.expect(never(contains("(tearDownAll)")));
+ test.shouldExit(0);
+ });
+
+ test("doesn't run if no tests in the group are selected", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group", () {
+ tearDownAll(() => throw "oh no");
+
+ test("with", () {});
+ });
+
+ group("group", () {
+ test("without", () {});
+ });
+ }
+ """).create();
+
+ var test = runTest(["test.dart", "--name", "without"]);
+ test.stdout.expect(never(contains("(tearDownAll)")));
+ test.shouldExit(0);
+ });
+
+ test("doesn't run if no tests in the group match the platform", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group", () {
+ tearDownAll(() => throw "oh no");
+
+ test("with", () {}, testOn: "browser");
+ });
+
+ group("group", () {
+ test("without", () {});
+ });
+ }
+ """).create();
+
+ var test = runTest(["test.dart"]);
+ test.stdout.expect(never(contains("(tearDownAll)")));
+ test.shouldExit(0);
+ });
+
+ test("doesn't run if the group doesn't match the platform", () {
+ d.file("test.dart", r"""
+ import 'package:test/test.dart';
+
+ void main() {
+ group("group", () {
+ tearDownAll(() => throw "oh no");
+
+ test("with", () {});
+ }, testOn: "browser");
+
+ group("group", () {
+ test("without", () {});
+ });
+ }
+ """).create();
+
+ var test = runTest(["test.dart"]);
+ test.stdout.expect(never(contains("(tearDownAll)")));
+ test.shouldExit(0);
+ });
+}
diff --git a/test/utils.dart b/test/utils.dart
index acbec12..9662716 100644
--- a/test/utils.dart
+++ b/test/utils.dart
@@ -16,7 +16,10 @@
import 'package:test/src/backend/state.dart';
import 'package:test/src/backend/suite.dart';
import 'package:test/src/runner/application_exception.dart';
+import 'package:test/src/runner/engine.dart';
import 'package:test/src/runner/load_exception.dart';
+import 'package:test/src/runner/runner_suite.dart';
+import 'package:test/src/runner/vm/environment.dart';
import 'package:test/src/util/remote_exception.dart';
import 'package:test/test.dart';
@@ -284,8 +287,29 @@
return future;
}
+/// Runs [body] with a declarer, runs all the declared tests, and asserts that
+/// they pass.
+Future expectTestsPass(void body()) async {
+ var engine = declareEngine(body);
+ var success = await engine.run();
+
+ for (var test in engine.liveTests) {
+ expectTestPassed(test);
+ }
+
+ expect(success, isTrue);
+}
+
/// Runs [body] with a declarer and returns the declared entries.
List<GroupEntry> declare(void body()) {
var declarer = new Declarer()..declare(body);
return declarer.build().entries;
}
+
+/// Runs [body] with a declarer and returns an engine that runs those tests.
+Engine declareEngine(void body()) {
+ var declarer = new Declarer()..declare(body);
+ return new Engine.withSuites([
+ new RunnerSuite(const VMEnvironment(), declarer.build())
+ ]);
+}