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 17ab86adfdba4f4363a4e41c5ea8f69dbfbeed3d..2a73e8eccd3ab0d1d7e75026f10541152556056b 100644 |
--- a/pkg/scheduled_test/lib/src/schedule.dart |
+++ b/pkg/scheduled_test/lib/src/schedule.dart |
@@ -60,7 +60,6 @@ class Schedule { |
ScheduleState _state = ScheduleState.SET_UP; |
// TODO(nweiz): make this a read-only view once issue 8321 is fixed. |
- |
/// Errors thrown by the task queues. |
/// |
/// When running tasks in [tasks], this will always be empty. If an error |
@@ -74,6 +73,10 @@ class Schedule { |
/// added to this list. |
final errors = <ScheduleError>[]; |
+ // TODO(nweiz): make this a read-only view once issue 8321 is fixed. |
+ /// Additional debugging info registered via [addDebugInfo]. |
+ final debugInfo = <String>[]; |
+ |
/// The task queue that's currently being run. One of [tasks], [onException], |
/// or [onComplete]. This starts as [tasks], and can only be `null` after the |
/// schedule is done. |
@@ -185,6 +188,12 @@ class Schedule { |
} |
} |
+ /// Adds [info] to the debugging output that will be printed if the test |
+ /// fails. Unlike [signalError], this won't cause the test to fail, nor will |
+ /// it short-circuit the current [TaskQueue]; it's just useful for providing |
+ /// additional information that may not fit cleanly into an existing error. |
+ void addDebugInfo(String info) => debugInfo.add(info); |
+ |
/// Notifies the schedule of an error that occurred in a task or out-of-band |
/// callback after the appropriate queue has timed out. If this schedule is |
/// still running, the error will be added to the errors list to be shown |
@@ -272,10 +281,18 @@ class Schedule { |
/// Returns a string representation of all errors registered on this schedule. |
String errorString() { |
if (errors.isEmpty) return "The schedule had no errors."; |
- if (errors.length == 1) return errors.first.toString(); |
- var errorStrings = errors.map((e) => e.toString()).join("\n================" |
- "================================================================\n"); |
- return "The schedule had ${errors.length} errors:\n$errorStrings"; |
+ if (errors.length == 1 && debugInfo.isEmpty) return errors.first.toString(); |
+ |
+ var border = "\n===========================================================" |
+ "=====================\n"; |
+ var errorStrings = errors.map((e) => e.toString()).join(border); |
+ var message = "The schedule had ${errors.length} errors:\n$errorStrings"; |
+ |
+ if (!debugInfo.isEmpty) { |
+ message = "$message$border\nDebug info:\n${debugInfo.join(border)}"; |
+ } |
+ |
+ return message; |
} |
/// Notifies the schedule that progress is being made on an asynchronous task. |
@@ -371,7 +388,20 @@ class TaskQueue { |
/// null if no task is currently running. |
SubstituteFuture _taskFuture; |
- TaskQueue._(this.name, this._schedule); |
+ /// A [Future] that completes when the tasks in [this] are all complete. If an |
+ /// error occurs while running this queue, the returned [Future] will complete |
+ /// with that error. |
+ /// |
+ /// The returned [Future] can complete before outstanding out-of-band |
+ /// callbacks have finished running. |
+ Future get onTasksComplete => _onTasksCompleteCompleter.future; |
+ final _onTasksCompleteCompleter = new Completer(); |
+ |
+ TaskQueue._(this.name, this._schedule) { |
+ // Avoid top-leveling errors that are passed to onTasksComplete if there are no |
Bob Nystrom
2013/03/04 23:52:00
Long line.
nweiz
2013/03/05 02:16:09
Done.
|
+ // listeners. |
+ onTasksComplete.catchError((_) {}); |
+ } |
/// Whether this queue is currently running. |
bool get isRunning => _schedule.state == ScheduleState.RUNNING && |
@@ -403,7 +433,11 @@ class TaskQueue { |
return task.runChild(wrappedFn, description); |
} |
- var task = new Task(fn, description, this); |
+ var task = new Task(() { |
+ return new Future.of(fn).catchError((e) { |
+ throw new ScheduleError.from(_schedule, e); |
+ }); |
+ }, description, this); |
_contents.add(task); |
return task.result; |
} |
@@ -421,14 +455,27 @@ class TaskQueue { |
_taskFuture = null; |
_schedule.heartbeat(); |
}).catchError((e) { |
- if (_error != null) _schedule._addError(_error); |
- throw new ScheduleError.from(_schedule, e); |
+ var error = new ScheduleError.from(_schedule, e); |
+ _signalError(error); |
+ throw _error; |
}); |
+ }).then((_) { |
+ _onTasksCompleteCompleter.complete(); |
+ }).catchError((e) { |
+ _onTasksCompleteCompleter.completeError(e); |
+ throw e; |
}).whenComplete(() { |
_schedule._currentTask = null; |
- return _schedule._awaitNoPendingCallbacks(); |
- }).then((_) { |
+ return _schedule._awaitNoPendingCallbacks().catchError((e) { |
+ // Signal the error rather than passing it through directly so that if a |
+ // timeout happens after an in-task error, both are reported. |
+ _signalError(new ScheduleError.from(_schedule, e)); |
+ }); |
+ }).whenComplete(() { |
_schedule.heartbeat(); |
+ // If the tasks were otherwise successful, make sure we throw any |
+ // out-of-band errors. If a task failed, make sure we throw the most |
+ // recent error. |
if (_error != null) throw _error; |
}); |
} |