Run outer tearDown()s even if inner ones fail.

R=kevmoo@google.com

Review URL: https://codereview.chromium.org//1364893004 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 04d6eea..f46c883 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.12.4+9
+
+* If a `tearDown()` callback throws an error, outer `tearDown()` callbacks are
+  still executed.
+
 ## 0.12.4+8
 
 * Don't compile tests to JavaScript when running via `pub serve` on Dartium or
diff --git a/lib/src/backend/group.dart b/lib/src/backend/group.dart
index 69144c6..d1bc67a 100644
--- a/lib/src/backend/group.dart
+++ b/lib/src/backend/group.dart
@@ -7,6 +7,7 @@
 import 'dart:async';
 
 import '../utils.dart';
+import 'invoker.dart';
 import 'metadata.dart';
 
 /// A group contains multiple tests and subgroups.
@@ -76,13 +77,25 @@
   Future runTearDown() {
     // TODO(nweiz): Use async/await here once issue 23497 has been fixed in two
     // stable versions.
-    if (parent != null) {
-      return new Future.sync(() {
-        if (tearDown != null) return tearDown();
-      }).then((_) => parent.runTearDown());
+    if (parent == null) {
+      return tearDown == null ? new Future.value() : new Future.sync(tearDown);
     }
 
-    if (tearDown != null) return new Future.sync(tearDown);
-    return new Future.value();
+    return _errorsDontStopTest(() {
+      if (tearDown != null) return tearDown();
+    }).then((_) => parent.runTearDown());
+  }
+
+  /// Runs [body] with special error-handling behavior.
+  ///
+  /// Errors emitted [body] will still cause be the test to fail, but they won't
+  /// cause it to *stop*. In particular, they won't remove any outstanding
+  /// callbacks registered outside of [body].
+  Future _errorsDontStopTest(body()) {
+    var completer = new Completer();
+    Invoker.current.waitForOutstandingCallbacks(() {
+      new Future.sync(body).whenComplete(completer.complete);
+    });
+    return completer.future;
   }
 }
diff --git a/pubspec.yaml b/pubspec.yaml
index bedb824..89683f2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: test
-version: 0.12.4+8
+version: 0.12.4+9
 author: Dart Team <misc@dartlang.org>
 description: A library for writing dart unit tests.
 homepage: https://github.com/dart-lang/test
diff --git a/test/backend/declarer_test.dart b/test/backend/declarer_test.dart
index 4873b61..3815367 100644
--- a/test/backend/declarer_test.dart
+++ b/test/backend/declarer_test.dart
@@ -389,6 +389,26 @@
         expect(outerTearDownRun, isTrue);
       });
 
+      test("runs outer callbacks even when inner ones fail", () async {
+        var outerTearDownRun = false;
+        _declarer.tearDown(() {
+          return new Future(() => outerTearDownRun = true);
+        });
+
+        _declarer.group("inner", () {
+          _declarer.tearDown(() {
+            throw 'inner error';
+          });
+
+          _declarer.test("description", expectAsync(() {
+            expect(outerTearDownRun, isFalse);
+          }, max: 1));
+        });
+
+        await _runTest(0, shouldFail: true);
+        expect(outerTearDownRun, isTrue);
+      });
+
       test("can't be called multiple times", () {
         _declarer.group("group", () {
           _declarer.tearDown(() {});