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

Unified Diff: pkg/scheduled_test/lib/src/schedule.dart

Issue 12209073: Add a scheduled test library. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Make everything play nicely with the outside world. Created 7 years, 10 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 side-by-side diff with in-line comments
Download patch
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
new file mode 100644
index 0000000000000000000000000000000000000000..dd9e6d3a776d09ca31c854bab4d73029717cf046
--- /dev/null
+++ b/pkg/scheduled_test/lib/src/schedule.dart
@@ -0,0 +1,248 @@
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library schedule;
+
+import 'dart:async';
+import 'dart:collection';
+
+import 'package:unittest/unittest.dart' as unittest;
+
+import 'schedule_error.dart';
+import 'task.dart';
+
+/// The schedule of tasks to run for a single test. This has three separate task
+/// queues: [tasks], [onComplete], and [onException]. It also provides
+/// visibility into the current state of the schedule.
+class Schedule {
+ /// The main task queue for the schedule. These tasks are run before the other
+ /// queues and generally constitute the main test body.
+ TaskQueue get tasks => _tasks;
+ TaskQueue _tasks;
+
+ /// The queue of tasks to run if an error is caught while running [tasks]. The
+ /// error will be available via [error]. These tasks won't be run if no error
+ /// occurs. Note that expectation failures count as errors.
+ ///
+ /// This queue runs before [onComplete], and errors in [onComplete] will not
+ /// cause this queue to be run.
+ ///
+ /// If an error occurs in a task in this queue, all further tasks will be
+ /// skipped.
+ TaskQueue get onException => _onException;
+ TaskQueue _onException;
+
+ /// The queue of tasks to run after [tasks] and possibly [onException] have
+ /// run. This queue will run whether or not an error occurred. If one did, it
+ /// will be available via [error]. Note that expectation failures count as
+ /// errors.
+ ///
+ /// This queue runs after [onException]. If an error occurs while running
+ /// [onException], that error will be available via [error] in place of the
+ /// original error.
+ ///
+ /// If an error occurs in a task in this queue, all further tasks will be
+ /// skipped.
+ TaskQueue get onComplete => _onComplete;
+ TaskQueue _onComplete;
+
+ /// Returns the [Task] that's currently executing, or `null` if there is no
+ /// such task. This will be `null` both before the schedule starts running and
+ /// after it's finished.
+ Task get currentTask => _currentTask;
+ Task _currentTask;
+
+ /// Whether the schedule has finished running. This is only set once
+ /// [onComplete] has finished running. It will be set whether or not an
+ /// exception has occurred.
+ bool get done => _done;
+ bool _done = false;
+
+ /// The error thrown by the task queue. This will only be set while running
+ /// [onException] and [onComplete], since an error in [tasks] will cause it to
+ /// terminate immediately.
+ ScheduleError get error => _error;
+ ScheduleError _error;
+
+ /// The task queue that's currently being run, or `null` if there is no such
+ /// queue. One of [tasks], [onException], or [onComplete]. This will be `null`
+ /// before the schedule starts running.
+ TaskQueue get currentQueue => _done ? null : _currentQueue;
+ TaskQueue _currentQueue;
+
+ /// The number of out-of-band callbacks that have been registered with
+ /// [wrapAsync] but have yet to be called.
+ int _pendingCallbacks = 0;
+
+ /// A completer that will be completed once [_pendingCallbacks] reaches zero.
+ /// This will only be non-`null` if [_awaitPendingCallbacks] has been called
+ /// while [_pendingCallbacks] is non-zero.
+ Completer _noPendingCallbacks;
+
+ /// Creates a new schedule with empty task queues.
+ Schedule() {
+ _tasks = new TaskQueue._("tasks", this);
+ _onComplete = new TaskQueue._("onComplete", this);
+ _onException = new TaskQueue._("onException", this);
+ }
+
+ /// Sets up this schedule by running [setUp], then runs all the task queues in
+ /// order. Any errors in [setUp] will cause [onException] to run.
+ Future run(void setUp()) {
+ return new Future.immediate(null).then((_) {
+ try {
+ setUp();
+ } catch (e, stackTrace) {
+ throw new ScheduleError.from(this, e, stackTrace: stackTrace);
+ }
+
+ return tasks._run();
+ }).catchError((e) {
+ _error = e;
+ return onException._run().then((_) {
+ throw e;
+ });
+ }).whenComplete(() => onComplete._run()).whenComplete(() {
+ _done = true;
+ });
+ }
+
+ /// Signals that an out-of-band error has occurred. Using [wrapAsync] along
+ /// with `throw` is usually preferable to calling this directly.
+ ///
+ /// The metadata in [AsyncError]s and [ScheduleError]s will be preserved.
+ void signalError(error, [stackTrace]) {
+ var scheduleError = new ScheduleError.from(this, error,
+ stackTrace: stackTrace, task: currentTask);
+ if (_done) {
+ throw new StateError(
+ "An out-of-band error was signaled outside of wrapAsync after the "
+ "schedule finished running:"
+ "${prefixLines(scheduleError.toString())}");
+ } else if (currentQueue == null) {
+ // If we're not done but there's no current queue, that means we haven't
+ // started yet and thus we're in setUp or the synchronous body of the
+ // function. Throwing the error will thus pipe it into the main
+ // error-handling code.
+ throw scheduleError;
+ } else {
+ _currentQueue._signalError(scheduleError);
+ }
+ }
+
+ /// Returns a function wrapping [fn] that pipes any errors into the schedule
+ /// chain. This will also block the current task queue from completing until
+ /// the returned function has been called. It's used to ensure that
+ /// out-of-band callbacks are properly handled by the scheduled test.
+ ///
+ /// The top-level `wrapAsync` function should usually be used in preference to
+ /// this.
+ Function wrapAsync(fn(arg)) {
+ if (_done) {
+ throw new StateError("wrapAsync called after the schedule has finished "
+ "running.");
+ }
+
+ _pendingCallbacks++;
+ return (arg) {
+ try {
+ return fn(arg);
+ } catch (e, stackTrace) {
+ signalError(e, stackTrace);
+ } finally {
+ _pendingCallbacks--;
+ if (_pendingCallbacks == 0 && _noPendingCallbacks != null) {
+ _noPendingCallbacks.complete();
+ _noPendingCallbacks = null;
+ }
+ }
+ };
+ }
+
+ /// Returns a [Future] that will complete once there are no pending
+ /// out-of-band callbacks.
+ Future _awaitNoPendingCallbacks() {
+ if (_pendingCallbacks == 0) return new Future.immediate(null);
+ if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer();
+ return _noPendingCallbacks.future;
+ }
+}
+
+/// A queue of asynchronous tasks to execute in order.
+class TaskQueue {
+ // TODO(nweiz): make this a read-only view when issue 8321 is fixed.
+ /// The tasks in the queue.
+ Collection<Task> get contents => _contents;
+ final _contents = new Queue<Task>();
+
+ /// The name of the queue, for debugging purposes.
+ final String name;
+
+ /// The [Schedule] that created this queue.
+ final Schedule _schedule;
+
+ /// An out-of-band error signaled by [_schedule]. If this is non-null, it
+ /// indicates that the queue should stop as soon as possible and re-throw this
+ /// error.
+ ScheduleError _error;
+
+ TaskQueue._(this.name, this._schedule);
+
+ /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks
+ /// will be run in the order they're scheduled. In [fn] returns a [Future],
+ /// tasks after it won't be run until that [Future] completes.
+ ///
+ /// The return value will be completed once the scheduled task has finished
+ /// running. Its return value is the same as the return value of [fn], or the
+ /// value it completes to if it's a [Future].
+ ///
+ /// If [description] is passed, it's used to describe the task for debugging
+ /// purposes when an error occurs.
+ Future schedule(fn(), [String description]) {
+ var task = new Task(fn, this, description);
+ _contents.add(task);
+ return task.result;
+ }
+
+ /// Runs all the tasks in this queue in order.
+ Future _run() {
+ _schedule._currentQueue = this;
+ return Future.forEach(_contents, (task) {
+ _schedule._currentTask = task;
+ if (_error != null) throw _error;
+ return task.fn().catchError((e) {
+ throw new ScheduleError.from(_schedule, e, task: task);
+ });
+ }).whenComplete(() {
+ _schedule._currentTask = null;
+ return _schedule._awaitNoPendingCallbacks();
+ }).then((_) {
+ if (_error != null) throw _error;
+ });
+ }
+
+ /// Signals that an out-of-band error has been detected and the queue should
+ /// stop running as soon as possible.
+ void _signalError(ScheduleError error) {
+ _error = error;
+ }
+
+ String toString() => name;
+
+ /// Returns a detailed representation of the queue as a tree of tasks. If
+ /// [highlight] is passed, that task is specially highlighted.
+ ///
+ /// [highlight] must be a task in this queue.
+ String generateTree([Task highlight]) {
+ assert(highlight == null || highlight.queue == this);
+ return _contents.map((task) {
+ var lines = task.toString().split("\n");
+ var firstLine = task == highlight ?
+ "> ${lines.first}" : "* ${lines.first}";
+ lines = new List.from(lines.skip(1).map((line) => "| $line"));
+ lines.insertRange(0, 1, firstLine);
+ return lines.join("\n");
+ }).join("\n");
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698