From 7babd03969368b21e51de65a3e84d43925f6e009 Mon Sep 17 00:00:00 2001
From: Florian Loitsch <floitsch@google.com>
Date: Thu, 24 Nov 2016 11:47:43 +0100
Subject: [PATCH] Add expectAsyncX and expectAsyncUntilX methods, and deprecate
 the old methods.

Fixes #436.

R=nweiz@google.com

Review URL: https://codereview.chromium.org//2515303002 .
---
 .analysis_options                             |   2 -
 CHANGELOG.md                                  |   8 +
 README.md                                     |   2 +-
 lib/src/frontend/expect_async.dart            | 596 ++++++++++++++++--
 test/backend/declarer_test.dart               |  74 +--
 test/backend/invoker_test.dart                |  14 +-
 test/frontend/expect_async_test.dart          |  48 +-
 test/frontend/set_up_all_test.dart            |   8 +-
 test/frontend/tear_down_all_test.dart         |   6 +-
 test/runner/configuration/top_level_test.dart |   2 +-
 test/runner/engine_test.dart                  |  18 +-
 test/runner/load_suite_test.dart              |   2 +-
 test/runner/pause_after_load_test.dart        |  14 +-
 test/runner/signal_test.dart                  |   2 +-
 test/util/forkable_stream_test.dart           |  36 +-
 test/util/one_off_handler_test.dart           |   8 +-
 test/util/path_handler_test.dart              |  10 +-
 test/util/stream_queue_test.dart              |  14 +-
 test/utils.dart                               |   4 +-
 19 files changed, 682 insertions(+), 186 deletions(-)

diff --git a/.analysis_options b/.analysis_options
index 32fe5bc6..a10d4c5a 100644
--- a/.analysis_options
+++ b/.analysis_options
@@ -1,4 +1,2 @@
 analyzer:
   strong-mode: true
-  exclude:
-    - test/**/*.dart
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 56507bd3..61659e04 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.13.0
+
+* Deprecate `expectAsync` and `expectAsyncUntil`, since they currently can't be
+  made to work cleanly in strong mode. They are replaced with separate methods
+  for each number of callback arguments:
+    * `expectAsync0`, `expectAsync1`, ... `expectAsync6`, and
+    * `expectAsyncUntil0`, `expectAsyncUntil1`, ... `expectAsyncUntil6`.
+
 ## 0.12.16
 
 * Allow tools to interact with browser debuggers using the JSON reporter.
diff --git a/README.md b/README.md
index dfa9b06e..f2570ebf 100644
--- a/README.md
+++ b/README.md
@@ -343,7 +343,7 @@ void main() {
   test("Stream.fromIterable() emits the values in the iterable", () {
     var stream = new Stream.fromIterable([1, 2, 3]);
 
-    stream.listen(expectAsync((number) {
+    stream.listen(expectAsync1((number) {
       expect(number, inInclusiveRange(1, 3));
     }, count: 3));
   });
diff --git a/lib/src/frontend/expect_async.dart b/lib/src/frontend/expect_async.dart
index aa4b8b61..e437a542 100644
--- a/lib/src/frontend/expect_async.dart
+++ b/lib/src/frontend/expect_async.dart
@@ -11,13 +11,13 @@ import 'expect.dart';
 const _PLACEHOLDER = const Object();
 
 // Functions used to check how many arguments a callback takes.
-typedef _Func0();
-typedef _Func1(a);
-typedef _Func2(a, b);
-typedef _Func3(a, b, c);
-typedef _Func4(a, b, c, d);
-typedef _Func5(a, b, c, d, e);
-typedef _Func6(a, b, c, d, e, f);
+typedef T Func0<T>();
+typedef T Func1<T, A>(A a);
+typedef T Func2<T, A, B>(A a, B b);
+typedef T Func3<T, A, B, C>(A a, B b, C c);
+typedef T Func4<T, A, B, C, D>(A a, B b, C c, D d);
+typedef T Func5<T, A, B, C, D, E>(A a, B b, C c, D d, E e);
+typedef T Func6<T, A, B, C, D, E, F>(A a, B b, C c, D d, E e, F f);
 
 typedef bool _IsDoneCallback();
 
@@ -29,7 +29,7 @@ typedef bool _IsDoneCallback();
 ///
 /// The wrapper function is accessible via [func]. It supports up to six
 /// optional and/or required positional arguments, but no named arguments.
-class _ExpectedFunction {
+class _ExpectedFunction<T> {
   /// The wrapped callback.
   final Function _callback;
 
@@ -128,13 +128,13 @@ class _ExpectedFunction {
   /// Returns a function that has the same number of positional arguments as the
   /// wrapped function (up to a total of 6).
   Function get func {
-    if (_callback is _Func6) return _max6;
-    if (_callback is _Func5) return _max5;
-    if (_callback is _Func4) return _max4;
-    if (_callback is _Func3) return _max3;
-    if (_callback is _Func2) return _max2;
-    if (_callback is _Func1) return _max1;
-    if (_callback is _Func0) return _max0;
+    if (_callback is Func6) return max6;
+    if (_callback is Func5) return max5;
+    if (_callback is Func4) return max4;
+    if (_callback is Func3) return max3;
+    if (_callback is Func2) return max2;
+    if (_callback is Func1) return max1;
+    if (_callback is Func0) return max0;
 
     _invoker.removeOutstandingCallback();
     throw new ArgumentError(
@@ -143,27 +143,44 @@ class _ExpectedFunction {
 
   // This indirection is critical. It ensures the returned function has an
   // argument count of zero.
-  _max0() => _max6();
-
-  _max1([a0 = _PLACEHOLDER]) => _max6(a0);
-
-  _max2([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER]) => _max6(a0, a1);
-
-  _max3([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER]) =>
-      _max6(a0, a1, a2);
-
-  _max4([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER,
-      a3 = _PLACEHOLDER]) => _max6(a0, a1, a2, a3);
-
-  _max5([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER,
-      a3 = _PLACEHOLDER, a4 = _PLACEHOLDER]) => _max6(a0, a1, a2, a3, a4);
-
-  _max6([a0 = _PLACEHOLDER, a1 = _PLACEHOLDER, a2 = _PLACEHOLDER,
-      a3 = _PLACEHOLDER, a4 = _PLACEHOLDER, a5 = _PLACEHOLDER]) =>
+  T max0() => max6();
+
+  T max1([Object a0 = _PLACEHOLDER]) => max6(a0);
+
+  T max2([Object a0 = _PLACEHOLDER, Object a1 = _PLACEHOLDER]) => max6(a0, a1);
+
+  T max3(
+          [Object a0 = _PLACEHOLDER,
+          Object a1 = _PLACEHOLDER,
+          Object a2 = _PLACEHOLDER]) =>
+      max6(a0, a1, a2);
+
+  T max4(
+          [Object a0 = _PLACEHOLDER,
+          Object a1 = _PLACEHOLDER,
+          Object a2 = _PLACEHOLDER,
+          Object a3 = _PLACEHOLDER]) =>
+      max6(a0, a1, a2, a3);
+
+  T max5(
+          [Object a0 = _PLACEHOLDER,
+          Object a1 = _PLACEHOLDER,
+          Object a2 = _PLACEHOLDER,
+          Object a3 = _PLACEHOLDER,
+          Object a4 = _PLACEHOLDER]) =>
+      max6(a0, a1, a2, a3, a4);
+
+  T max6(
+          [Object a0 = _PLACEHOLDER,
+          Object a1 = _PLACEHOLDER,
+          Object a2 = _PLACEHOLDER,
+          Object a3 = _PLACEHOLDER,
+          Object a4 = _PLACEHOLDER,
+          Object a5 = _PLACEHOLDER]) =>
       _run([a0, a1, a2, a3, a4, a5].where((a) => a != _PLACEHOLDER));
 
   /// Runs the wrapped function with [args] and returns its return value.
-  _run(Iterable args) {
+  T _run(Iterable args) {
     // Note that in the old test, this returned `null` if it encountered an
     // error, where now it just re-throws that error because Zone machinery will
     // pass it to the invoker anyway.
@@ -171,13 +188,13 @@ class _ExpectedFunction {
       _actualCalls++;
       if (_invoker.liveTest.state.shouldBeDone) {
         throw 'Callback ${_id}called ($_actualCalls) after test case '
-              '${_invoker.liveTest.test.name} had already completed.$_reason';
+            '${_invoker.liveTest.test.name} had already completed.$_reason';
       } else if (_maxExpectedCalls >= 0 && _actualCalls > _maxExpectedCalls) {
         throw new TestFailure('Callback ${_id}called more times than expected '
-                              '($_maxExpectedCalls).$_reason');
+            '($_maxExpectedCalls).$_reason');
       }
 
-      return Function.apply(_callback, args.toList());
+      return Function.apply(_callback, args.toList()) as T;
     } catch (error, stackTrace) {
       _zone.handleUncaughtError(error, stackTrace);
       return null;
@@ -199,13 +216,29 @@ class _ExpectedFunction {
   }
 }
 
-/// Indicate that [callback] is expected to be called [count] number of times
-/// (by default 1).
+/// This function is deprecated because it doesn't work well with strong mode.
+/// Use [expectAsync0], [expectAsync1],
+/// [expectAsync2], [expectAsync3], [expectAsync4], [expectAsync5], or
+/// [expectAsync6] instead.
+@Deprecated("Will be removed in 0.13.0")
+Function expectAsync(Function callback,
+    {int count: 1, int max: 0, String id, String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError("expectAsync() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction(callback, count, max, id: id, reason: reason)
+      .func;
+}
+
+/// Informs the framework that the given [callback] of arity 0 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
 ///
 /// The test framework will wait for the callback to run the [count] times
-/// before it considers the current test to be complete. [callback] may take up
-/// to six optional or required positional arguments; named arguments are not
-/// supported.
+/// before it considers the current test to be complete.
 ///
 /// [max] can be used to specify an upper bound on the number of calls; if this
 /// is exceeded the test will fail. If [max] is `0` (the default), the callback
@@ -215,27 +248,253 @@ class _ExpectedFunction {
 /// Both [id] and [reason] are optional and provide extra information about the
 /// callback when debugging. [id] should be the name of the callback, while
 /// [reason] should be the reason the callback is expected to be called.
-Function expectAsync(Function callback,
-        {int count: 1, int max: 0, String id, String reason}) {
+///
+/// This method takes callbacks with zero arguments. See also
+/// [expectAsync1], [expectAsync2], [expectAsync3], [expectAsync4],
+/// [expectAsync5], and [expectAsync6] for callbacks with different arity.
+Func0<dynamic/*=T*/ > expectAsync0/*<T>*/(dynamic/*=T*/ callback(),
+    {int count: 1, int max: 0, String id, String reason}) {
   if (Invoker.current == null) {
-    throw new StateError("expectAsync() may only be called within a test.");
+    throw new StateError("expectAsync0() may only be called within a test.");
   }
 
-  return new _ExpectedFunction(callback, count, max, id: id, reason: reason)
-      .func;
+  return new _ExpectedFunction/*<T>*/(callback, count, max,
+          id: id, reason: reason)
+      .max0;
 }
 
-/// Indicate that [callback] is expected to be called until [isDone] returns
-/// true.
+/// Informs the framework that the given [callback] of arity 1 is expected to be
+/// called [count] number of times (by default 1).
 ///
-/// [isDone] is called after each time the function is run. Only when it returns
-/// true will the callback be considered complete. [callback] may take up to six
-/// optional or required positional arguments; named arguments are not
-/// supported.
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with one argument. See also
+/// [expectAsync0], [expectAsync2], [expectAsync3], [expectAsync4],
+/// [expectAsync5], and [expectAsync6] for callbacks with different arity.
+Func1<dynamic/*=T*/, dynamic/*=A*/ > expectAsync1/*<T, A>*/(
+    dynamic/*=T*/ callback(dynamic/*=A*/ a),
+    {int count: 1,
+    int max: 0,
+    String id,
+    String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError("expectAsync1() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, count, max,
+          id: id, reason: reason)
+      .max1;
+}
+
+/// Informs the framework that the given [callback] of arity 2 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with two arguments. See also
+/// [expectAsync0], [expectAsync1], [expectAsync3], [expectAsync4],
+/// [expectAsync5], and [expectAsync6] for callbacks with different arity.
+Func2<dynamic/*=T*/, dynamic/*=A*/, dynamic/*=B*/ > expectAsync2/*<T, A, B>*/(
+    dynamic/*=T*/ callback(dynamic/*=A*/ a, dynamic/*=B*/ b),
+    {int count: 1,
+    int max: 0,
+    String id,
+    String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError("expectAsync2() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, count, max,
+          id: id, reason: reason)
+      .max2;
+}
+
+/// Informs the framework that the given [callback] of arity 3 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
 ///
 /// Both [id] and [reason] are optional and provide extra information about the
 /// callback when debugging. [id] should be the name of the callback, while
 /// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with three arguments. See also
+/// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync4],
+/// [expectAsync5], and [expectAsync6] for callbacks with different arity.
+Func3<dynamic/*=T*/, dynamic/*=A*/, dynamic/*=B*/, dynamic/*=C*/ >
+    expectAsync3/*<T, A, B, C>*/(
+        dynamic/*=T*/ callback(
+            dynamic/*=A*/ a, dynamic/*=B*/ b, dynamic/*=C*/ c),
+        {int count: 1,
+        int max: 0,
+        String id,
+        String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError("expectAsync3() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, count, max,
+          id: id, reason: reason)
+      .max3;
+}
+
+/// Informs the framework that the given [callback] of arity 4 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with four arguments. See also
+/// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync3],
+/// [expectAsync5], and [expectAsync6] for callbacks with different arity.
+Func4<dynamic/*=T*/, dynamic/*=A*/, dynamic/*=B*/, dynamic/*=C*/,
+        dynamic/*=D*/ >
+    expectAsync4/*<T, A, B, C, D>*/(
+        dynamic/*=T*/ callback(
+            dynamic/*=A*/ a, dynamic/*=B*/ b, dynamic/*=C*/ c, dynamic/*=D*/ d),
+        {int count: 1,
+        int max: 0,
+        String id,
+        String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError("expectAsync4() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, count, max,
+          id: id, reason: reason)
+      .max4;
+}
+
+/// Informs the framework that the given [callback] of arity 5 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with five arguments. See also
+/// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync3],
+/// [expectAsync4], and [expectAsync6] for callbacks with different arity.
+Func5<dynamic/*=T*/, dynamic/*=A*/, dynamic/*=B*/, dynamic/*=C*/,
+        dynamic/*=D*/, dynamic/*=E*/ >
+    expectAsync5/*<T, A, B, C, D, E>*/(
+        dynamic/*=T*/ callback(dynamic/*=A*/ a, dynamic/*=B*/ b,
+            dynamic/*=C*/ c, dynamic/*=D*/ d, dynamic/*=E*/ e),
+        {int count: 1,
+        int max: 0,
+        String id,
+        String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError("expectAsync5() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, count, max,
+          id: id, reason: reason)
+      .max5;
+}
+
+/// Informs the framework that the given [callback] of arity 6 is expected to be
+/// called [count] number of times (by default 1).
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// The test framework will wait for the callback to run the [count] times
+/// before it considers the current test to be complete.
+///
+/// [max] can be used to specify an upper bound on the number of calls; if this
+/// is exceeded the test will fail. If [max] is `0` (the default), the callback
+/// is expected to be called exactly [count] times. If [max] is `-1`, the
+/// callback is allowed to be called any number of times greater than [count].
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with six arguments. See also
+/// [expectAsync0], [expectAsync1], [expectAsync2], [expectAsync3],
+/// [expectAsync4], and [expectAsync5] for callbacks with different arity.
+Func6<dynamic/*=T*/, dynamic/*=A*/, dynamic/*=B*/, dynamic/*=C*/,
+        dynamic/*=D*/, dynamic/*=E*/, dynamic/*=F*/ >
+    expectAsync6/*<T, A, B, C, D, E, F>*/(
+        dynamic/*=T*/ callback(dynamic/*=A*/ a, dynamic/*=B*/ b,
+            dynamic/*=C*/ c, dynamic/*=D*/ d, dynamic/*=E*/ e, dynamic/*=F*/ f),
+        {int count: 1,
+        int max: 0,
+        String id,
+        String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError("expectAsync6() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, count, max,
+          id: id, reason: reason)
+      .max6;
+}
+
+/// This function is deprecated because it doesn't work well with strong mode.
+/// Use [expectAsyncUntil0], [expectAsyncUntil1],
+/// [expectAsyncUntil2], [expectAsyncUntil3], [expectAsyncUntil4],
+/// [expectAsyncUntil5], or [expectAsyncUntil6] instead.
+@Deprecated("Will be removed in 0.13.0")
 Function expectAsyncUntil(Function callback, bool isDone(),
     {String id, String reason}) {
   if (Invoker.current == null) {
@@ -244,5 +503,236 @@ Function expectAsyncUntil(Function callback, bool isDone(),
   }
 
   return new _ExpectedFunction(callback, 0, -1,
-      id: id, reason: reason, isDone: isDone).func;
+          id: id, reason: reason, isDone: isDone)
+      .func;
+}
+
+/// Informs the framework that the given [callback] of arity 0 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with zero arguments. See also
+/// [expectAsyncUntil1], [expectAsyncUntil2], [expectAsyncUntil3],
+/// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func0<dynamic/*=T*/ > expectAsyncUntil0/*<T>*/(
+    dynamic/*=T*/ callback(), bool isDone(),
+    {String id, String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError(
+        "expectAsyncUntil0() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, 0, -1,
+          id: id, reason: reason, isDone: isDone)
+      .max0;
+}
+
+/// Informs the framework that the given [callback] of arity 1 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with one argument. See also
+/// [expectAsyncUntil0], [expectAsyncUntil2], [expectAsyncUntil3],
+/// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func1<dynamic/*=T*/, dynamic/*=A*/ > expectAsyncUntil1/*<T, A>*/(
+    dynamic/*=T*/ callback(dynamic/*=A*/ a), bool isDone(),
+    {String id, String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError(
+        "expectAsyncUntil1() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, 0, -1,
+          id: id, reason: reason, isDone: isDone)
+      .max1;
+}
+
+/// Informs the framework that the given [callback] of arity 2 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with two arguments. See also
+/// [expectAsyncUntil0], [expectAsyncUntil1], [expectAsyncUntil3],
+/// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func2<dynamic/*=T*/, dynamic/*=A*/, dynamic/*=B*/ >
+    expectAsyncUntil2/*<T, A, B>*/(
+        dynamic/*=T*/ callback(dynamic/*=A*/ a, dynamic/*=B*/ b), bool isDone(),
+        {String id, String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError(
+        "expectAsyncUntil2() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, 0, -1,
+          id: id, reason: reason, isDone: isDone)
+      .max2;
+}
+
+/// Informs the framework that the given [callback] of arity 3 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with three arguments. See also
+/// [expectAsyncUntil0], [expectAsyncUntil1], [expectAsyncUntil2],
+/// [expectAsyncUntil4], [expectAsyncUntil5], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func3<dynamic/*=T*/, dynamic/*=A*/, dynamic/*=B*/, dynamic/*=C*/ >
+    expectAsyncUntil3/*<T, A, B, C>*/(
+        dynamic/*=T*/ callback(
+            dynamic/*=A*/ a, dynamic/*=B*/ b, dynamic/*=C*/ c),
+        bool isDone(),
+        {String id,
+        String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError(
+        "expectAsyncUntil3() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, 0, -1,
+          id: id, reason: reason, isDone: isDone)
+      .max3;
+}
+
+/// Informs the framework that the given [callback] of arity 4 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with four arguments. See also
+/// [expectAsyncUntil0], [expectAsyncUntil1], [expectAsyncUntil2],
+/// [expectAsyncUntil3], [expectAsyncUntil5], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func4<dynamic/*=T*/, dynamic/*=A*/, dynamic/*=B*/, dynamic/*=C*/,
+        dynamic/*=D*/ >
+    expectAsyncUntil4/*<T, A, B, C, D>*/(
+        dynamic/*=T*/ callback(
+            dynamic/*=A*/ a, dynamic/*=B*/ b, dynamic/*=C*/ c, dynamic/*=D*/ d),
+        bool isDone(),
+        {String id,
+        String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError(
+        "expectAsyncUntil4() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, 0, -1,
+          id: id, reason: reason, isDone: isDone)
+      .max4;
+}
+
+/// Informs the framework that the given [callback] of arity 5 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with five arguments. See also
+/// [expectAsyncUntil0], [expectAsyncUntil1], [expectAsyncUntil2],
+/// [expectAsyncUntil3], [expectAsyncUntil4], and [expectAsyncUntil6] for
+/// callbacks with different arity.
+Func5<dynamic/*=T*/, dynamic/*=A*/, dynamic/*=B*/, dynamic/*=C*/,
+        dynamic/*=D*/, dynamic/*=E*/ >
+    expectAsyncUntil5/*<T, A, B, C, D, E>*/(
+        dynamic/*=T*/ callback(dynamic/*=A*/ a, dynamic/*=B*/ b,
+            dynamic/*=C*/ c, dynamic/*=D*/ d, dynamic/*=E*/ e),
+        bool isDone(),
+        {String id,
+        String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError(
+        "expectAsyncUntil5() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, 0, -1,
+          id: id, reason: reason, isDone: isDone)
+      .max5;
+}
+
+/// Informs the framework that the given [callback] of arity 6 is expected to be
+/// called until [isDone] returns true.
+///
+/// Returns a wrapped function that should be used as a replacement of the
+/// original callback.
+///
+/// [isDone] is called after each time the function is run. Only when it returns
+/// true will the callback be considered complete.
+///
+/// Both [id] and [reason] are optional and provide extra information about the
+/// callback when debugging. [id] should be the name of the callback, while
+/// [reason] should be the reason the callback is expected to be called.
+///
+/// This method takes callbacks with six arguments. See also
+/// [expectAsyncUntil0], [expectAsyncUntil1], [expectAsyncUntil2],
+/// [expectAsyncUntil3], [expectAsyncUntil4], and [expectAsyncUntil5] for
+/// callbacks with different arity.
+Func6<dynamic/*=T*/, dynamic/*=A*/, dynamic/*=B*/, dynamic/*=C*/,
+        dynamic/*=D*/, dynamic/*=E*/, dynamic/*=F*/ >
+    expectAsyncUntil6/*<T, A, B, C, D, E, F>*/(
+        dynamic/*=T*/ callback(dynamic/*=A*/ a, dynamic/*=B*/ b,
+            dynamic/*=C*/ c, dynamic/*=D*/ d, dynamic/*=E*/ e, dynamic/*=F*/ f),
+        bool isDone(),
+        {String id,
+        String reason}) {
+  if (Invoker.current == null) {
+    throw new StateError(
+        "expectAsyncUntil() may only be called within a test.");
+  }
+
+  return new _ExpectedFunction/*<T>*/(callback, 0, -1,
+          id: id, reason: reason, isDone: isDone)
+      .max6;
 }
diff --git a/test/backend/declarer_test.dart b/test/backend/declarer_test.dart
index e66fc403..ce38df63 100644
--- a/test/backend/declarer_test.dart
+++ b/test/backend/declarer_test.dart
@@ -65,12 +65,12 @@ void main() {
       var tests = declare(() {
         setUp(() => setUpRun = true);
 
-        test("description 1", expectAsync(() {
+        test("description 1", expectAsync0(() {
           expect(setUpRun, isTrue);
           setUpRun = false;
         }, max: 1));
 
-        test("description 2", expectAsync(() {
+        test("description 2", expectAsync0(() {
           expect(setUpRun, isTrue);
           setUpRun = false;
         }, max: 1));
@@ -87,7 +87,7 @@ void main() {
           return new Future(() => setUpRun = true);
         });
 
-        test("description", expectAsync(() {
+        test("description", expectAsync0(() {
           expect(setUpRun, isTrue);
         }, max: 1));
       });
@@ -100,25 +100,25 @@ void main() {
       var secondSetUpRun = false;
       var thirdSetUpRun = false;
       var tests = declare(() {
-        setUp(expectAsync(() async {
+        setUp(expectAsync0(() async {
           expect(secondSetUpRun, isFalse);
           expect(thirdSetUpRun, isFalse);
           firstSetUpRun = true;
         }));
 
-        setUp(expectAsync(() async {
+        setUp(expectAsync0(() async {
           expect(firstSetUpRun, isTrue);
           expect(thirdSetUpRun, isFalse);
           secondSetUpRun = true;
         }));
 
-        setUp(expectAsync(() async {
+        setUp(expectAsync0(() async {
           expect(firstSetUpRun, isTrue);
           expect(secondSetUpRun, isTrue);
           thirdSetUpRun = true;
         }));
 
-        test("description", expectAsync(() {
+        test("description", expectAsync0(() {
           expect(firstSetUpRun, isTrue);
           expect(secondSetUpRun, isTrue);
           expect(thirdSetUpRun, isTrue);
@@ -136,11 +136,11 @@ void main() {
         setUp(() => tearDownRun = false);
         tearDown(() => tearDownRun = true);
 
-        test("description 1", expectAsync(() {
+        test("description 1", expectAsync0(() {
           expect(tearDownRun, isFalse);
         }, max: 1));
 
-        test("description 2", expectAsync(() {
+        test("description 2", expectAsync0(() {
           expect(tearDownRun, isFalse);
         }, max: 1));
       });
@@ -157,7 +157,7 @@ void main() {
         setUp(() => tearDownRun = false);
         tearDown(() => tearDownRun = true);
 
-        test("description 1", expectAsync(() {
+        test("description 1", expectAsync0(() {
           Invoker.current.addOutstandingCallback();
           new Future(() => throw new TestFailure("oh no"));
         }, max: 1));
@@ -174,7 +174,7 @@ void main() {
           return new Future(() => tearDownRun = true);
         });
 
-        test("description", expectAsync(() {
+        test("description", expectAsync0(() {
           expect(tearDownRun, isFalse);
         }, max: 1));
       });
@@ -227,25 +227,25 @@ void main() {
       var secondTearDownRun = false;
       var thirdTearDownRun = false;
       var tests = declare(() {
-        tearDown(expectAsync(() async {
+        tearDown(expectAsync0(() async {
           expect(secondTearDownRun, isTrue);
           expect(thirdTearDownRun, isTrue);
           firstTearDownRun = true;
         }));
 
-        tearDown(expectAsync(() async {
+        tearDown(expectAsync0(() async {
           expect(firstTearDownRun, isFalse);
           expect(thirdTearDownRun, isTrue);
           secondTearDownRun = true;
         }));
 
-        tearDown(expectAsync(() async {
+        tearDown(expectAsync0(() async {
           expect(firstTearDownRun, isFalse);
           expect(secondTearDownRun, isFalse);
           thirdTearDownRun = true;
         }));
 
-        test("description", expectAsync(() {
+        test("description", expectAsync0(() {
           expect(firstTearDownRun, isFalse);
           expect(secondTearDownRun, isFalse);
           expect(thirdTearDownRun, isFalse);
@@ -257,13 +257,13 @@ void main() {
 
     test("runs further tearDowns in a group even if one fails", () async {
       var tests = declare(() {
-        tearDown(expectAsync(() {}));
+        tearDown(expectAsync0(() {}));
 
         tearDown(() async {
           throw 'error';
         });
 
-        test("description", expectAsync(() {}));
+        test("description", expectAsync0(() {}));
       });
 
       await _runTest(tests.single, shouldFail: true);
@@ -367,13 +367,13 @@ void main() {
           group("group", () {
             setUp(() => setUpRun = true);
 
-            test("description 1", expectAsync(() {
+            test("description 1", expectAsync0(() {
               expect(setUpRun, isTrue);
               setUpRun = false;
             }, max: 1));
           });
 
-          test("description 2", expectAsync(() {
+          test("description 2", expectAsync0(() {
             expect(setUpRun, isFalse);
             setUpRun = false;
           }, max: 1));
@@ -388,27 +388,27 @@ void main() {
         var middleSetUpRun = false;
         var innerSetUpRun = false;
         var entries = declare(() {
-          setUp(expectAsync(() {
+          setUp(expectAsync0(() {
             expect(middleSetUpRun, isFalse);
             expect(innerSetUpRun, isFalse);
             outerSetUpRun = true;
           }, max: 1));
 
           group("middle", () {
-            setUp(expectAsync(() {
+            setUp(expectAsync0(() {
               expect(outerSetUpRun, isTrue);
               expect(innerSetUpRun, isFalse);
               middleSetUpRun = true;
             }, max: 1));
 
             group("inner", () {
-              setUp(expectAsync(() {
+              setUp(expectAsync0(() {
                 expect(outerSetUpRun, isTrue);
                 expect(middleSetUpRun, isTrue);
                 innerSetUpRun = true;
               }, max: 1));
 
-              test("description", expectAsync(() {
+              test("description", expectAsync0(() {
                 expect(outerSetUpRun, isTrue);
                 expect(middleSetUpRun, isTrue);
                 expect(innerSetUpRun, isTrue);
@@ -426,18 +426,18 @@ void main() {
         var outerSetUpRun = false;
         var innerSetUpRun = false;
         var entries = declare(() {
-          setUp(expectAsync(() {
+          setUp(expectAsync0(() {
             expect(innerSetUpRun, isFalse);
             return new Future(() => outerSetUpRun = true);
           }, max: 1));
 
           group("inner", () {
-            setUp(expectAsync(() {
+            setUp(expectAsync0(() {
               expect(outerSetUpRun, isTrue);
               return new Future(() => innerSetUpRun = true);
             }, max: 1));
 
-            test("description", expectAsync(() {
+            test("description", expectAsync0(() {
               expect(outerSetUpRun, isTrue);
               expect(innerSetUpRun, isTrue);
             }, max: 1));
@@ -484,12 +484,12 @@ void main() {
           group("group", () {
             tearDown(() => tearDownRun = true);
 
-            test("description 1", expectAsync(() {
+            test("description 1", expectAsync0(() {
               expect(tearDownRun, isFalse);
             }, max: 1));
           });
 
-          test("description 2", expectAsync(() {
+          test("description 2", expectAsync0(() {
             expect(tearDownRun, isFalse);
           }, max: 1));
         });
@@ -506,27 +506,27 @@ void main() {
         var middleTearDownRun = false;
         var outerTearDownRun = false;
         var entries = declare(() {
-          tearDown(expectAsync(() {
+          tearDown(expectAsync0(() {
             expect(innerTearDownRun, isTrue);
             expect(middleTearDownRun, isTrue);
             outerTearDownRun = true;
           }, max: 1));
 
           group("middle", () {
-            tearDown(expectAsync(() {
+            tearDown(expectAsync0(() {
               expect(innerTearDownRun, isTrue);
               expect(outerTearDownRun, isFalse);
               middleTearDownRun = true;
             }, max: 1));
 
             group("inner", () {
-              tearDown(expectAsync(() {
+              tearDown(expectAsync0(() {
                 expect(outerTearDownRun, isFalse);
                 expect(middleTearDownRun, isFalse);
                 innerTearDownRun = true;
               }, max: 1));
 
-              test("description", expectAsync(() {
+              test("description", expectAsync0(() {
                 expect(outerTearDownRun, isFalse);
                 expect(middleTearDownRun, isFalse);
                 expect(innerTearDownRun, isFalse);
@@ -547,18 +547,18 @@ void main() {
         var outerTearDownRun = false;
         var innerTearDownRun = false;
         var entries = declare(() {
-          tearDown(expectAsync(() {
+          tearDown(expectAsync0(() {
             expect(innerTearDownRun, isTrue);
             return new Future(() => outerTearDownRun = true);
           }, max: 1));
 
           group("inner", () {
-            tearDown(expectAsync(() {
+            tearDown(expectAsync0(() {
               expect(outerTearDownRun, isFalse);
               return new Future(() => innerTearDownRun = true);
             }, max: 1));
 
-            test("description", expectAsync(() {
+            test("description", expectAsync0(() {
               expect(outerTearDownRun, isFalse);
               expect(innerTearDownRun, isFalse);
             }, max: 1));
@@ -583,7 +583,7 @@ void main() {
               throw 'inner error';
             });
 
-            test("description", expectAsync(() {
+            test("description", expectAsync0(() {
               expect(outerTearDownRun, isFalse);
             }, max: 1));
           });
@@ -605,7 +605,7 @@ Future _runTest(Test test, {bool shouldFail: false}) {
   var liveTest = test.load(_suite);
 
   liveTest.onError.listen(shouldFail
-      ? expectAsync((_) {})
+      ? expectAsync1((_) {})
       : (error) => registerException(error.error, error.stackTrace));
 
   return liveTest.run();
diff --git a/test/backend/invoker_test.dart b/test/backend/invoker_test.dart
index f31ac50a..4989fd9c 100644
--- a/test/backend/invoker_test.dart
+++ b/test/backend/invoker_test.dart
@@ -33,7 +33,7 @@ void main() {
       var liveTest = _localTest(() {
         invoker = Invoker.current;
       }).load(suite);
-      liveTest.onError.listen(expectAsync((_) {}, count: 0));
+      liveTest.onError.listen(expectAsync1((_) {}, count: 0));
 
       await liveTest.run();
       expect(invoker.liveTest, equals(liveTest));
@@ -51,7 +51,7 @@ void main() {
           completer.complete(Invoker.current);
         });
       }).load(suite);
-      liveTest.onError.listen(expectAsync((_) {}, count: 0));
+      liveTest.onError.listen(expectAsync1((_) {}, count: 0));
 
       expect(liveTest.run(), completes);
       var invoker = await completer.future;
@@ -67,7 +67,7 @@ void main() {
       liveTest = _localTest(() {
         stateInTest = liveTest.state;
       }).load(suite);
-      liveTest.onError.listen(expectAsync((_) {}, count: 0));
+      liveTest.onError.listen(expectAsync1((_) {}, count: 0));
 
       expect(liveTest.state.status, equals(Status.pending));
       expect(liveTest.state.result, equals(Result.success));
@@ -88,10 +88,10 @@ void main() {
 
     test("onStateChange fires for each state change", () {
       var liveTest = _localTest(() {}).load(suite);
-      liveTest.onError.listen(expectAsync((_) {}, count: 0));
+      liveTest.onError.listen(expectAsync1((_) {}, count: 0));
 
       var first = true;
-      liveTest.onStateChange.listen(expectAsync((state) {
+      liveTest.onStateChange.listen(expectAsync1((state) {
         if (first) {
           expect(state.status, equals(Status.running));
           first = false;
@@ -368,7 +368,7 @@ void main() {
       });
     }).load(suite);
 
-    liveTest.onError.listen(expectAsync((_) {}, count: 0));
+    liveTest.onError.listen(expectAsync1((_) {}, count: 0));
 
     await liveTest.run();
     expect(outstandingCallbackRemoved, isTrue);
@@ -488,7 +488,7 @@ void main() {
         () async {
       var callbackRun = false;
       await Invoker.current.waitForOutstandingCallbacks(() {
-        pumpEventQueue().then(expectAsync((_) {
+        pumpEventQueue().then(expectAsync1((_) {
           callbackRun = true;
         }));
       });
diff --git a/test/frontend/expect_async_test.dart b/test/frontend/expect_async_test.dart
index 2c118f8e..d647102b 100644
--- a/test/frontend/expect_async_test.dart
+++ b/test/frontend/expect_async_test.dart
@@ -14,7 +14,7 @@ void main() {
     test("0", () async {
       var callbackRun = false;
       var liveTest = await runTestBody(() {
-        expectAsync(() {
+        expectAsync0(() {
           callbackRun = true;
         })();
       });
@@ -26,7 +26,7 @@ void main() {
     test("1", () async {
       var callbackRun = false;
       var liveTest = await runTestBody(() {
-        expectAsync((arg) {
+        expectAsync1((arg) {
           expect(arg, equals(1));
           callbackRun = true;
         })(1);
@@ -39,7 +39,7 @@ void main() {
     test("2", () async {
       var callbackRun = false;
       var liveTest = await runTestBody(() {
-        expectAsync((arg1, arg2) {
+        expectAsync2((arg1, arg2) {
           expect(arg1, equals(1));
           expect(arg2, equals(2));
           callbackRun = true;
@@ -53,7 +53,7 @@ void main() {
     test("3", () async {
       var callbackRun = false;
       var liveTest = await runTestBody(() {
-        expectAsync((arg1, arg2, arg3) {
+        expectAsync3((arg1, arg2, arg3) {
           expect(arg1, equals(1));
           expect(arg2, equals(2));
           expect(arg3, equals(3));
@@ -68,7 +68,7 @@ void main() {
     test("4", () async {
       var callbackRun = false;
       var liveTest = await runTestBody(() {
-        expectAsync((arg1, arg2, arg3, arg4) {
+        expectAsync4((arg1, arg2, arg3, arg4) {
           expect(arg1, equals(1));
           expect(arg2, equals(2));
           expect(arg3, equals(3));
@@ -84,7 +84,7 @@ void main() {
     test("5", () async {
       var callbackRun = false;
       var liveTest = await runTestBody(() {
-        expectAsync((arg1, arg2, arg3, arg4, arg5) {
+        expectAsync5((arg1, arg2, arg3, arg4, arg5) {
           expect(arg1, equals(1));
           expect(arg2, equals(2));
           expect(arg3, equals(3));
@@ -101,7 +101,7 @@ void main() {
     test("6", () async {
       var callbackRun = false;
       var liveTest = await runTestBody(() {
-        expectAsync((arg1, arg2, arg3, arg4, arg5, arg6) {
+        expectAsync6((arg1, arg2, arg3, arg4, arg5, arg6) {
           expect(arg1, equals(1));
           expect(arg2, equals(2));
           expect(arg3, equals(3));
@@ -121,7 +121,7 @@ void main() {
     test("allows them to be passed", () async {
       var callbackRun = false;
       var liveTest = await runTestBody(() {
-        expectAsync(([arg = 1]) {
+        expectAsync1(([arg = 1]) {
           expect(arg, equals(2));
           callbackRun = true;
         })(2);
@@ -134,7 +134,7 @@ void main() {
     test("allows them not to be passed", () async {
       var callbackRun = false;
       var liveTest = await runTestBody(() {
-        expectAsync(([arg = 1]) {
+        expectAsync1(([arg = 1]) {
           expect(arg, equals(1));
           callbackRun = true;
         })();
@@ -153,13 +153,13 @@ void main() {
   group("by default", () {
     test("won't allow the test to complete until it's called", () {
       return expectTestBlocks(
-          () => expectAsync(() {}),
+          () => expectAsync0(() {}),
           (callback) => callback());
     });
 
     test("may only be called once", () async {
       var liveTest = await runTestBody(() {
-        var callback = expectAsync(() {});
+        var callback = expectAsync0(() {});
         callback();
         callback();
       });
@@ -175,7 +175,7 @@ void main() {
       var liveTest;
       var future;
       liveTest = createTest(() {
-        var callback = expectAsync(() {}, count: 3);
+        var callback = expectAsync0(() {}, count: 3);
 
         future = new Future.sync(() async {
           await pumpEventQueue();
@@ -201,7 +201,7 @@ void main() {
 
     test("will throw an error if it's called more than that many times", () async {
       var liveTest = await runTestBody(() {
-        var callback = expectAsync(() {}, count: 3);
+        var callback = expectAsync0(() {}, count: 3);
         callback();
         callback();
         callback();
@@ -214,12 +214,12 @@ void main() {
 
     group("0,", () {
       test("won't block the test's completion", () {
-        expectAsync(() {}, count: 0);
+        expectAsync0(() {}, count: 0);
       });
 
       test("will throw an error if it's ever called", () async {
         var liveTest = await runTestBody(() {
-          expectAsync(() {}, count: 0)();
+          expectAsync0(() {}, count: 0)();
         });
 
         expectTestFailed(
@@ -230,21 +230,21 @@ void main() {
 
   group("with max", () {
     test("will allow the callback to be called that many times", () {
-      var callback = expectAsync(() {}, max: 3);
+      var callback = expectAsync0(() {}, max: 3);
       callback();
       callback();
       callback();
     });
 
     test("will allow the callback to be called fewer than that many times", () {
-      var callback = expectAsync(() {}, max: 3);
+      var callback = expectAsync0(() {}, max: 3);
       callback();
     });
 
     test("will throw an error if it's called more than that many times",
         () async {
       var liveTest = await runTestBody(() {
-        var callback = expectAsync(() {}, max: 3);
+        var callback = expectAsync0(() {}, max: 3);
         callback();
         callback();
         callback();
@@ -256,7 +256,7 @@ void main() {
     });
 
     test("-1, will allow the callback to be called any number of times", () {
-      var callback = expectAsync(() {}, max: -1);
+      var callback = expectAsync0(() {}, max: -1);
       for (var i = 0; i < 20; i++) {
         callback();
       }
@@ -264,7 +264,7 @@ void main() {
   });
 
   test("will throw an error if max is less than count", () {
-    expect(() => expectAsync(() {}, max: 1, count: 2),
+    expect(() => expectAsync0(() {}, max: 1, count: 2),
         throwsArgumentError);
   });
 
@@ -275,7 +275,7 @@ void main() {
       var future;
       liveTest = createTest(() {
         var done = false;
-        var callback = expectAsyncUntil(() {}, () => done);
+        var callback = expectAsyncUntil0(() {}, () => done);
 
         future = new Future.sync(() async {
           await pumpEventQueue();
@@ -297,7 +297,7 @@ void main() {
 
     test("doesn't call isDone until after the callback is called", () {
       var callbackRun = false;
-      expectAsyncUntil(() => callbackRun = true, () {
+      expectAsyncUntil0(() => callbackRun = true, () {
         expect(callbackRun, isTrue);
         return true;
       })();
@@ -307,7 +307,7 @@ void main() {
   group("with errors", () {
     test("reports them to the current test", () async {
       var liveTest = await runTestBody(() {
-        expectAsync(() => throw new TestFailure('oh no'))();
+        expectAsync0(() => throw new TestFailure('oh no'))();
       });
 
       expectTestFailed(liveTest, 'oh no');
@@ -318,7 +318,7 @@ void main() {
       var caughtError = false;
       var liveTest = await runTestBody(() {
         try {
-          returnValue = expectAsync(() => throw new TestFailure('oh no'))();
+          returnValue = expectAsync0(() => throw new TestFailure('oh no'))();
         } on TestFailure catch (_) {
           caughtError = true;
         }
diff --git a/test/frontend/set_up_all_test.dart b/test/frontend/set_up_all_test.dart
index 7ae11375..a1f2cfc3 100644
--- a/test/frontend/set_up_all_test.dart
+++ b/test/frontend/set_up_all_test.dart
@@ -204,7 +204,7 @@ void main() {
 
   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 shouldNotRun = expectAsync0(() {}, count: 0);
 
     var engine = declareEngine(() {
       group("skipped", () {
@@ -279,7 +279,7 @@ void main() {
 
     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 shouldNotRun = expectAsync0(() {}, count: 0);
 
       var engine = declareEngine(() {
         setUpAll(() => throw "error");
@@ -292,7 +292,7 @@ void main() {
 
     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 shouldNotRun = expectAsync0(() {}, count: 0);
 
       var engine = declareEngine(() {
         setUpAll(() => throw "error");
@@ -307,7 +307,7 @@ void main() {
 
     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 shouldNotRun = expectAsync0(() {}, count: 0);
 
       var engine = declareEngine(() {
         setUpAll(() => throw "error");
diff --git a/test/frontend/tear_down_all_test.dart b/test/frontend/tear_down_all_test.dart
index a5d9e0b1..9b69ebe1 100644
--- a/test/frontend/tear_down_all_test.dart
+++ b/test/frontend/tear_down_all_test.dart
@@ -262,7 +262,7 @@ void main() {
 
   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 shouldNotRun = expectAsync0(() {}, count: 0);
 
     var engine = declareEngine(() {
       group("skipped", () {
@@ -339,7 +339,7 @@ void main() {
     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 shouldRun = expectAsync0(() {});
 
       var engine = declareEngine(() {
         tearDownAll(() => throw "error");
@@ -354,7 +354,7 @@ void main() {
     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 shouldRun = expectAsync0(() {});
 
       var engine = declareEngine(() {
         tearDownAll(shouldRun);
diff --git a/test/runner/configuration/top_level_test.dart b/test/runner/configuration/top_level_test.dart
index 2ebab89a..d32a5816 100644
--- a/test/runner/configuration/top_level_test.dart
+++ b/test/runner/configuration/top_level_test.dart
@@ -63,7 +63,7 @@ void main() {
 
     schedule(() async {
       var nextLineFired = false;
-      test.stdout.next().then(expectAsync((line) {
+      test.stdout.next().then(expectAsync1((line) {
         expect(line, contains("+0: success"));
         nextLineFired = true;
       }));
diff --git a/test/runner/engine_test.dart b/test/runner/engine_test.dart
index 6ff68041..ffe90099 100644
--- a/test/runner/engine_test.dart
+++ b/test/runner/engine_test.dart
@@ -16,7 +16,7 @@ void main() {
     var testsRun = 0;
     var tests = declare(() {
       for (var i = 0; i < 4; i++) {
-        test("test ${i + 1}", expectAsync(() {
+        test("test ${i + 1}", expectAsync0(() {
           expect(testsRun, equals(i));
           testsRun++;
         }, max: 1));
@@ -36,7 +36,7 @@ void main() {
     var testsRun = 0;
     var tests = declare(() {
       for (var i = 0; i < 4; i++) {
-        test("test ${i + 1}", expectAsync(() {
+        test("test ${i + 1}", expectAsync0(() {
           expect(testsRun, equals(i));
           testsRun++;
         }, max: 1));
@@ -57,11 +57,11 @@ void main() {
     var testsRun = 0;
     var engine = declareEngine(() {
       for (var i = 0; i < 3; i++) {
-        test("test ${i + 1}", expectAsync(() => testsRun++, max: 1));
+        test("test ${i + 1}", expectAsync0(() => testsRun++, max: 1));
       }
     });
 
-    engine.onTestStarted.listen(expectAsync((liveTest) {
+    engine.onTestStarted.listen(expectAsync1((liveTest) {
       // [testsRun] should be one less than the test currently running.
       expect(liveTest.test.name, equals("test ${testsRun + 1}"));
 
@@ -117,7 +117,7 @@ void main() {
     // than the inner test.
     var secondTestStarted = false;
     var firstTestFinished = false;
-    var tearDownBody = expectAsync(() {
+    var tearDownBody = expectAsync0(() {
       expect(secondTestStarted, isFalse);
       expect(firstTestFinished, isFalse);
     });
@@ -173,12 +173,12 @@ void main() {
 
       var engine = new Engine.withSuites([runnerSuite(new Group.root(tests))]);
 
-      engine.onTestStarted.listen(expectAsync((liveTest) {
+      engine.onTestStarted.listen(expectAsync1((liveTest) {
         expect(liveTest, same(engine.liveTests.single));
         expect(liveTest.test.name, equals(tests.single.name));
 
         var i = 0;
-        liveTest.onStateChange.listen(expectAsync((state) {
+        liveTest.onStateChange.listen(expectAsync1((state) {
           if (i == 0) {
             expect(state, equals(const State(Status.running, Result.success)));
           } else if (i == 1) {
@@ -231,12 +231,12 @@ void main() {
       var engine = new Engine.withSuites(
           [runnerSuite(new Group.root(entries))]);
 
-      engine.onTestStarted.listen(expectAsync((liveTest) {
+      engine.onTestStarted.listen(expectAsync1((liveTest) {
         expect(liveTest, same(engine.liveTests.single));
         expect(liveTest.test.name, equals("group test"));
 
         var i = 0;
-        liveTest.onStateChange.listen(expectAsync((state) {
+        liveTest.onStateChange.listen(expectAsync1((state) {
           if (i == 0) {
             expect(state, equals(const State(Status.running, Result.success)));
           } else if (i == 1) {
diff --git a/test/runner/load_suite_test.dart b/test/runner/load_suite_test.dart
index 99372707..19739779 100644
--- a/test/runner/load_suite_test.dart
+++ b/test/runner/load_suite_test.dart
@@ -146,7 +146,7 @@ void main() {
       var suite = new LoadSuite("name", SuiteConfiguration.empty, () => null);
       expect(suite.group.entries, hasLength(1));
 
-      var newSuite = suite.changeSuite(expectAsync((_) {}, count: 0));
+      var newSuite = suite.changeSuite(expectAsync1((_) {}, count: 0));
       expect(newSuite.suite, completion(isNull));
 
       var liveTest = await (suite.group.entries.single as Test).load(suite);
diff --git a/test/runner/pause_after_load_test.dart b/test/runner/pause_after_load_test.dart
index 8c701e31..1103a709 100644
--- a/test/runner/pause_after_load_test.dart
+++ b/test/runner/pause_after_load_test.dart
@@ -49,7 +49,7 @@ void main() {
 
     schedule(() async {
       var nextLineFired = false;
-      test.stdout.next().then(expectAsync((line) {
+      test.stdout.next().then(expectAsync1((line) {
         expect(line, contains("+0: test1.dart: success"));
         nextLineFired = true;
       }));
@@ -72,7 +72,7 @@ void main() {
 
     schedule(() async {
       var nextLineFired = false;
-      test.stdout.next().then(expectAsync((line) {
+      test.stdout.next().then(expectAsync1((line) {
         expect(line, contains("+1: test2.dart: success"));
         nextLineFired = true;
       }));
@@ -112,7 +112,7 @@ void main() {
 
     schedule(() async {
       var nextLineFired = false;
-      test.stdout.next().then(expectAsync((line) {
+      test.stdout.next().then(expectAsync1((line) {
         expect(line, contains("+0: [Dartium] success"));
         nextLineFired = true;
       }));
@@ -134,7 +134,7 @@ void main() {
 
     schedule(() async {
       var nextLineFired = false;
-      test.stdout.next().then(expectAsync((line) {
+      test.stdout.next().then(expectAsync1((line) {
         expect(line, contains("+1: [Chrome] success"));
         nextLineFired = true;
       }));
@@ -193,7 +193,7 @@ void main() {
 
     schedule(() async {
       var nextLineFired = false;
-      test.stdout.next().then(expectAsync((line) {
+      test.stdout.next().then(expectAsync1((line) {
         expect(line, contains("+0: [Dartium] success"));
         nextLineFired = true;
       }));
@@ -266,7 +266,7 @@ void main() {
 
     schedule(() async {
       var nextLineFired = false;
-      test.stdout.next().then(expectAsync((line) {
+      test.stdout.next().then(expectAsync1((line) {
         expect(line, contains("+0: success"));
         nextLineFired = true;
       }));
@@ -308,7 +308,7 @@ void main() {
 
     schedule(() async {
       var nextLineFired = false;
-      test.stdout.next().then(expectAsync((line) {
+      test.stdout.next().then(expectAsync1((line) {
         expect(line, contains("+0: success"));
         nextLineFired = true;
       }));
diff --git a/test/runner/signal_test.dart b/test/runner/signal_test.dart
index 42c99c2b..bc6c054d 100644
--- a/test/runner/signal_test.dart
+++ b/test/runner/signal_test.dart
@@ -234,7 +234,7 @@ void main() {
 
     await new Future.delayed(new Duration(seconds: 1));
     try {
-      expectAsync(() {});
+      expectAsync0(() {});
     } catch (_) {
       expectAsyncThrewError = true;
     }
diff --git a/test/util/forkable_stream_test.dart b/test/util/forkable_stream_test.dart
index e4b5bdca..0a410890 100644
--- a/test/util/forkable_stream_test.dart
+++ b/test/util/forkable_stream_test.dart
@@ -213,7 +213,7 @@ void main() {
         var queue4Fired = false;
         var queue5Fired = false;
 
-        queue5.next.then(expectAsync((_) {
+        queue5.next.then(expectAsync1((_) {
           queue5Fired = true;
           expect(queue1Fired, isTrue);
           expect(queue2Fired, isTrue);
@@ -221,7 +221,7 @@ void main() {
           expect(queue4Fired, isTrue);
         }));
 
-        queue1.next.then(expectAsync((_) {
+        queue1.next.then(expectAsync1((_) {
           queue1Fired = true;
           expect(queue2Fired, isFalse);
           expect(queue3Fired, isFalse);
@@ -229,7 +229,7 @@ void main() {
           expect(queue5Fired, isFalse);
         }));
 
-        queue4.next.then(expectAsync((_) {
+        queue4.next.then(expectAsync1((_) {
           queue4Fired = true;
           expect(queue1Fired, isTrue);
           expect(queue2Fired, isTrue);
@@ -237,7 +237,7 @@ void main() {
           expect(queue5Fired, isFalse);
         }));
 
-        queue2.next.then(expectAsync((_) {
+        queue2.next.then(expectAsync1((_) {
           queue2Fired = true;
           expect(queue1Fired, isTrue);
           expect(queue3Fired, isFalse);
@@ -245,7 +245,7 @@ void main() {
           expect(queue5Fired, isFalse);
         }));
 
-        queue3.next.then(expectAsync((_) {
+        queue3.next.then(expectAsync1((_) {
           queue3Fired = true;
           expect(queue1Fired, isTrue);
           expect(queue2Fired, isTrue);
@@ -311,7 +311,7 @@ void main() {
 
   group("modification during dispatch:", () {
     test("forking during onCancel", () {
-      controller = new StreamController<int>(onCancel: expectAsync(() {
+      controller = new StreamController<int>(onCancel: expectAsync0(() {
         expect(stream.fork().toList(), completion(isEmpty));
       }));
       stream = new ForkableStream<int>(controller.stream);
@@ -320,7 +320,7 @@ void main() {
     });
 
     test("forking during onPause", () {
-      controller = new StreamController<int>(onPause: expectAsync(() {
+      controller = new StreamController<int>(onPause: expectAsync0(() {
         stream.fork().listen(null);
       }));
       stream = new ForkableStream<int>(controller.stream);
@@ -333,9 +333,9 @@ void main() {
 
     test("forking during onData", () {
       var sub;
-      sub = stream.listen(expectAsync((value1) {
+      sub = stream.listen(expectAsync1((value1) {
         expect(value1, equals(1));
-        stream.fork().listen(expectAsync((value2) {
+        stream.fork().listen(expectAsync1((value2) {
           expect(value2, equals(2));
         }));
         sub.cancel();
@@ -347,17 +347,17 @@ void main() {
 
     test("canceling a fork during onData", () {
       var fork = stream.fork();
-      var forkSub = fork.listen(expectAsync((_) {}, count: 0));
+      var forkSub = fork.listen(expectAsync1((_) {}, count: 0));
 
-      stream.listen(expectAsync((_) => forkSub.cancel()));
+      stream.listen(expectAsync1((_) => forkSub.cancel()));
       controller.add(null);
     });
 
     test("forking during onError", () {
       var sub;
-      sub = stream.listen(null, onError: expectAsync((error1) {
+      sub = stream.listen(null, onError: expectAsync1((error1) {
         expect(error1, equals("error 1"));
-        stream.fork().listen(null, onError: expectAsync((error2) {
+        stream.fork().listen(null, onError: expectAsync1((error2) {
           expect(error2, equals("error 2"));
         }));
         sub.cancel();
@@ -369,14 +369,14 @@ void main() {
 
     test("canceling a fork during onError", () {
       var fork = stream.fork();
-      var forkSub = fork.listen(expectAsync((_) {}, count: 0));
+      var forkSub = fork.listen(expectAsync1((_) {}, count: 0));
 
-      stream.listen(null, onError: expectAsync((_) => forkSub.cancel()));
+      stream.listen(null, onError: expectAsync1((_) => forkSub.cancel()));
       controller.addError("error");
     });
 
     test("forking during onDone", () {
-      stream.listen(null, onDone: expectAsync(() {
+      stream.listen(null, onDone: expectAsync0(() {
         expect(stream.fork().toList(), completion(isEmpty));
       }));
 
@@ -385,9 +385,9 @@ void main() {
 
     test("canceling a fork during onDone", () {
       var fork = stream.fork();
-      var forkSub = fork.listen(null, onDone: expectAsync(() {}, count: 0));
+      var forkSub = fork.listen(null, onDone: expectAsync0(() {}, count: 0));
 
-      stream.listen(null, onDone: expectAsync(() => forkSub.cancel()));
+      stream.listen(null, onDone: expectAsync0(() => forkSub.cancel()));
       controller.close();
     });
   });
diff --git a/test/util/one_off_handler_test.dart b/test/util/one_off_handler_test.dart
index 44c2e4cb..af7db225 100644
--- a/test/util/one_off_handler_test.dart
+++ b/test/util/one_off_handler_test.dart
@@ -25,7 +25,7 @@ void main() {
   });
 
   test("passes a request to a handler only once", () async {
-    var path = handler.create(expectAsync((request) {
+    var path = handler.create(expectAsync1((request) {
       expect(request.method, equals("GET"));
       return new shelf.Response.ok("good job!");
     }));
@@ -40,17 +40,17 @@ void main() {
   });
 
   test("passes requests to the correct handlers", () async {
-    var path1 = handler.create(expectAsync((request) {
+    var path1 = handler.create(expectAsync1((request) {
       expect(request.method, equals("GET"));
       return new shelf.Response.ok("one");
     }));
 
-    var path2 = handler.create(expectAsync((request) {
+    var path2 = handler.create(expectAsync1((request) {
       expect(request.method, equals("GET"));
       return new shelf.Response.ok("two");
     }));
 
-    var path3 = handler.create(expectAsync((request) {
+    var path3 = handler.create(expectAsync1((request) {
       expect(request.method, equals("GET"));
       return new shelf.Response.ok("three");
     }));
diff --git a/test/util/path_handler_test.dart b/test/util/path_handler_test.dart
index 4ac0835d..729b94ae 100644
--- a/test/util/path_handler_test.dart
+++ b/test/util/path_handler_test.dart
@@ -26,7 +26,7 @@ void main() {
 
   test("runs a handler for an exact URL", () async {
     var request = new shelf.Request("GET", Uri.parse("http://localhost/foo"));
-    handler.add("foo", expectAsync((request) {
+    handler.add("foo", expectAsync1((request) {
       expect(request.handlerPath, equals('/foo'));
       expect(request.url.path, isEmpty);
       return new shelf.Response.ok("good job!");
@@ -40,7 +40,7 @@ void main() {
   test("runs a handler for a suffix", () async {
     var request = new shelf.Request(
         "GET", Uri.parse("http://localhost/foo/bar"));
-    handler.add("foo", expectAsync((request) {
+    handler.add("foo", expectAsync1((request) {
       expect(request.handlerPath, equals('/foo/'));
       expect(request.url.path, 'bar');
       return new shelf.Response.ok("good job!");
@@ -55,13 +55,13 @@ void main() {
     var request = new shelf.Request(
         "GET", Uri.parse("http://localhost/foo/bar/baz"));
 
-    handler.add("foo", expectAsync((_) {}, count: 0));
-    handler.add("foo/bar", expectAsync((request) {
+    handler.add("foo", expectAsync1((_) {}, count: 0));
+    handler.add("foo/bar", expectAsync1((request) {
       expect(request.handlerPath, equals('/foo/bar/'));
       expect(request.url.path, 'baz');
       return new shelf.Response.ok("good job!");
     }));
-    handler.add("foo/bar/baz/bang", expectAsync((_) {}, count: 0));
+    handler.add("foo/bar/baz/bang", expectAsync1((_) {}, count: 0));
 
     var response = await _handle(request);
     expect(response.statusCode, equals(200));
diff --git a/test/util/stream_queue_test.dart b/test/util/stream_queue_test.dart
index 3545d16f..617b33ed 100644
--- a/test/util/stream_queue_test.dart
+++ b/test/util/stream_queue_test.dart
@@ -744,7 +744,7 @@ main() {
         var queue4Fired = false;
         var queue5Fired = false;
 
-        queue5.next.then(expectAsync((_) {
+        queue5.next.then(expectAsync1((_) {
           queue5Fired = true;
           expect(queue1Fired, isTrue);
           expect(queue2Fired, isTrue);
@@ -752,7 +752,7 @@ main() {
           expect(queue4Fired, isTrue);
         }));
 
-        queue1.next.then(expectAsync((_) {
+        queue1.next.then(expectAsync1((_) {
           queue1Fired = true;
           expect(queue2Fired, isFalse);
           expect(queue3Fired, isFalse);
@@ -760,7 +760,7 @@ main() {
           expect(queue5Fired, isFalse);
         }));
 
-        queue4.next.then(expectAsync((_) {
+        queue4.next.then(expectAsync1((_) {
           queue4Fired = true;
           expect(queue1Fired, isTrue);
           expect(queue2Fired, isTrue);
@@ -768,7 +768,7 @@ main() {
           expect(queue5Fired, isFalse);
         }));
 
-        queue2.next.then(expectAsync((_) {
+        queue2.next.then(expectAsync1((_) {
           queue2Fired = true;
           expect(queue1Fired, isTrue);
           expect(queue3Fired, isFalse);
@@ -776,7 +776,7 @@ main() {
           expect(queue5Fired, isFalse);
         }));
 
-        queue3.next.then(expectAsync((_) {
+        queue3.next.then(expectAsync1((_) {
           queue3Fired = true;
           expect(queue1Fired, isTrue);
           expect(queue2Fired, isTrue);
@@ -996,7 +996,7 @@ main() {
         var queue1 = new StreamQueue<int>(createStream());
         var queue2 = queue1.fork();
 
-        queue2.rest.listen(expectAsync((_) {}, count: 0)).pause();
+        queue2.rest.listen(expectAsync1((_) {}, count: 0)).pause();
 
         expect(await queue1.next, 1);
         expect(await queue1.next, 2);
@@ -1009,7 +1009,7 @@ main() {
         var queue1 = new StreamQueue<int>(createStream());
         var queue2 = queue1.fork();
 
-        queue1.rest.listen(expectAsync((_) {}, count: 0)).pause();
+        queue1.rest.listen(expectAsync1((_) {}, count: 0)).pause();
 
         expect(await queue2.next, 1);
         expect(await queue2.next, 2);
diff --git a/test/utils.dart b/test/utils.dart
index 19ac4970..74833a4f 100644
--- a/test/utils.dart
+++ b/test/utils.dart
@@ -35,7 +35,7 @@ State lastState;
 /// The most recent emitted state is stored in [_lastState].
 void expectStates(LiveTest liveTest, Iterable<State> statesIter) {
   var states = new Queue.from(statesIter);
-  liveTest.onStateChange.listen(expectAsync((state) {
+  liveTest.onStateChange.listen(expectAsync1((state) {
     lastState = state;
     expect(state, equals(states.removeFirst()));
   }, count: states.length, max: states.length));
@@ -45,7 +45,7 @@ void expectStates(LiveTest liveTest, Iterable<State> statesIter) {
 /// [validators], in order.
 void expectErrors(LiveTest liveTest, Iterable<Function> validatorsIter) {
   var validators = new Queue.from(validatorsIter);
-  liveTest.onError.listen(expectAsync((error) {
+  liveTest.onError.listen(expectAsync1((error) {
     validators.removeFirst()(error.error);
   }, count: validators.length, max: validators.length));
 }
-- 
GitLab