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 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
53 /// such task. This will be `null` both before the schedule starts running and | 53 /// such task. This will be `null` both before the schedule starts running and |
54 /// after it's finished. | 54 /// after it's finished. |
55 Task get currentTask => _currentTask; | 55 Task get currentTask => _currentTask; |
56 Task _currentTask; | 56 Task _currentTask; |
57 | 57 |
58 /// The current state of the schedule. | 58 /// The current state of the schedule. |
59 ScheduleState get state => _state; | 59 ScheduleState get state => _state; |
60 ScheduleState _state = ScheduleState.SET_UP; | 60 ScheduleState _state = ScheduleState.SET_UP; |
61 | 61 |
62 // TODO(nweiz): make this a read-only view once issue 8321 is fixed. | 62 // TODO(nweiz): make this a read-only view once issue 8321 is fixed. |
63 | |
64 /// Errors thrown by the task queues. | 63 /// Errors thrown by the task queues. |
65 /// | 64 /// |
66 /// When running tasks in [tasks], this will always be empty. If an error | 65 /// 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] | 66 /// 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 | 67 /// 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 | 68 /// 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 | 69 /// also be added to this list, although no scheduled tasks will be run |
71 /// afterwards. | 70 /// afterwards. |
72 /// | 71 /// |
73 /// Any out-of-band callbacks that throw errors will also have those errors | 72 /// Any out-of-band callbacks that throw errors will also have those errors |
74 /// added to this list. | 73 /// added to this list. |
75 final errors = <ScheduleError>[]; | 74 final errors = <ScheduleError>[]; |
76 | 75 |
76 // TODO(nweiz): make this a read-only view once issue 8321 is fixed. | |
77 /// Additional debugging info registered via [addDebugInfo]. | |
78 final debugInfo = <String>[]; | |
79 | |
77 /// The task queue that's currently being run. One of [tasks], [onException], | 80 /// The task queue that's currently being run. One of [tasks], [onException], |
78 /// or [onComplete]. This starts as [tasks], and can only be `null` after the | 81 /// or [onComplete]. This starts as [tasks], and can only be `null` after the |
79 /// schedule is done. | 82 /// schedule is done. |
80 TaskQueue get currentQueue => | 83 TaskQueue get currentQueue => |
81 _state == ScheduleState.DONE ? null : _currentQueue; | 84 _state == ScheduleState.DONE ? null : _currentQueue; |
82 TaskQueue _currentQueue; | 85 TaskQueue _currentQueue; |
83 | 86 |
84 /// The time to wait before terminating a task queue for inactivity. Defaults | 87 /// The time to wait before terminating a task queue for inactivity. Defaults |
85 /// to 30 seconds. This can be set to `null` to disable timeouts entirely. | 88 /// to 30 seconds. This can be set to `null` to disable timeouts entirely. |
86 /// | 89 /// |
(...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
178 "${errorString()}"); | 181 "${errorString()}"); |
179 } else if (state == ScheduleState.SET_UP) { | 182 } else if (state == ScheduleState.SET_UP) { |
180 // If we're setting up, throwing the error will pipe it into the main | 183 // If we're setting up, throwing the error will pipe it into the main |
181 // error-handling code. | 184 // error-handling code. |
182 throw scheduleError; | 185 throw scheduleError; |
183 } else { | 186 } else { |
184 _currentQueue._signalError(scheduleError); | 187 _currentQueue._signalError(scheduleError); |
185 } | 188 } |
186 } | 189 } |
187 | 190 |
191 /// Adds [info] to the debugging output that will be printed if the test | |
192 /// fails. Unlike [signalError], this won't cause the test to fail, nor will | |
193 /// it short-circuit the current [TaskQueue]; it's just useful for providing | |
194 /// additional information that may not fit cleanly into an existing error. | |
195 void addDebugInfo(String info) => debugInfo.add(info); | |
196 | |
188 /// Notifies the schedule of an error that occurred in a task or out-of-band | 197 /// Notifies the schedule of an error that occurred in a task or out-of-band |
189 /// callback after the appropriate queue has timed out. If this schedule is | 198 /// callback after the appropriate queue has timed out. If this schedule is |
190 /// still running, the error will be added to the errors list to be shown | 199 /// still running, the error will be added to the errors list to be shown |
191 /// along with the timeout error; otherwise, a top-level error will be thrown. | 200 /// along with the timeout error; otherwise, a top-level error will be thrown. |
192 void _signalPostTimeoutError(error, [stackTrace]) { | 201 void _signalPostTimeoutError(error, [stackTrace]) { |
193 var scheduleError = new ScheduleError.from(this, error, | 202 var scheduleError = new ScheduleError.from(this, error, |
194 stackTrace: stackTrace); | 203 stackTrace: stackTrace); |
195 _addError(scheduleError); | 204 _addError(scheduleError); |
196 if (_state == ScheduleState.DONE) { | 205 if (_state == ScheduleState.DONE) { |
197 throw new StateError( | 206 throw new StateError( |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
265 // Don't top-level the error, since it's already been signaled to the | 274 // Don't top-level the error, since it's already been signaled to the |
266 // schedule. | 275 // schedule. |
267 future.catchError((_) => null); | 276 future.catchError((_) => null); |
268 | 277 |
269 return future; | 278 return future; |
270 } | 279 } |
271 | 280 |
272 /// Returns a string representation of all errors registered on this schedule. | 281 /// Returns a string representation of all errors registered on this schedule. |
273 String errorString() { | 282 String errorString() { |
274 if (errors.isEmpty) return "The schedule had no errors."; | 283 if (errors.isEmpty) return "The schedule had no errors."; |
275 if (errors.length == 1) return errors.first.toString(); | 284 if (errors.length == 1 && debugInfo.isEmpty) return errors.first.toString(); |
276 var errorStrings = errors.map((e) => e.toString()).join("\n================" | 285 |
277 "================================================================\n"); | 286 var border = "\n===========================================================" |
278 return "The schedule had ${errors.length} errors:\n$errorStrings"; | 287 "=====================\n"; |
288 var errorStrings = errors.map((e) => e.toString()).join(border); | |
289 var message = "The schedule had ${errors.length} errors:\n$errorStrings"; | |
290 | |
291 if (!debugInfo.isEmpty) { | |
292 message = "$message$border\nDebug info:\n${debugInfo.join(border)}"; | |
293 } | |
294 | |
295 return message; | |
279 } | 296 } |
280 | 297 |
281 /// Notifies the schedule that progress is being made on an asynchronous task. | 298 /// Notifies the schedule that progress is being made on an asynchronous task. |
282 /// This resets the timeout timer, and can be used in long-running tasks to | 299 /// This resets the timeout timer, and can be used in long-running tasks to |
283 /// keep them from timing out. | 300 /// keep them from timing out. |
284 void heartbeat() { | 301 void heartbeat() { |
285 if (_timeoutTimer != null) _timeoutTimer.cancel(); | 302 if (_timeoutTimer != null) _timeoutTimer.cancel(); |
286 if (_timeout == null) { | 303 if (_timeout == null) { |
287 _timeoutTimer = null; | 304 _timeoutTimer = null; |
288 } else { | 305 } else { |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
364 | 381 |
365 /// An out-of-band error signaled by [_schedule]. If this is non-null, it | 382 /// An out-of-band error signaled by [_schedule]. If this is non-null, it |
366 /// indicates that the queue should stop as soon as possible and re-throw this | 383 /// indicates that the queue should stop as soon as possible and re-throw this |
367 /// error. | 384 /// error. |
368 ScheduleError _error; | 385 ScheduleError _error; |
369 | 386 |
370 /// The [SubstituteFuture] for the currently-running task in the queue, or | 387 /// The [SubstituteFuture] for the currently-running task in the queue, or |
371 /// null if no task is currently running. | 388 /// null if no task is currently running. |
372 SubstituteFuture _taskFuture; | 389 SubstituteFuture _taskFuture; |
373 | 390 |
374 TaskQueue._(this.name, this._schedule); | 391 /// A [Future] that completes when the tasks in [this] are all complete. If an |
392 /// error occurs while running this queue, the returned [Future] will complete | |
393 /// with that error. | |
394 /// | |
395 /// The returned [Future] can complete before outstanding out-of-band | |
396 /// callbacks have finished running. | |
397 Future get onTasksComplete => _onTasksCompleteCompleter.future; | |
398 final _onTasksCompleteCompleter = new Completer(); | |
399 | |
400 TaskQueue._(this.name, this._schedule) { | |
401 // 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.
| |
402 // listeners. | |
403 onTasksComplete.catchError((_) {}); | |
404 } | |
375 | 405 |
376 /// Whether this queue is currently running. | 406 /// Whether this queue is currently running. |
377 bool get isRunning => _schedule.state == ScheduleState.RUNNING && | 407 bool get isRunning => _schedule.state == ScheduleState.RUNNING && |
378 _schedule.currentQueue == this; | 408 _schedule.currentQueue == this; |
379 | 409 |
380 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks | 410 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks |
381 /// will be run in the order they're scheduled. In [fn] returns a [Future], | 411 /// will be run in the order they're scheduled. In [fn] returns a [Future], |
382 /// tasks after it won't be run until that [Future] completes. | 412 /// tasks after it won't be run until that [Future] completes. |
383 /// | 413 /// |
384 /// The return value will be completed once the scheduled task has finished | 414 /// The return value will be completed once the scheduled task has finished |
(...skipping 11 matching lines...) Expand all Loading... | |
396 /// top-level tasks which run in sequence. | 426 /// top-level tasks which run in sequence. |
397 Future schedule(fn(), [String description]) { | 427 Future schedule(fn(), [String description]) { |
398 if (isRunning) { | 428 if (isRunning) { |
399 var task = _schedule.currentTask; | 429 var task = _schedule.currentTask; |
400 var wrappedFn = () => _schedule.wrapFuture( | 430 var wrappedFn = () => _schedule.wrapFuture( |
401 new Future.immediate(null).then((_) => fn())); | 431 new Future.immediate(null).then((_) => fn())); |
402 if (task == null) return wrappedFn(); | 432 if (task == null) return wrappedFn(); |
403 return task.runChild(wrappedFn, description); | 433 return task.runChild(wrappedFn, description); |
404 } | 434 } |
405 | 435 |
406 var task = new Task(fn, description, this); | 436 var task = new Task(() { |
437 return new Future.of(fn).catchError((e) { | |
438 throw new ScheduleError.from(_schedule, e); | |
439 }); | |
440 }, description, this); | |
407 _contents.add(task); | 441 _contents.add(task); |
408 return task.result; | 442 return task.result; |
409 } | 443 } |
410 | 444 |
411 /// Runs all the tasks in this queue in order. | 445 /// Runs all the tasks in this queue in order. |
412 Future _run() { | 446 Future _run() { |
413 _schedule._currentQueue = this; | 447 _schedule._currentQueue = this; |
414 _schedule.heartbeat(); | 448 _schedule.heartbeat(); |
415 return Future.forEach(_contents, (task) { | 449 return Future.forEach(_contents, (task) { |
416 _schedule._currentTask = task; | 450 _schedule._currentTask = task; |
417 if (_error != null) throw _error; | 451 if (_error != null) throw _error; |
418 | 452 |
419 _taskFuture = new SubstituteFuture(task.fn()); | 453 _taskFuture = new SubstituteFuture(task.fn()); |
420 return _taskFuture.whenComplete(() { | 454 return _taskFuture.whenComplete(() { |
421 _taskFuture = null; | 455 _taskFuture = null; |
422 _schedule.heartbeat(); | 456 _schedule.heartbeat(); |
423 }).catchError((e) { | 457 }).catchError((e) { |
424 if (_error != null) _schedule._addError(_error); | 458 var error = new ScheduleError.from(_schedule, e); |
425 throw new ScheduleError.from(_schedule, e); | 459 _signalError(error); |
460 throw _error; | |
461 }); | |
462 }).then((_) { | |
463 _onTasksCompleteCompleter.complete(); | |
464 }).catchError((e) { | |
465 _onTasksCompleteCompleter.completeError(e); | |
466 throw e; | |
467 }).whenComplete(() { | |
468 _schedule._currentTask = null; | |
469 return _schedule._awaitNoPendingCallbacks().catchError((e) { | |
470 // Signal the error rather than passing it through directly so that if a | |
471 // timeout happens after an in-task error, both are reported. | |
472 _signalError(new ScheduleError.from(_schedule, e)); | |
426 }); | 473 }); |
427 }).whenComplete(() { | 474 }).whenComplete(() { |
428 _schedule._currentTask = null; | |
429 return _schedule._awaitNoPendingCallbacks(); | |
430 }).then((_) { | |
431 _schedule.heartbeat(); | 475 _schedule.heartbeat(); |
476 // If the tasks were otherwise successful, make sure we throw any | |
477 // out-of-band errors. If a task failed, make sure we throw the most | |
478 // recent error. | |
432 if (_error != null) throw _error; | 479 if (_error != null) throw _error; |
433 }); | 480 }); |
434 } | 481 } |
435 | 482 |
436 /// Signals that an out-of-band error has been detected and the queue should | 483 /// Signals that an out-of-band error has been detected and the queue should |
437 /// stop running as soon as possible. | 484 /// stop running as soon as possible. |
438 void _signalError(ScheduleError error) { | 485 void _signalError(ScheduleError error) { |
439 // If multiple errors are detected while a task is running, make sure the | 486 // If multiple errors are detected while a task is running, make sure the |
440 // earlier ones are recorded in the schedule. | 487 // earlier ones are recorded in the schedule. |
441 if (_error != null) _schedule._addError(_error); | 488 if (_error != null) _schedule._addError(_error); |
(...skipping 28 matching lines...) Expand all Loading... | |
470 return _contents.map((task) { | 517 return _contents.map((task) { |
471 var lines = task.toString().split("\n"); | 518 var lines = task.toString().split("\n"); |
472 var firstLine = task == highlight ? | 519 var firstLine = task == highlight ? |
473 "> ${lines.first}" : "* ${lines.first}"; | 520 "> ${lines.first}" : "* ${lines.first}"; |
474 lines = new List.from(lines.skip(1).map((line) => "| $line")); | 521 lines = new List.from(lines.skip(1).map((line) => "| $line")); |
475 lines.insertRange(0, 1, firstLine); | 522 lines.insertRange(0, 1, firstLine); |
476 return lines.join("\n"); | 523 return lines.join("\n"); |
477 }).join("\n"); | 524 }).join("\n"); |
478 } | 525 } |
479 } | 526 } |
OLD | NEW |