Chromium Code Reviews| Index: pkg/scheduled_test/lib/src/schedule.dart |
| diff --git a/pkg/scheduled_test/lib/src/schedule.dart b/pkg/scheduled_test/lib/src/schedule.dart |
| index 58a14871418f67a2c4e3742d34b5cd49a23f1740..3ff53ac901741a85c8be5f07fe988239e364a48e 100644 |
| --- a/pkg/scheduled_test/lib/src/schedule.dart |
| +++ b/pkg/scheduled_test/lib/src/schedule.dart |
| @@ -127,19 +127,20 @@ class Schedule { |
| try { |
| setUp(); |
| } catch (e, stackTrace) { |
| + _state = ScheduleState.RUNNING; |
|
Bob Nystrom
2013/02/19 23:15:04
Document why you're setting this state even though
nweiz
2013/02/20 00:23:12
Done.
|
| throw new ScheduleError.from(this, e, stackTrace: stackTrace); |
| } |
| _state = ScheduleState.RUNNING; |
| return tasks._run(); |
| }).catchError((e) { |
| - errors.add(e); |
| + _addError(e); |
| return onException._run().catchError((innerError) { |
| // If an error occurs in a task in the onException queue, make sure it's |
| // registered in the error list and re-throw it. We could also re-throw |
| // `e`; ultimately, all the errors will be shown to the user if any |
| // ScheduleError is thrown. |
| - errors.add(innerError); |
| + _addError(innerError); |
| throw innerError; |
| }).then((_) { |
| // If there are no errors in the onException queue, re-throw the |
| @@ -150,7 +151,7 @@ class Schedule { |
| return onComplete._run().catchError((e) { |
| // If an error occurs in a task in the onComplete queue, make sure it's |
| // registered in the error list and re-throw it. |
| - errors.add(e); |
| + _addError(e); |
| throw e; |
| }); |
| }).whenComplete(() { |
| @@ -189,7 +190,7 @@ class Schedule { |
| void _signalPostTimeoutError(error, [stackTrace]) { |
| var scheduleError = new ScheduleError.from(this, error, |
| stackTrace: stackTrace); |
| - errors.add(scheduleError); |
| + _addError(scheduleError); |
| if (_state == ScheduleState.DONE) { |
| throw new StateError( |
| "An out-of-band error was caught after the test timed out.\n" |
| @@ -203,7 +204,7 @@ class Schedule { |
| /// out-of-band callbacks are properly handled by the scheduled test. |
| /// |
| /// The top-level `wrapAsync` function should usually be used in preference to |
| - /// this. |
| + /// this in test code. |
| Function wrapAsync(fn(arg)) { |
| if (_state == ScheduleState.DONE) { |
| throw new StateError("wrapAsync called after the schedule has finished " |
| @@ -237,6 +238,35 @@ class Schedule { |
| }; |
| } |
| + /// Like [wrapAsync], this ensures that the current task queue waits for |
| + /// out-of-band asynchronous code, and that errors raised in that code are |
| + /// handled correctly. However, [wrapFuture] wraps a [Future] chain rather |
| + /// than a single callback. |
| + /// |
| + /// The returned [Future] completes to the same value or error as [future]. |
| + /// |
| + /// The top-level `wrapFuture` function should usually be used in preference |
| + /// to this in test code. |
| + Future wrapFuture(Future future) { |
| + var doneCb = wrapAsync((_) => null); |
|
Bob Nystrom
2013/02/19 23:15:04
Cb -> Callback
Or just inline this function in th
nweiz
2013/02/20 00:23:12
Done.
|
| + done() => new Future.immediate(null).then(doneCb); |
| + |
| + future = future.then((result) { |
| + done(); |
| + return result; |
| + }).catchError((e) { |
| + signalError(e); |
| + done(); |
| + throw e; |
| + }); |
| + |
| + // Don't top-level the error, since it's already been signaled to the |
| + // schedule. |
| + future.catchError((_) => null); |
| + |
| + return future; |
| + } |
| + |
| /// Returns a string representation of all errors registered on this schedule. |
| String errorString() { |
| if (errors.isEmpty) return "The schedule had no errors."; |
| @@ -285,6 +315,14 @@ class Schedule { |
| if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer(); |
| return _noPendingCallbacks.future; |
| } |
| + |
| + /// Register an error in the schedule's error list. This ensures that there |
| + /// are no duplicate errors, and that all errors are wrapped in |
| + /// [ScheduleError]. |
| + void _addError(error) { |
| + if (errors.contains(error)) return; |
| + errors.add(new ScheduleError.from(this, error)); |
| + } |
| } |
| /// An enum of states for a [Schedule]. |
| @@ -333,6 +371,10 @@ class TaskQueue { |
| TaskQueue._(this.name, this._schedule); |
| + /// Whether this queue is currently running. |
| + bool get isRunning => _schedule.state == ScheduleState.RUNNING && |
| + _schedule.currentQueue == this; |
|
Bob Nystrom
2013/02/19 23:15:04
Indent +2.
nweiz
2013/02/20 00:23:12
Done.
|
| + |
| /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks |
| /// will be run in the order they're scheduled. In [fn] returns a [Future], |
| /// tasks after it won't be run until that [Future] completes. |
| @@ -343,8 +385,21 @@ class TaskQueue { |
| /// |
| /// If [description] is passed, it's used to describe the task for debugging |
| /// purposes when an error occurs. |
| + /// |
| + /// If this is called when this queue is currently running, it will run [fn] |
| + /// on the next event loop iteration rather than adding it to a queue. The |
| + /// current task will not complete until [fn] (and any [Future] it returns) |
| + /// has finished running. Any errors in [fn] will automatically be handled. |
| Future schedule(fn(), [String description]) { |
| - var task = new Task(fn, this, description); |
| + if (isRunning) { |
| + var task = _schedule.currentTask; |
| + var wrappedFn = () => _schedule.wrapFuture( |
| + new Future.immediate(null).then((_) => fn())); |
| + if (task == null) return wrappedFn(); |
| + return task.runChild(wrappedFn, description); |
| + } |
| + |
| + var task = new Task(fn, description, this); |
| _contents.add(task); |
| return task.result; |
| } |
| @@ -362,7 +417,7 @@ class TaskQueue { |
| _taskFuture = null; |
| _schedule.heartbeat(); |
| }).catchError((e) { |
| - if (_error != null) _schedule.errors.add(_error); |
| + if (_error != null) _schedule._addError(_error); |
| throw new ScheduleError.from(_schedule, e); |
| }); |
| }).whenComplete(() { |
| @@ -379,7 +434,7 @@ class TaskQueue { |
| void _signalError(ScheduleError error) { |
| // If multiple errors are detected while a task is running, make sure the |
| // earlier ones are recorded in the schedule. |
| - if (_error != null) _schedule.errors.add(_error); |
| + if (_error != null) _schedule._addError(_error); |
| _error = error; |
| } |