| Index: lib/src/backend/invoker.dart
|
| diff --git a/lib/src/backend/invoker.dart b/lib/src/backend/invoker.dart
|
| index 9788be827d0ef45c4d5a34f5f1a4668ca65ddc54..0dc42c9fa9fd4fe37d960f4df299d02fa1a8fa5f 100644
|
| --- a/lib/src/backend/invoker.dart
|
| +++ b/lib/src/backend/invoker.dart
|
| @@ -56,15 +56,28 @@ class Invoker {
|
| LiveTest get liveTest => _controller.liveTest;
|
| LiveTestController _controller;
|
|
|
| + bool get _closable => Zone.current[_closableKey];
|
| +
|
| + /// An opaque object used as a key in the zone value map to identify
|
| + /// [_closable].
|
| + ///
|
| + /// This is an instance variable to ensure that multiple invokers don't step
|
| + /// on one anothers' toes.
|
| + final _closableKey = new Object();
|
| +
|
| /// Whether the test has been closed.
|
| ///
|
| /// Once the test is closed, [expect] and [expectAsync] will throw
|
| /// [ClosedException]s whenever accessed to help the test stop executing as
|
| /// soon as possible.
|
| - bool get closed => _onCloseCompleter.isCompleted;
|
| + bool get closed => _closable && _onCloseCompleter.isCompleted;
|
|
|
| /// A future that completes once the test has been closed.
|
| - Future get onClose => _onCloseCompleter.future;
|
| + Future get onClose => _closable
|
| + ? _onCloseCompleter.future
|
| + // If we're in an unclosable block, return a future that will never
|
| + // complete.
|
| + : new Completer().future;
|
| final _onCloseCompleter = new Completer();
|
|
|
| /// The test being run.
|
| @@ -75,12 +88,19 @@ class Invoker {
|
|
|
| /// The outstanding callback counter for the current zone.
|
| OutstandingCallbackCounter get _outstandingCallbacks {
|
| - var counter = Zone.current[this];
|
| + var counter = Zone.current[_counterKey];
|
| if (counter != null) return counter;
|
| throw new StateError("Can't add or remove outstanding callbacks outside "
|
| "of a test body.");
|
| }
|
|
|
| + /// An opaque object used as a key in the zone value map to identify
|
| + /// [_outstandingCallbacks].
|
| + ///
|
| + /// This is an instance variable to ensure that multiple invokers don't step
|
| + /// on one anothers' toes.
|
| + final _counterKey = new Object();
|
| +
|
| /// The current invoker, or `null` if none is defined.
|
| ///
|
| /// An invoker is only set within the zone scope of a running test.
|
| @@ -158,14 +178,25 @@ class Invoker {
|
| new Future.sync(fn).then((_) => counter.removeOutstandingCallback());
|
| }, onError: _handleError);
|
| }, zoneValues: {
|
| - // Use the invoker as a key so that multiple invokers can have different
|
| - // outstanding callback counters at once.
|
| - this: counter
|
| + _counterKey: counter
|
| });
|
|
|
| return counter.noOutstandingCallbacks;
|
| }
|
|
|
| + /// Runs [fn] in a zone where [closed] is always `false`.
|
| + ///
|
| + /// This is useful for running code that should be able to register callbacks
|
| + /// and interact with the test framework normally even when the invoker is
|
| + /// closed, for example cleanup code.
|
| + unclosable(fn()) {
|
| + heartbeat();
|
| +
|
| + return runZoned(fn, zoneValues: {
|
| + _closableKey: false
|
| + });
|
| + }
|
| +
|
| /// Notifies the invoker that progress is being made.
|
| ///
|
| /// Each heartbeat resets the timeout timer. This helps ensure that
|
| @@ -247,7 +278,8 @@ class Invoker {
|
| #test.invoker: this,
|
| // Use the invoker as a key so that multiple invokers can have different
|
| // outstanding callback counters at once.
|
| - this: outstandingCallbacksForBody
|
| + _counterKey: outstandingCallbacksForBody,
|
| + _closableKey: true
|
| },
|
| zoneSpecification: new ZoneSpecification(
|
| print: (self, parent, zone, line) => _controller.print(line)),
|
|
|