Allow a timeout following a completed test (#1189)

Fixes #1188

A test is marked completed as soon as it fails. If a `tearDown` callback
also take too long, or never completes, we ignore the timeout and never
move on to more tests.
diff --git a/pkgs/test/test/runner/timeout_test.dart b/pkgs/test/test/runner/timeout_test.dart
index b05fb4e..a9756da 100644
--- a/pkgs/test/test/runner/timeout_test.dart
+++ b/pkgs/test/test/runner/timeout_test.dart
@@ -110,4 +110,54 @@
             ['Test timed out after 0.4 seconds.', '-1: Some tests failed.']));
     await test.shouldExit(1);
   });
+
+  test('times out teardown callbacks', () async {
+    await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+  tearDown(() async {
+    await Completer<void>().future;
+  });
+
+  test('timeout in teardown', () async {
+    // nothing
+  });
+}
+''').create();
+
+    var test = await runTest(['--timeout=50ms', 'test.dart']);
+    expect(
+        test.stdout,
+        containsInOrder(
+            ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+    await test.shouldExit(1);
+  });
+
+  test('times out after failing test', () async {
+    await d.file('test.dart', '''
+import 'dart:async';
+
+import 'package:test/test.dart';
+
+void main() {
+  tearDown(() async {
+    await Completer<void>().future;
+  });
+
+  test('timeout in teardown', () async {
+    expect(true, false);
+  });
+}
+''').create();
+
+    var test = await runTest(['--timeout=50ms', 'test.dart']);
+    expect(
+        test.stdout,
+        containsInOrder(
+            ['Test timed out after 0 seconds.', '-1: Some tests failed.']));
+    await test.shouldExit(1);
+  });
 }
diff --git a/pkgs/test_api/CHANGELOG.md b/pkgs/test_api/CHANGELOG.md
index e140751..463cdb9 100644
--- a/pkgs/test_api/CHANGELOG.md
+++ b/pkgs/test_api/CHANGELOG.md
@@ -4,6 +4,8 @@
   is done matching.
   * This fixes a bug where using a matcher on a custom stream controller and
     then awaiting the `close()` method on that controller would hang.
+* Avoid causing the test runner to hang if there is a timeout during a
+  `tearDown` callback following a failing test case.
 
 ## 0.2.14
 
diff --git a/pkgs/test_api/lib/src/backend/invoker.dart b/pkgs/test_api/lib/src/backend/invoker.dart
index 9c56627..2c83b2d 100644
--- a/pkgs/test_api/lib/src/backend/invoker.dart
+++ b/pkgs/test_api/lib/src/backend/invoker.dart
@@ -284,7 +284,6 @@
 
     _timeoutTimer = _invokerZone.createTimer(timeout, () {
       _outstandingCallbackZones.last.run(() {
-        if (liveTest.isComplete) return;
         _handleError(Zone.current, TimeoutException(message(), timeout));
       });
     });