Index: lib/src/backend/invoker.dart |
diff --git a/lib/src/backend/invoker.dart b/lib/src/backend/invoker.dart |
index dfcc795e04f0749513945a01c07378f52eccc486..5b6eec6264a933d19da7d885c91924d2ca8fb100 100644 |
--- a/lib/src/backend/invoker.dart |
+++ b/lib/src/backend/invoker.dart |
@@ -92,6 +92,12 @@ class Invoker { |
"of a test body."); |
} |
+ /// All the zones created by [waitForOutstandingCallbacks], in the order they |
+ /// were created. |
+ /// |
+ /// This is used to throw timeout errors in the most recent zone. |
+ final _outstandingCallbackZones = <Zone>[]; |
+ |
/// An opaque object used as a key in the zone value map to identify |
/// [_outstandingCallbacks]. |
/// |
@@ -164,21 +170,30 @@ class Invoker { |
/// failed, removes all outstanding callbacks registered within [fn], and |
/// completes the returned future. It does not remove any outstanding |
/// callbacks registered outside of [fn]. |
+ /// |
+ /// If the test times out, the *most recent* call to |
+ /// [waitForOutstandingCallbacks] will treat that error as occurring within |
+ /// [fn]—that is, it will complete immediately. |
Future waitForOutstandingCallbacks(fn()) { |
heartbeat(); |
+ var zone; |
var counter = new OutstandingCallbackCounter(); |
runZoned(() { |
// TODO(nweiz): Use async/await here once issue 23497 has been fixed in |
// two stable versions. |
runZoned(() { |
+ zone = Zone.current; |
+ _outstandingCallbackZones.add(zone); |
new Future.sync(fn).then((_) => counter.removeOutstandingCallback()); |
}, onError: _handleError); |
}, zoneValues: { |
_counterKey: counter |
}); |
- return counter.noOutstandingCallbacks; |
+ return counter.noOutstandingCallbacks.whenComplete(() { |
+ _outstandingCallbackZones.remove(zone); |
+ }); |
} |
/// Runs [fn] in a zone where [closed] is always `false`. |
@@ -205,13 +220,14 @@ class Invoker { |
var timeout = liveTest.test.metadata.timeout |
.apply(new Duration(seconds: 30)); |
if (timeout == null) return; |
- _timeoutTimer = _invokerZone.createTimer(timeout, |
- Zone.current.bindCallback(() { |
- if (liveTest.isComplete) return; |
- _handleError( |
- new TimeoutException( |
- "Test timed out after ${niceDuration(timeout)}.", timeout)); |
- })); |
+ _timeoutTimer = _invokerZone.createTimer(timeout, () { |
+ _outstandingCallbackZones.last.run(() { |
+ if (liveTest.isComplete) return; |
+ _handleError( |
+ new TimeoutException( |
+ "Test timed out after ${niceDuration(timeout)}.", timeout)); |
+ }); |
+ }); |
} |
/// Notifies the invoker of an asynchronous error. |
@@ -251,8 +267,7 @@ class Invoker { |
Chain.capture(() { |
runZonedWithValues(() { |
_invokerZone = Zone.current; |
- |
- heartbeat(); |
+ _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 |