Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(38)

Side by Side Diff: pkg/scheduled_test/lib/src/schedule.dart

Issue 12637020: Display metadata about out-of-band callbacks in scheduled test errors. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: mege Created 7 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 83 matching lines...) Expand 10 before | Expand all | Expand 10 after
94 /// 94 ///
95 /// If a task times out and then later completes with an error, that error 95 /// If a task times out and then later completes with an error, that error
96 /// cannot be handled. The user will still be notified of it. 96 /// cannot be handled. The user will still be notified of it.
97 Duration get timeout => _timeout; 97 Duration get timeout => _timeout;
98 Duration _timeout = new Duration(seconds: 30); 98 Duration _timeout = new Duration(seconds: 30);
99 set timeout(Duration duration) { 99 set timeout(Duration duration) {
100 _timeout = duration; 100 _timeout = duration;
101 heartbeat(); 101 heartbeat();
102 } 102 }
103 103
104 /// The number of out-of-band callbacks that have been registered with
105 /// [wrapAsync] but have yet to be called.
106 int _pendingCallbacks = 0;
107
108 /// A completer that will be completed once [_pendingCallbacks] reaches zero.
109 /// This will only be non-`null` if [_awaitPendingCallbacks] has been called
110 /// while [_pendingCallbacks] is non-zero.
111 Completer _noPendingCallbacks;
112
113 /// The timer for keeping track of task timeouts. This may be null. 104 /// The timer for keeping track of task timeouts. This may be null.
114 Timer _timeoutTimer; 105 Timer _timeoutTimer;
115 106
116 /// Creates a new schedule with empty task queues. 107 /// Creates a new schedule with empty task queues.
117 Schedule() { 108 Schedule() {
118 _tasks = new TaskQueue._("tasks", this); 109 _tasks = new TaskQueue._("tasks", this);
119 _onComplete = new TaskQueue._("onComplete", this); 110 _onComplete = new TaskQueue._("onComplete", this);
120 _onException = new TaskQueue._("onException", this); 111 _onException = new TaskQueue._("onException", this);
121 _currentQueue = _tasks; 112 _currentQueue = _tasks;
122 113
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
207 "An out-of-band error was caught after the test timed out.\n" 198 "An out-of-band error was caught after the test timed out.\n"
208 "${errorString()}"); 199 "${errorString()}");
209 } 200 }
210 } 201 }
211 202
212 /// Returns a function wrapping [fn] that pipes any errors into the schedule 203 /// Returns a function wrapping [fn] that pipes any errors into the schedule
213 /// chain. This will also block the current task queue from completing until 204 /// chain. This will also block the current task queue from completing until
214 /// the returned function has been called. It's used to ensure that 205 /// the returned function has been called. It's used to ensure that
215 /// out-of-band callbacks are properly handled by the scheduled test. 206 /// out-of-band callbacks are properly handled by the scheduled test.
216 /// 207 ///
208 /// [description] provides an optional description of the callback, which is
209 /// used when generating error messages.
210 ///
217 /// The top-level `wrapAsync` function should usually be used in preference to 211 /// The top-level `wrapAsync` function should usually be used in preference to
218 /// this in test code. 212 /// this in test code.
219 Function wrapAsync(fn(arg)) { 213 Function wrapAsync(fn(arg), [String description]) {
220 if (_state == ScheduleState.DONE) { 214 if (_state == ScheduleState.DONE) {
221 throw new StateError("wrapAsync called after the schedule has finished " 215 throw new StateError("wrapAsync called after the schedule has finished "
222 "running."); 216 "running.");
223 } 217 }
224 heartbeat(); 218 heartbeat();
225 219
226 var queue = currentQueue; 220 return currentQueue._wrapAsync(fn, description);
227 // It's possible that the queue timed out before this.
228 bool _timedOut() => queue != currentQueue || _pendingCallbacks == 0;
229
230 _pendingCallbacks++;
231 return (arg) {
232 try {
233 return fn(arg);
234 } catch (e, stackTrace) {
235 if (_timedOut()) {
236 _signalPostTimeoutError(e, stackTrace);
237 } else {
238 signalError(e, stackTrace);
239 }
240 } finally {
241 if (_timedOut()) return;
242
243 _pendingCallbacks--;
244 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) {
245 _noPendingCallbacks.complete();
246 _noPendingCallbacks = null;
247 }
248 }
249 };
250 } 221 }
251 222
252 /// Like [wrapAsync], this ensures that the current task queue waits for 223 /// Like [wrapAsync], this ensures that the current task queue waits for
253 /// out-of-band asynchronous code, and that errors raised in that code are 224 /// out-of-band asynchronous code, and that errors raised in that code are
254 /// handled correctly. However, [wrapFuture] wraps a [Future] chain rather 225 /// handled correctly. However, [wrapFuture] wraps a [Future] chain rather
255 /// than a single callback. 226 /// than a single callback.
256 /// 227 ///
257 /// The returned [Future] completes to the same value or error as [future]. 228 /// The returned [Future] completes to the same value or error as [future].
258 /// 229 ///
230 /// [description] provides an optional description of the future, which is
231 /// used when generating error messages.
232 ///
259 /// The top-level `wrapFuture` function should usually be used in preference 233 /// The top-level `wrapFuture` function should usually be used in preference
260 /// to this in test code. 234 /// to this in test code.
261 Future wrapFuture(Future future) { 235 Future wrapFuture(Future future, [String description]) {
262 var doneCallback = wrapAsync((_) => null); 236 var done = wrapAsync((fn) => fn(), description);
263 done() => new Future.immediate(null).then(doneCallback);
264 237
265 future = future.then((result) { 238 future = future.then((result) => done(() => result)).catchError((e) {
266 done(); 239 done(() {
267 return result; 240 throw e;
268 }).catchError((e) { 241 });
269 signalError(e); 242 // wrapAsync will catch the first throw, so we throw [e] again so it
270 done(); 243 // propagates through the Future chain.
271 throw e; 244 throw e;
272 }); 245 });
273 246
274 // Don't top-level the error, since it's already been signaled to the 247 // Don't top-level the error, since it's already been signaled to the
275 // schedule. 248 // schedule.
276 future.catchError((_) => null); 249 future.catchError((_) => null);
277 250
278 return future; 251 return future;
279 } 252 }
280 253
(...skipping 15 matching lines...) Expand all
296 } 269 }
297 270
298 /// Notifies the schedule that progress is being made on an asynchronous task. 271 /// Notifies the schedule that progress is being made on an asynchronous task.
299 /// This resets the timeout timer, and can be used in long-running tasks to 272 /// This resets the timeout timer, and can be used in long-running tasks to
300 /// keep them from timing out. 273 /// keep them from timing out.
301 void heartbeat() { 274 void heartbeat() {
302 if (_timeoutTimer != null) _timeoutTimer.cancel(); 275 if (_timeoutTimer != null) _timeoutTimer.cancel();
303 if (_timeout == null) { 276 if (_timeout == null) {
304 _timeoutTimer = null; 277 _timeoutTimer = null;
305 } else { 278 } else {
306 _timeoutTimer = mock_clock.newTimer(_timeout, _signalTimeout); 279 _timeoutTimer = mock_clock.newTimer(_timeout, () {
280 _timeoutTimer = null;
281 currentQueue._signalTimeout(new ScheduleError.from(this, "The schedule "
282 "timed out after $_timeout of inactivity."));
283 });
307 } 284 }
308 } 285 }
309 286
310 /// The callback to run when the timeout timer fires. Notifies the current
311 /// queue that a timeout has occurred.
312 void _signalTimeout() {
313 // Reset the timer so that we can detect timeouts in the onException and
314 // onComplete queues.
315 _timeoutTimer = null;
316
317 var error = new ScheduleError.from(this, "The schedule timed out after "
318 "$_timeout of inactivity.");
319
320 _pendingCallbacks = 0;
321 if (_noPendingCallbacks != null) {
322 var noPendingCallbacks = _noPendingCallbacks;
323 _noPendingCallbacks = null;
324 noPendingCallbacks.completeError(error);
325 } else {
326 currentQueue._signalTimeout(error);
327 }
328 }
329
330 /// Returns a [Future] that will complete once there are no pending
331 /// out-of-band callbacks.
332 Future _awaitNoPendingCallbacks() {
333 if (_pendingCallbacks == 0) return new Future.immediate(null);
334 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer();
335 return _noPendingCallbacks.future;
336 }
337
338 /// Register an error in the schedule's error list. This ensures that there 287 /// Register an error in the schedule's error list. This ensures that there
339 /// are no duplicate errors, and that all errors are wrapped in 288 /// are no duplicate errors, and that all errors are wrapped in
340 /// [ScheduleError]. 289 /// [ScheduleError].
341 void _addError(error) { 290 void _addError(error) {
342 if (errors.contains(error)) return; 291 if (errors.contains(error)) return;
343 errors.add(new ScheduleError.from(this, error)); 292 errors.add(new ScheduleError.from(this, error));
344 } 293 }
345 } 294 }
346 295
347 /// An enum of states for a [Schedule]. 296 /// An enum of states for a [Schedule].
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
381 330
382 /// An out-of-band error signaled by [_schedule]. If this is non-null, it 331 /// An out-of-band error signaled by [_schedule]. If this is non-null, it
383 /// indicates that the queue should stop as soon as possible and re-throw this 332 /// indicates that the queue should stop as soon as possible and re-throw this
384 /// error. 333 /// error.
385 ScheduleError _error; 334 ScheduleError _error;
386 335
387 /// The [SubstituteFuture] for the currently-running task in the queue, or 336 /// The [SubstituteFuture] for the currently-running task in the queue, or
388 /// null if no task is currently running. 337 /// null if no task is currently running.
389 SubstituteFuture _taskFuture; 338 SubstituteFuture _taskFuture;
390 339
340 /// The toal number of out-of-band callbacks that have been registered on
341 /// [this].
342 int _totalCallbacks = 0;
343
344 // TODO(nweiz): make this a read-only view when issue 8321 is fixed.
345 /// The descriptions of all callbacks that are blocking the completion of
346 /// [this].
347 Collection<String> get pendingCallbacks => _pendingCallbacks;
348 final _pendingCallbacks = new Queue<String>();
349
350 /// A completer that will be completed once [_pendingCallbacks] becomes empty
351 /// after the queue finished running its tasks.
Bob Nystrom 2013/03/12 20:08:51 finished -> finishes.
nweiz 2013/03/12 20:56:51 Done.
352 Future get _noPendingCallbacks => _noPendingCallbacksCompleter.future;
353 final Completer _noPendingCallbacksCompleter = new Completer();
354
391 /// A [Future] that completes when the tasks in [this] are all complete. If an 355 /// 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 356 /// error occurs while running this queue, the returned [Future] will complete
393 /// with that error. 357 /// with that error.
394 /// 358 ///
395 /// The returned [Future] can complete before outstanding out-of-band 359 /// The returned [Future] can complete before outstanding out-of-band
396 /// callbacks have finished running. 360 /// callbacks have finished running.
397 Future get onTasksComplete => _onTasksCompleteCompleter.future; 361 Future get onTasksComplete => _onTasksCompleteCompleter.future;
398 final _onTasksCompleteCompleter = new Completer(); 362 final _onTasksCompleteCompleter = new Completer();
399 363
400 TaskQueue._(this.name, this._schedule) { 364 TaskQueue._(this.name, this._schedule) {
401 // Avoid top-leveling errors that are passed to onTasksComplete if there are 365 // Avoid top-leveling errors that are passed to onTasksComplete if there are
402 // no listeners. 366 // no listeners.
403 onTasksComplete.catchError((_) {}); 367 onTasksComplete.catchError((_) {});
404 } 368 }
405 369
406 /// Whether this queue is currently running. 370 /// Whether this queue is currently running.
407 bool get isRunning => _schedule.state == ScheduleState.RUNNING && 371 bool get isRunning => _schedule.state == ScheduleState.RUNNING &&
408 _schedule.currentQueue == this; 372 _schedule.currentQueue == this;
409 373
374 /// Whether this queue is running its tasks (as opposed to waiting for
375 /// out-of-band callbacks or not running at all).
376 bool get isRunningTasks => isRunning && _schedule.currentTask != null;
377
410 /// 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
411 /// 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],
412 /// tasks after it won't be run until that [Future] completes. 380 /// tasks after it won't be run until that [Future] completes.
413 /// 381 ///
414 /// 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
415 /// 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
416 /// value it completes to if it's a [Future]. 384 /// value it completes to if it's a [Future].
417 /// 385 ///
418 /// 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
419 /// purposes when an error occurs. 387 /// purposes when an error occurs.
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
452 420
453 _taskFuture = new SubstituteFuture(task.fn()); 421 _taskFuture = new SubstituteFuture(task.fn());
454 return _taskFuture.whenComplete(() { 422 return _taskFuture.whenComplete(() {
455 _taskFuture = null; 423 _taskFuture = null;
456 _schedule.heartbeat(); 424 _schedule.heartbeat();
457 }).catchError((e) { 425 }).catchError((e) {
458 var error = new ScheduleError.from(_schedule, e); 426 var error = new ScheduleError.from(_schedule, e);
459 _signalError(error); 427 _signalError(error);
460 throw _error; 428 throw _error;
461 }); 429 });
430 }).whenComplete(() {
431 _schedule._currentTask = null;
462 }).then((_) { 432 }).then((_) {
463 _onTasksCompleteCompleter.complete(); 433 _onTasksCompleteCompleter.complete();
464 }).catchError((e) { 434 }).catchError((e) {
465 _onTasksCompleteCompleter.completeError(e); 435 _onTasksCompleteCompleter.completeError(e);
466 throw e; 436 throw e;
467 }).whenComplete(() { 437 }).whenComplete(() {
468 _schedule._currentTask = null; 438 if (pendingCallbacks.isEmpty) return;
469 return _schedule._awaitNoPendingCallbacks().catchError((e) { 439 return _noPendingCallbacks.catchError((e) {
470 // Signal the error rather than passing it through directly so that if a 440 // Signal the error rather than passing it through directly so that if a
471 // timeout happens after an in-task error, both are reported. 441 // timeout happens after an in-task error, both are reported.
472 _signalError(new ScheduleError.from(_schedule, e)); 442 _signalError(new ScheduleError.from(_schedule, e));
473 }); 443 });
474 }).whenComplete(() { 444 }).whenComplete(() {
475 _schedule.heartbeat(); 445 _schedule.heartbeat();
476 // If the tasks were otherwise successful, make sure we throw any 446 // 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 447 // out-of-band errors. If a task failed, make sure we throw the most
478 // recent error. 448 // recent error.
479 if (_error != null) throw _error; 449 if (_error != null) throw _error;
480 }); 450 });
481 } 451 }
482 452
453 /// Returns a function wrapping [fn] that pipes any errors into the schedule
454 /// chain. This will also block [this] from completing until the returned
455 /// function has been called. It's used to ensure that out-of-band callbacks
456 /// are properly handled by the scheduled test.
457 Function _wrapAsync(fn(arg), String description) {
458 assert(isRunning);
459
460 // It's possible that the queue timed out before [fn] finished.
461 bool _timedOut() =>
462 _schedule.currentQueue != this || pendingCallbacks.isEmpty;
463
464 if (description == null) {
465 description = "Out-of-band operation #${_totalCallbacks}";
466 }
467 _totalCallbacks++;
468
469 _pendingCallbacks.add(description);;
Bob Nystrom 2013/03/12 20:08:51 ;;
nweiz 2013/03/12 20:56:51 Done.
470 return (arg) {
471 try {
472 return fn(arg);
473 } catch (e, stackTrace) {
474 var error = new ScheduleError.from(
475 _schedule, e, stackTrace: stackTrace);
476 if (_timedOut()) {
477 _schedule._signalPostTimeoutError(error);
478 } else {
479 _schedule.signalError(error);
480 }
481 } finally {
482 if (_timedOut()) return;
483
484 _pendingCallbacks.remove(description);
485 if (_pendingCallbacks.isEmpty && !isRunningTasks) {
486 _noPendingCallbacksCompleter.complete();
487 }
488 }
489 };
490 }
491
483 /// Signals that an out-of-band error has been detected and the queue should 492 /// Signals that an out-of-band error has been detected and the queue should
484 /// stop running as soon as possible. 493 /// stop running as soon as possible.
485 void _signalError(ScheduleError error) { 494 void _signalError(ScheduleError error) {
486 // If multiple errors are detected while a task is running, make sure the 495 // If multiple errors are detected while a task is running, make sure the
487 // earlier ones are recorded in the schedule. 496 // earlier ones are recorded in the schedule.
488 if (_error != null) _schedule._addError(_error); 497 if (_error != null) _schedule._addError(_error);
489 _error = error; 498 _error = error;
490 } 499 }
491 500
492 /// Notifies the queue that it has timed out and it needs to terminate 501 /// Notifies the queue that it has timed out and it needs to terminate
493 /// immediately with a timeout error. 502 /// immediately with a timeout error.
494 void _signalTimeout(ScheduleError error) { 503 void _signalTimeout(ScheduleError error) {
495 if (_taskFuture != null) { 504 _pendingCallbacks.clear();
505 if (!isRunningTasks) {
506 _noPendingCallbacksCompleter.completeError(error);
507 } else if (_taskFuture != null) {
496 // Catch errors coming off the old task future, in case it completes after 508 // Catch errors coming off the old task future, in case it completes after
497 // timing out. 509 // timing out.
498 _taskFuture.substitute(new Future.immediateError(error)).catchError((e) { 510 _taskFuture.substitute(new Future.immediateError(error)).catchError((e) {
499 _schedule._signalPostTimeoutError(e); 511 _schedule._signalPostTimeoutError(e);
500 }); 512 });
501 } else { 513 } else {
502 // This branch probably won't be reached, but it's conceivable that the 514 // This branch probably won't be reached, but it's conceivable that the
503 // event loop might get pumped when _taskFuture is null but we haven't yet 515 // event loop might get pumped when _taskFuture is null but we haven't yet
504 // called _awaitNoPendingCallbacks. 516 // finished running all the tasks.
505 _signalError(error); 517 _signalError(error);
506 } 518 }
507 } 519 }
508 520
509 String toString() => name; 521 String toString() => name;
510 522
511 /// Returns a detailed representation of the queue as a tree of tasks. If 523 /// Returns a detailed representation of the queue as a tree of tasks. If
512 /// [highlight] is passed, that task is specially highlighted. 524 /// [highlight] is passed, that task is specially highlighted.
513 /// 525 ///
514 /// [highlight] must be a task in this queue. 526 /// [highlight] must be a task in this queue.
515 String generateTree([Task highlight]) { 527 String generateTree([Task highlight]) {
516 assert(highlight == null || highlight.queue == this); 528 assert(highlight == null || highlight.queue == this);
517 return _contents.map((task) { 529 return _contents.map((task) {
518 var lines = task.toString().split("\n"); 530 var lines = task.toString().split("\n");
519 var firstLine = task == highlight ? 531 var firstLine = task == highlight ?
520 "> ${lines.first}" : "* ${lines.first}"; 532 "> ${lines.first}" : "* ${lines.first}";
521 lines = new List.from(lines.skip(1).map((line) => "| $line")); 533 lines = new List.from(lines.skip(1).map((line) => "| $line"));
522 lines.insertRange(0, 1, firstLine); 534 lines.insertRange(0, 1, firstLine);
523 return lines.join("\n"); 535 return lines.join("\n");
524 }).join("\n"); 536 }).join("\n");
525 } 537 }
526 } 538 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698