Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library schedule; | 5 library schedule; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 | 9 |
| 10 import 'package:unittest/unittest.dart' as unittest; | 10 import 'package:unittest/unittest.dart' as unittest; |
| 11 | 11 |
| 12 import 'schedule_error.dart'; | 12 import 'schedule_error.dart'; |
| 13 import 'task.dart'; | 13 import 'task.dart'; |
| 14 | 14 |
| 15 /// The schedule of tasks to run for a single test. This has three separate task | 15 /// The schedule of tasks to run for a single test. This has three separate task |
| 16 /// queues: [tasks], [onComplete], and [onException]. It also provides | 16 /// queues: [tasks], [onComplete], and [onException]. It also provides |
| 17 /// visibility into the current state of the schedule. | 17 /// visibility into the current state of the schedule. |
| 18 class Schedule { | 18 class Schedule { |
| 19 /// The main task queue for the schedule. These tasks are run before the other | 19 /// The main task queue for the schedule. These tasks are run before the other |
| 20 /// queues and generally constitute the main test body. | 20 /// queues and generally constitute the main test body. |
| 21 TaskQueue get tasks => _tasks; | 21 TaskQueue get tasks => _tasks; |
| 22 TaskQueue _tasks; | 22 TaskQueue _tasks; |
| 23 | 23 |
| 24 /// The queue of tasks to run if an error is caught while running [tasks]. The | 24 /// The queue of tasks to run if an error is caught while running [tasks]. The |
| 25 /// error will be available via [error]. These tasks won't be run if no error | 25 /// error will be available in [errors]. These tasks won't be run if no error |
| 26 /// occurs. Note that expectation failures count as errors. | 26 /// occurs. Note that expectation failures count as errors. |
| 27 /// | 27 /// |
| 28 /// This queue runs before [onComplete], and errors in [onComplete] will not | 28 /// This queue runs before [onComplete], and errors in [onComplete] will not |
| 29 /// cause this queue to be run. | 29 /// cause this queue to be run. |
| 30 /// | 30 /// |
| 31 /// If an error occurs in a task in this queue, all further tasks will be | 31 /// If an error occurs in a task in this queue, all further tasks will be |
| 32 /// skipped. | 32 /// skipped. |
| 33 TaskQueue get onException => _onException; | 33 TaskQueue get onException => _onException; |
| 34 TaskQueue _onException; | 34 TaskQueue _onException; |
| 35 | 35 |
| 36 /// The queue of tasks to run after [tasks] and possibly [onException] have | 36 /// The queue of tasks to run after [tasks] and possibly [onException] have |
| 37 /// run. This queue will run whether or not an error occurred. If one did, it | 37 /// run. This queue will run whether or not an error occurred. If one did, it |
| 38 /// will be available via [error]. Note that expectation failures count as | 38 /// will be available in [errors]. Note that expectation failures count as |
| 39 /// errors. | 39 /// errors. |
| 40 /// | 40 /// |
| 41 /// This queue runs after [onException]. If an error occurs while running | 41 /// This queue runs after [onException]. If an error occurs while running |
| 42 /// [onException], that error will be available via [error] in place of the | 42 /// [onException], that error will be available in [errors] after the original |
| 43 /// original error. | 43 /// error. |
| 44 /// | 44 /// |
| 45 /// If an error occurs in a task in this queue, all further tasks will be | 45 /// If an error occurs in a task in this queue, all further tasks will be |
| 46 /// skipped. | 46 /// skipped. |
| 47 TaskQueue get onComplete => _onComplete; | 47 TaskQueue get onComplete => _onComplete; |
| 48 TaskQueue _onComplete; | 48 TaskQueue _onComplete; |
| 49 | 49 |
| 50 /// Returns the [Task] that's currently executing, or `null` if there is no | 50 /// Returns the [Task] that's currently executing, or `null` if there is no |
| 51 /// such task. This will be `null` both before the schedule starts running and | 51 /// such task. This will be `null` both before the schedule starts running and |
| 52 /// after it's finished. | 52 /// after it's finished. |
| 53 Task get currentTask => _currentTask; | 53 Task get currentTask => _currentTask; |
| 54 Task _currentTask; | 54 Task _currentTask; |
| 55 | 55 |
| 56 /// Whether the schedule has finished running. This is only set once | 56 /// Whether the schedule has finished running. This is only set once |
| 57 /// [onComplete] has finished running. It will be set whether or not an | 57 /// [onComplete] has finished running. It will be set whether or not an |
| 58 /// exception has occurred. | 58 /// exception has occurred. |
| 59 bool get done => _done; | 59 bool get done => _done; |
| 60 bool _done = false; | 60 bool _done = false; |
| 61 | 61 |
| 62 /// The error thrown by the task queue. This will only be set while running | 62 // TODO(nweiz): make this a read-only view once issue 8321 is fixed. |
| 63 /// [onException] and [onComplete], since an error in [tasks] will cause it to | 63 |
| 64 /// terminate immediately. | 64 /// Errors thrown by the task queues. |
| 65 ScheduleError get error => _error; | 65 /// |
| 66 ScheduleError _error; | 66 /// When running tasks in [tasks], this will always be empty. If an error |
| 67 /// occurs in [tasks], it will be added to this list and then [onException] | |
| 68 /// will be run. If an error occurs there as well, it will be added to this | |
| 69 /// list and [onComplete] will be run. Errors thrown during [onComplete] will | |
| 70 /// also be added to this list, although no scheduled tasks will be run | |
| 71 /// afterwards. | |
| 72 /// | |
| 73 /// Any out-of-band callbacks that throw errors will also have those errors | |
| 74 /// added to this list. | |
| 75 final List<ScheduleError> errors = <ScheduleError>[]; | |
|
Bob Nystrom
2013/02/11 22:28:38
You can probably drop the type annotation here:
f
nweiz
2013/02/11 22:52:20
Done.
| |
| 67 | 76 |
| 68 /// The task queue that's currently being run, or `null` if there is no such | 77 /// The task queue that's currently being run, or `null` if there is no such |
| 69 /// queue. One of [tasks], [onException], or [onComplete]. This will be `null` | 78 /// queue. One of [tasks], [onException], or [onComplete]. This will be `null` |
| 70 /// before the schedule starts running. | 79 /// before the schedule starts running. |
| 71 TaskQueue get currentQueue => _done ? null : _currentQueue; | 80 TaskQueue get currentQueue => _done ? null : _currentQueue; |
| 72 TaskQueue _currentQueue; | 81 TaskQueue _currentQueue; |
| 73 | 82 |
| 74 /// The number of out-of-band callbacks that have been registered with | 83 /// The number of out-of-band callbacks that have been registered with |
| 75 /// [wrapAsync] but have yet to be called. | 84 /// [wrapAsync] but have yet to be called. |
| 76 int _pendingCallbacks = 0; | 85 int _pendingCallbacks = 0; |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 92 Future run(void setUp()) { | 101 Future run(void setUp()) { |
| 93 return new Future.immediate(null).then((_) { | 102 return new Future.immediate(null).then((_) { |
| 94 try { | 103 try { |
| 95 setUp(); | 104 setUp(); |
| 96 } catch (e, stackTrace) { | 105 } catch (e, stackTrace) { |
| 97 throw new ScheduleError.from(this, e, stackTrace: stackTrace); | 106 throw new ScheduleError.from(this, e, stackTrace: stackTrace); |
| 98 } | 107 } |
| 99 | 108 |
| 100 return tasks._run(); | 109 return tasks._run(); |
| 101 }).catchError((e) { | 110 }).catchError((e) { |
| 102 _error = e; | 111 errors.add(e); |
| 103 return onException._run().then((_) { | 112 return onException._run().catchError((e) { |
| 113 errors.add(e); | |
| 114 throw e; | |
| 115 }).then((_) { | |
| 104 throw e; | 116 throw e; |
|
Bob Nystrom
2013/02/11 22:28:38
Is the intent here that the Future returned from r
nweiz
2013/02/11 22:52:20
Done.
| |
| 105 }); | 117 }); |
| 106 }).whenComplete(() => onComplete._run()).whenComplete(() { | 118 }).whenComplete(() { |
| 119 return onComplete._run().catchError((e) { | |
| 120 errors.add(e); | |
| 121 throw e; | |
| 122 }); | |
| 123 }).whenComplete(() { | |
| 107 _done = true; | 124 _done = true; |
| 108 }); | 125 }); |
| 109 } | 126 } |
| 110 | 127 |
| 111 /// Signals that an out-of-band error has occurred. Using [wrapAsync] along | 128 /// Signals that an out-of-band error has occurred. Using [wrapAsync] along |
| 112 /// with `throw` is usually preferable to calling this directly. | 129 /// with `throw` is usually preferable to calling this directly. |
| 113 /// | 130 /// |
| 114 /// The metadata in [AsyncError]s and [ScheduleError]s will be preserved. | 131 /// The metadata in [AsyncError]s and [ScheduleError]s will be preserved. |
| 115 void signalError(error, [stackTrace]) { | 132 void signalError(error, [stackTrace]) { |
| 116 var scheduleError = new ScheduleError.from(this, error, | 133 var scheduleError = new ScheduleError.from(this, error, |
| 117 stackTrace: stackTrace, task: currentTask); | 134 stackTrace: stackTrace, task: currentTask); |
| 118 if (_done) { | 135 if (_done) { |
| 136 errors.add(scheduleError); | |
| 119 throw new StateError( | 137 throw new StateError( |
| 120 "An out-of-band error was signaled outside of wrapAsync after the " | 138 "An out-of-band error was signaled outside of wrapAsync after the " |
| 121 "schedule finished running:" | 139 "schedule finished running.\n" |
| 122 "${prefixLines(scheduleError.toString())}"); | 140 "${errorString()}"); |
| 123 } else if (currentQueue == null) { | 141 } else if (currentQueue == null) { |
| 124 // If we're not done but there's no current queue, that means we haven't | 142 // If we're not done but there's no current queue, that means we haven't |
| 125 // started yet and thus we're in setUp or the synchronous body of the | 143 // started yet and thus we're in setUp or the synchronous body of the |
| 126 // function. Throwing the error will thus pipe it into the main | 144 // function. Throwing the error will thus pipe it into the main |
| 127 // error-handling code. | 145 // error-handling code. |
| 128 throw scheduleError; | 146 throw scheduleError; |
| 129 } else { | 147 } else { |
| 130 _currentQueue._signalError(scheduleError); | 148 _currentQueue._signalError(scheduleError); |
| 131 } | 149 } |
| 132 } | 150 } |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 153 } finally { | 171 } finally { |
| 154 _pendingCallbacks--; | 172 _pendingCallbacks--; |
| 155 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) { | 173 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) { |
| 156 _noPendingCallbacks.complete(); | 174 _noPendingCallbacks.complete(); |
| 157 _noPendingCallbacks = null; | 175 _noPendingCallbacks = null; |
| 158 } | 176 } |
| 159 } | 177 } |
| 160 }; | 178 }; |
| 161 } | 179 } |
| 162 | 180 |
| 181 /// Returns a string representation of all errors registered on this schedule. | |
| 182 String errorString() { | |
| 183 if (errors.isEmpty) return "The schedule had no errors."; | |
| 184 if (errors.length == 1) return errors.first.toString(); | |
| 185 var errorStrings = errors.map((e) => e.toString()).join("\n================" | |
| 186 "================================================================\n"); | |
| 187 return "The schedule had ${errors.length} errors:\n$errorStrings"; | |
| 188 } | |
| 189 | |
| 163 /// Returns a [Future] that will complete once there are no pending | 190 /// Returns a [Future] that will complete once there are no pending |
| 164 /// out-of-band callbacks. | 191 /// out-of-band callbacks. |
| 165 Future _awaitNoPendingCallbacks() { | 192 Future _awaitNoPendingCallbacks() { |
| 166 if (_pendingCallbacks == 0) return new Future.immediate(null); | 193 if (_pendingCallbacks == 0) return new Future.immediate(null); |
| 167 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer(); | 194 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer(); |
| 168 return _noPendingCallbacks.future; | 195 return _noPendingCallbacks.future; |
| 169 } | 196 } |
| 170 } | 197 } |
| 171 | 198 |
| 172 /// A queue of asynchronous tasks to execute in order. | 199 /// A queue of asynchronous tasks to execute in order. |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 205 return task.result; | 232 return task.result; |
| 206 } | 233 } |
| 207 | 234 |
| 208 /// Runs all the tasks in this queue in order. | 235 /// Runs all the tasks in this queue in order. |
| 209 Future _run() { | 236 Future _run() { |
| 210 _schedule._currentQueue = this; | 237 _schedule._currentQueue = this; |
| 211 return Future.forEach(_contents, (task) { | 238 return Future.forEach(_contents, (task) { |
| 212 _schedule._currentTask = task; | 239 _schedule._currentTask = task; |
| 213 if (_error != null) throw _error; | 240 if (_error != null) throw _error; |
| 214 return task.fn().catchError((e) { | 241 return task.fn().catchError((e) { |
| 242 if (_error != null) _schedule.errors.add(_error); | |
| 215 throw new ScheduleError.from(_schedule, e, task: task); | 243 throw new ScheduleError.from(_schedule, e, task: task); |
| 216 }); | 244 }); |
| 217 }).whenComplete(() { | 245 }).whenComplete(() { |
| 218 _schedule._currentTask = null; | 246 _schedule._currentTask = null; |
| 219 return _schedule._awaitNoPendingCallbacks(); | 247 return _schedule._awaitNoPendingCallbacks(); |
| 220 }).then((_) { | 248 }).then((_) { |
| 221 if (_error != null) throw _error; | 249 if (_error != null) throw _error; |
| 222 }); | 250 }); |
| 223 } | 251 } |
| 224 | 252 |
| 225 /// Signals that an out-of-band error has been detected and the queue should | 253 /// Signals that an out-of-band error has been detected and the queue should |
| 226 /// stop running as soon as possible. | 254 /// stop running as soon as possible. |
| 227 void _signalError(ScheduleError error) { | 255 void _signalError(ScheduleError error) { |
| 256 // If multiple errors are detected while a task is running, make sure the | |
| 257 // earlier ones are recorded in the schedule. | |
| 258 if (_error != null) _schedule.errors.add(_error); | |
| 228 _error = error; | 259 _error = error; |
| 229 } | 260 } |
| 230 | 261 |
| 231 String toString() => name; | 262 String toString() => name; |
| 232 | 263 |
| 233 /// Returns a detailed representation of the queue as a tree of tasks. If | 264 /// Returns a detailed representation of the queue as a tree of tasks. If |
| 234 /// [highlight] is passed, that task is specially highlighted. | 265 /// [highlight] is passed, that task is specially highlighted. |
| 235 /// | 266 /// |
| 236 /// [highlight] must be a task in this queue. | 267 /// [highlight] must be a task in this queue. |
| 237 String generateTree([Task highlight]) { | 268 String generateTree([Task highlight]) { |
| 238 assert(highlight == null || highlight.queue == this); | 269 assert(highlight == null || highlight.queue == this); |
| 239 return _contents.map((task) { | 270 return _contents.map((task) { |
| 240 var lines = task.toString().split("\n"); | 271 var lines = task.toString().split("\n"); |
| 241 var firstLine = task == highlight ? | 272 var firstLine = task == highlight ? |
| 242 "> ${lines.first}" : "* ${lines.first}"; | 273 "> ${lines.first}" : "* ${lines.first}"; |
| 243 lines = new List.from(lines.skip(1).map((line) => "| $line")); | 274 lines = new List.from(lines.skip(1).map((line) => "| $line")); |
| 244 lines.insertRange(0, 1, firstLine); | 275 lines.insertRange(0, 1, firstLine); |
| 245 return lines.join("\n"); | 276 return lines.join("\n"); |
| 246 }).join("\n"); | 277 }).join("\n"); |
| 247 } | 278 } |
| 248 } | 279 } |
| OLD | NEW |