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()
       ]);
     });