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; |
| (...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 120 heartbeat(); | 120 heartbeat(); |
| 121 } | 121 } |
| 122 | 122 |
| 123 /// Sets up this schedule by running [setUp], then runs all the task queues in | 123 /// Sets up this schedule by running [setUp], then runs all the task queues in |
| 124 /// order. Any errors in [setUp] will cause [onException] to run. | 124 /// order. Any errors in [setUp] will cause [onException] to run. |
| 125 Future run(void setUp()) { | 125 Future run(void setUp()) { |
| 126 return new Future.immediate(null).then((_) { | 126 return new Future.immediate(null).then((_) { |
| 127 try { | 127 try { |
| 128 setUp(); | 128 setUp(); |
| 129 } catch (e, stackTrace) { | 129 } catch (e, stackTrace) { |
| 130 _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.
| |
| 130 throw new ScheduleError.from(this, e, stackTrace: stackTrace); | 131 throw new ScheduleError.from(this, e, stackTrace: stackTrace); |
| 131 } | 132 } |
| 132 | 133 |
| 133 _state = ScheduleState.RUNNING; | 134 _state = ScheduleState.RUNNING; |
| 134 return tasks._run(); | 135 return tasks._run(); |
| 135 }).catchError((e) { | 136 }).catchError((e) { |
| 136 errors.add(e); | 137 _addError(e); |
| 137 return onException._run().catchError((innerError) { | 138 return onException._run().catchError((innerError) { |
| 138 // If an error occurs in a task in the onException queue, make sure it's | 139 // If an error occurs in a task in the onException queue, make sure it's |
| 139 // registered in the error list and re-throw it. We could also re-throw | 140 // registered in the error list and re-throw it. We could also re-throw |
| 140 // `e`; ultimately, all the errors will be shown to the user if any | 141 // `e`; ultimately, all the errors will be shown to the user if any |
| 141 // ScheduleError is thrown. | 142 // ScheduleError is thrown. |
| 142 errors.add(innerError); | 143 _addError(innerError); |
| 143 throw innerError; | 144 throw innerError; |
| 144 }).then((_) { | 145 }).then((_) { |
| 145 // If there are no errors in the onException queue, re-throw the | 146 // If there are no errors in the onException queue, re-throw the |
| 146 // original error that caused it to run. | 147 // original error that caused it to run. |
| 147 throw e; | 148 throw e; |
| 148 }); | 149 }); |
| 149 }).whenComplete(() { | 150 }).whenComplete(() { |
| 150 return onComplete._run().catchError((e) { | 151 return onComplete._run().catchError((e) { |
| 151 // If an error occurs in a task in the onComplete queue, make sure it's | 152 // If an error occurs in a task in the onComplete queue, make sure it's |
| 152 // registered in the error list and re-throw it. | 153 // registered in the error list and re-throw it. |
| 153 errors.add(e); | 154 _addError(e); |
| 154 throw e; | 155 throw e; |
| 155 }); | 156 }); |
| 156 }).whenComplete(() { | 157 }).whenComplete(() { |
| 157 if (_timeoutTimer != null) _timeoutTimer.cancel(); | 158 if (_timeoutTimer != null) _timeoutTimer.cancel(); |
| 158 _state = ScheduleState.DONE; | 159 _state = ScheduleState.DONE; |
| 159 }); | 160 }); |
| 160 } | 161 } |
| 161 | 162 |
| 162 /// Signals that an out-of-band error has occurred. Using [wrapAsync] along | 163 /// Signals that an out-of-band error has occurred. Using [wrapAsync] along |
| 163 /// with `throw` is usually preferable to calling this directly. | 164 /// with `throw` is usually preferable to calling this directly. |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 182 } | 183 } |
| 183 } | 184 } |
| 184 | 185 |
| 185 /// Notifies the schedule of an error that occurred in a task or out-of-band | 186 /// Notifies the schedule of an error that occurred in a task or out-of-band |
| 186 /// callback after the appropriate queue has timed out. If this schedule is | 187 /// callback after the appropriate queue has timed out. If this schedule is |
| 187 /// still running, the error will be added to the errors list to be shown | 188 /// still running, the error will be added to the errors list to be shown |
| 188 /// along with the timeout error; otherwise, a top-level error will be thrown. | 189 /// along with the timeout error; otherwise, a top-level error will be thrown. |
| 189 void _signalPostTimeoutError(error, [stackTrace]) { | 190 void _signalPostTimeoutError(error, [stackTrace]) { |
| 190 var scheduleError = new ScheduleError.from(this, error, | 191 var scheduleError = new ScheduleError.from(this, error, |
| 191 stackTrace: stackTrace); | 192 stackTrace: stackTrace); |
| 192 errors.add(scheduleError); | 193 _addError(scheduleError); |
| 193 if (_state == ScheduleState.DONE) { | 194 if (_state == ScheduleState.DONE) { |
| 194 throw new StateError( | 195 throw new StateError( |
| 195 "An out-of-band error was caught after the test timed out.\n" | 196 "An out-of-band error was caught after the test timed out.\n" |
| 196 "${errorString()}"); | 197 "${errorString()}"); |
| 197 } | 198 } |
| 198 } | 199 } |
| 199 | 200 |
| 200 /// Returns a function wrapping [fn] that pipes any errors into the schedule | 201 /// Returns a function wrapping [fn] that pipes any errors into the schedule |
| 201 /// chain. This will also block the current task queue from completing until | 202 /// chain. This will also block the current task queue from completing until |
| 202 /// the returned function has been called. It's used to ensure that | 203 /// the returned function has been called. It's used to ensure that |
| 203 /// out-of-band callbacks are properly handled by the scheduled test. | 204 /// out-of-band callbacks are properly handled by the scheduled test. |
| 204 /// | 205 /// |
| 205 /// The top-level `wrapAsync` function should usually be used in preference to | 206 /// The top-level `wrapAsync` function should usually be used in preference to |
| 206 /// this. | 207 /// this in test code. |
| 207 Function wrapAsync(fn(arg)) { | 208 Function wrapAsync(fn(arg)) { |
| 208 if (_state == ScheduleState.DONE) { | 209 if (_state == ScheduleState.DONE) { |
| 209 throw new StateError("wrapAsync called after the schedule has finished " | 210 throw new StateError("wrapAsync called after the schedule has finished " |
| 210 "running."); | 211 "running."); |
| 211 } | 212 } |
| 212 heartbeat(); | 213 heartbeat(); |
| 213 | 214 |
| 214 var queue = currentQueue; | 215 var queue = currentQueue; |
| 215 // It's possible that the queue timed out before this. | 216 // It's possible that the queue timed out before this. |
| 216 bool _timedOut() => queue != currentQueue || _pendingCallbacks == 0; | 217 bool _timedOut() => queue != currentQueue || _pendingCallbacks == 0; |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 230 | 231 |
| 231 _pendingCallbacks--; | 232 _pendingCallbacks--; |
| 232 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) { | 233 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) { |
| 233 _noPendingCallbacks.complete(); | 234 _noPendingCallbacks.complete(); |
| 234 _noPendingCallbacks = null; | 235 _noPendingCallbacks = null; |
| 235 } | 236 } |
| 236 } | 237 } |
| 237 }; | 238 }; |
| 238 } | 239 } |
| 239 | 240 |
| 241 /// Like [wrapAsync], this ensures that the current task queue waits for | |
| 242 /// out-of-band asynchronous code, and that errors raised in that code are | |
| 243 /// handled correctly. However, [wrapFuture] wraps a [Future] chain rather | |
| 244 /// than a single callback. | |
| 245 /// | |
| 246 /// The returned [Future] completes to the same value or error as [future]. | |
| 247 /// | |
| 248 /// The top-level `wrapFuture` function should usually be used in preference | |
| 249 /// to this in test code. | |
| 250 Future wrapFuture(Future future) { | |
| 251 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.
| |
| 252 done() => new Future.immediate(null).then(doneCb); | |
| 253 | |
| 254 future = future.then((result) { | |
| 255 done(); | |
| 256 return result; | |
| 257 }).catchError((e) { | |
| 258 signalError(e); | |
| 259 done(); | |
| 260 throw e; | |
| 261 }); | |
| 262 | |
| 263 // Don't top-level the error, since it's already been signaled to the | |
| 264 // schedule. | |
| 265 future.catchError((_) => null); | |
| 266 | |
| 267 return future; | |
| 268 } | |
| 269 | |
| 240 /// Returns a string representation of all errors registered on this schedule. | 270 /// Returns a string representation of all errors registered on this schedule. |
| 241 String errorString() { | 271 String errorString() { |
| 242 if (errors.isEmpty) return "The schedule had no errors."; | 272 if (errors.isEmpty) return "The schedule had no errors."; |
| 243 if (errors.length == 1) return errors.first.toString(); | 273 if (errors.length == 1) return errors.first.toString(); |
| 244 var errorStrings = errors.map((e) => e.toString()).join("\n================" | 274 var errorStrings = errors.map((e) => e.toString()).join("\n================" |
| 245 "================================================================\n"); | 275 "================================================================\n"); |
| 246 return "The schedule had ${errors.length} errors:\n$errorStrings"; | 276 return "The schedule had ${errors.length} errors:\n$errorStrings"; |
| 247 } | 277 } |
| 248 | 278 |
| 249 /// Notifies the schedule that progress is being made on an asynchronous task. | 279 /// Notifies the schedule that progress is being made on an asynchronous task. |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 278 } | 308 } |
| 279 } | 309 } |
| 280 | 310 |
| 281 /// Returns a [Future] that will complete once there are no pending | 311 /// Returns a [Future] that will complete once there are no pending |
| 282 /// out-of-band callbacks. | 312 /// out-of-band callbacks. |
| 283 Future _awaitNoPendingCallbacks() { | 313 Future _awaitNoPendingCallbacks() { |
| 284 if (_pendingCallbacks == 0) return new Future.immediate(null); | 314 if (_pendingCallbacks == 0) return new Future.immediate(null); |
| 285 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer(); | 315 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer(); |
| 286 return _noPendingCallbacks.future; | 316 return _noPendingCallbacks.future; |
| 287 } | 317 } |
| 318 | |
| 319 /// Register an error in the schedule's error list. This ensures that there | |
| 320 /// are no duplicate errors, and that all errors are wrapped in | |
| 321 /// [ScheduleError]. | |
| 322 void _addError(error) { | |
| 323 if (errors.contains(error)) return; | |
| 324 errors.add(new ScheduleError.from(this, error)); | |
| 325 } | |
| 288 } | 326 } |
| 289 | 327 |
| 290 /// An enum of states for a [Schedule]. | 328 /// An enum of states for a [Schedule]. |
| 291 class ScheduleState { | 329 class ScheduleState { |
| 292 /// The schedule can have tasks added to its queue, but is not yet running | 330 /// The schedule can have tasks added to its queue, but is not yet running |
| 293 /// them. | 331 /// them. |
| 294 static const SET_UP = const ScheduleState._("SET_UP"); | 332 static const SET_UP = const ScheduleState._("SET_UP"); |
| 295 | 333 |
| 296 /// The schedule is actively running tasks. This includes running tasks in | 334 /// The schedule is actively running tasks. This includes running tasks in |
| 297 /// [Schedule.onException] and [Schedule.onComplete]. | 335 /// [Schedule.onException] and [Schedule.onComplete]. |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 326 /// indicates that the queue should stop as soon as possible and re-throw this | 364 /// indicates that the queue should stop as soon as possible and re-throw this |
| 327 /// error. | 365 /// error. |
| 328 ScheduleError _error; | 366 ScheduleError _error; |
| 329 | 367 |
| 330 /// The [SubstituteFuture] for the currently-running task in the queue, or | 368 /// The [SubstituteFuture] for the currently-running task in the queue, or |
| 331 /// null if no task is currently running. | 369 /// null if no task is currently running. |
| 332 SubstituteFuture _taskFuture; | 370 SubstituteFuture _taskFuture; |
| 333 | 371 |
| 334 TaskQueue._(this.name, this._schedule); | 372 TaskQueue._(this.name, this._schedule); |
| 335 | 373 |
| 374 /// Whether this queue is currently running. | |
| 375 bool get isRunning => _schedule.state == ScheduleState.RUNNING && | |
| 376 _schedule.currentQueue == this; | |
|
Bob Nystrom
2013/02/19 23:15:04
Indent +2.
nweiz
2013/02/20 00:23:12
Done.
| |
| 377 | |
| 336 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks | 378 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks |
| 337 /// will be run in the order they're scheduled. In [fn] returns a [Future], | 379 /// will be run in the order they're scheduled. In [fn] returns a [Future], |
| 338 /// tasks after it won't be run until that [Future] completes. | 380 /// tasks after it won't be run until that [Future] completes. |
| 339 /// | 381 /// |
| 340 /// The return value will be completed once the scheduled task has finished | 382 /// The return value will be completed once the scheduled task has finished |
| 341 /// running. Its return value is the same as the return value of [fn], or the | 383 /// running. Its return value is the same as the return value of [fn], or the |
| 342 /// value it completes to if it's a [Future]. | 384 /// value it completes to if it's a [Future]. |
| 343 /// | 385 /// |
| 344 /// If [description] is passed, it's used to describe the task for debugging | 386 /// If [description] is passed, it's used to describe the task for debugging |
| 345 /// purposes when an error occurs. | 387 /// purposes when an error occurs. |
| 388 /// | |
| 389 /// If this is called when this queue is currently running, it will run [fn] | |
| 390 /// on the next event loop iteration rather than adding it to a queue. The | |
| 391 /// current task will not complete until [fn] (and any [Future] it returns) | |
| 392 /// has finished running. Any errors in [fn] will automatically be handled. | |
| 346 Future schedule(fn(), [String description]) { | 393 Future schedule(fn(), [String description]) { |
| 347 var task = new Task(fn, this, description); | 394 if (isRunning) { |
| 395 var task = _schedule.currentTask; | |
| 396 var wrappedFn = () => _schedule.wrapFuture( | |
| 397 new Future.immediate(null).then((_) => fn())); | |
| 398 if (task == null) return wrappedFn(); | |
| 399 return task.runChild(wrappedFn, description); | |
| 400 } | |
| 401 | |
| 402 var task = new Task(fn, description, this); | |
| 348 _contents.add(task); | 403 _contents.add(task); |
| 349 return task.result; | 404 return task.result; |
| 350 } | 405 } |
| 351 | 406 |
| 352 /// Runs all the tasks in this queue in order. | 407 /// Runs all the tasks in this queue in order. |
| 353 Future _run() { | 408 Future _run() { |
| 354 _schedule._currentQueue = this; | 409 _schedule._currentQueue = this; |
| 355 _schedule.heartbeat(); | 410 _schedule.heartbeat(); |
| 356 return Future.forEach(_contents, (task) { | 411 return Future.forEach(_contents, (task) { |
| 357 _schedule._currentTask = task; | 412 _schedule._currentTask = task; |
| 358 if (_error != null) throw _error; | 413 if (_error != null) throw _error; |
| 359 | 414 |
| 360 _taskFuture = new SubstituteFuture(task.fn()); | 415 _taskFuture = new SubstituteFuture(task.fn()); |
| 361 return _taskFuture.whenComplete(() { | 416 return _taskFuture.whenComplete(() { |
| 362 _taskFuture = null; | 417 _taskFuture = null; |
| 363 _schedule.heartbeat(); | 418 _schedule.heartbeat(); |
| 364 }).catchError((e) { | 419 }).catchError((e) { |
| 365 if (_error != null) _schedule.errors.add(_error); | 420 if (_error != null) _schedule._addError(_error); |
| 366 throw new ScheduleError.from(_schedule, e); | 421 throw new ScheduleError.from(_schedule, e); |
| 367 }); | 422 }); |
| 368 }).whenComplete(() { | 423 }).whenComplete(() { |
| 369 _schedule._currentTask = null; | 424 _schedule._currentTask = null; |
| 370 return _schedule._awaitNoPendingCallbacks(); | 425 return _schedule._awaitNoPendingCallbacks(); |
| 371 }).then((_) { | 426 }).then((_) { |
| 372 _schedule.heartbeat(); | 427 _schedule.heartbeat(); |
| 373 if (_error != null) throw _error; | 428 if (_error != null) throw _error; |
| 374 }); | 429 }); |
| 375 } | 430 } |
| 376 | 431 |
| 377 /// Signals that an out-of-band error has been detected and the queue should | 432 /// Signals that an out-of-band error has been detected and the queue should |
| 378 /// stop running as soon as possible. | 433 /// stop running as soon as possible. |
| 379 void _signalError(ScheduleError error) { | 434 void _signalError(ScheduleError error) { |
| 380 // If multiple errors are detected while a task is running, make sure the | 435 // If multiple errors are detected while a task is running, make sure the |
| 381 // earlier ones are recorded in the schedule. | 436 // earlier ones are recorded in the schedule. |
| 382 if (_error != null) _schedule.errors.add(_error); | 437 if (_error != null) _schedule._addError(_error); |
| 383 _error = error; | 438 _error = error; |
| 384 } | 439 } |
| 385 | 440 |
| 386 /// Notifies the queue that it has timed out and it needs to terminate | 441 /// Notifies the queue that it has timed out and it needs to terminate |
| 387 /// immediately with a timeout error. | 442 /// immediately with a timeout error. |
| 388 void _signalTimeout(ScheduleError error) { | 443 void _signalTimeout(ScheduleError error) { |
| 389 if (_taskFuture != null) { | 444 if (_taskFuture != null) { |
| 390 // Catch errors coming off the old task future, in case it completes after | 445 // Catch errors coming off the old task future, in case it completes after |
| 391 // timing out. | 446 // timing out. |
| 392 _taskFuture.substitute(new Future.immediateError(error)).catchError((e) { | 447 _taskFuture.substitute(new Future.immediateError(error)).catchError((e) { |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 411 return _contents.map((task) { | 466 return _contents.map((task) { |
| 412 var lines = task.toString().split("\n"); | 467 var lines = task.toString().split("\n"); |
| 413 var firstLine = task == highlight ? | 468 var firstLine = task == highlight ? |
| 414 "> ${lines.first}" : "* ${lines.first}"; | 469 "> ${lines.first}" : "* ${lines.first}"; |
| 415 lines = new List.from(lines.skip(1).map((line) => "| $line")); | 470 lines = new List.from(lines.skip(1).map((line) => "| $line")); |
| 416 lines.insertRange(0, 1, firstLine); | 471 lines.insertRange(0, 1, firstLine); |
| 417 return lines.join("\n"); | 472 return lines.join("\n"); |
| 418 }).join("\n"); | 473 }).join("\n"); |
| 419 } | 474 } |
| 420 } | 475 } |
| OLD | NEW |