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

Side by Side 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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
3 // BSD-style license that can be found in the LICENSE file.
4
5 library schedule;
6
7 import 'dart:async';
8 import 'dart:collection';
9
10 import 'package:unittest/unittest.dart' as unittest;
11
12 import 'schedule_error.dart';
13 import 'task.dart';
14
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
17 /// visibility into the current state of the schedule.
18 class Schedule {
19 /// The main task queue for the schedule. These tasks are run before the other
20 /// queues and generally constitute the main test body.
21 TaskQueue get tasks => _tasks;
22 TaskQueue _tasks;
23
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
26 /// occurs. Note that expectation failures count as errors.
27 ///
28 /// This queue runs before [onComplete], and errors in [onComplete] will not
29 /// cause this queue to be run.
30 ///
31 /// If an error occurs in a task in this queue, all further tasks will be
32 /// skipped.
33 TaskQueue get onException => _onException;
34 TaskQueue _onException;
35
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
38 /// will be available via [error]. Note that expectation failures count as
39 /// errors.
40 ///
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
43 /// original error.
44 ///
45 /// If an error occurs in a task in this queue, all further tasks will be
46 /// skipped.
47 TaskQueue get onComplete => _onComplete;
48 TaskQueue _onComplete;
49
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
52 /// after it's finished.
53 Task get currentTask => _currentTask;
54 Task _currentTask;
55
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
58 /// exception has occurred.
59 bool get done => _done;
60 bool _done = false;
61
62 /// The error thrown by the task queue. This will only be set while running
63 /// [onException] and [onComplete], since an error in [tasks] will cause it to
64 /// terminate immediately.
65 ScheduleError get error => _error;
66 ScheduleError _error;
67
68 /// 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`
70 /// before the schedule starts running.
71 TaskQueue get currentQueue => _done ? null : _currentQueue;
72 TaskQueue _currentQueue;
73
74 /// The number of out-of-band callbacks that have been registered with
75 /// [wrapAsync] but have yet to be called.
76 int _pendingCallbacks = 0;
77
78 /// A completer that will be completed once [_pendingCallbacks] reaches zero.
79 /// This will only be non-`null` if [_awaitPendingCallbacks] has been called
80 /// while [_pendingCallbacks] is non-zero.
81 Completer _noPendingCallbacks;
82
83 /// Creates a new schedule with empty task queues.
84 Schedule() {
85 _tasks = new TaskQueue._("tasks", this);
86 _onComplete = new TaskQueue._("onComplete", this);
87 _onException = new TaskQueue._("onException", this);
88 }
89
90 /// Sets up this schedule by running [setUp], then runs all the task queues in
91 /// order. Any errors in [setUp] will cause [onException] to run.
92 Future run(void setUp()) {
93 return new Future.immediate(null).then((_) {
94 try {
95 setUp();
96 } catch (e, stackTrace) {
97 throw new ScheduleError.from(this, e, stackTrace: stackTrace);
98 }
99
100 return tasks._run();
101 }).catchError((e) {
102 _error = e;
103 return onException._run().then((_) {
104 throw e;
105 });
106 }).whenComplete(() => onComplete._run()).whenComplete(() {
107 _done = true;
108 });
109 }
110
111 /// Signals that an out-of-band error has occurred. Using [wrapAsync] along
112 /// with `throw` is usually preferable to calling this directly.
113 ///
114 /// The metadata in [AsyncError]s and [ScheduleError]s will be preserved.
115 void signalError(error, [stackTrace]) {
116 var scheduleError = new ScheduleError.from(this, error,
117 stackTrace: stackTrace, task: currentTask);
118 if (_done) {
119 throw new StateError(
120 "An out-of-band error was signaled outside of wrapAsync after the "
121 "schedule finished running:"
122 "${prefixLines(scheduleError.toString())}");
123 } else if (currentQueue == null) {
124 // 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
126 // function. Throwing the error will thus pipe it into the main
127 // error-handling code.
128 throw scheduleError;
129 } else {
130 _currentQueue._signalError(scheduleError);
131 }
132 }
133
134 /// Returns a function wrapping [fn] that pipes any errors into the schedule
135 /// chain. This will also block the current task queue from completing until
136 /// the returned function has been called. It's used to ensure that
137 /// out-of-band callbacks are properly handled by the scheduled test.
138 ///
139 /// The top-level `wrapAsync` function should usually be used in preference to
140 /// this.
141 Function wrapAsync(fn(arg)) {
142 if (_done) {
143 throw new StateError("wrapAsync called after the schedule has finished "
144 "running.");
145 }
146
147 _pendingCallbacks++;
148 return (arg) {
149 try {
150 return fn(arg);
151 } catch (e, stackTrace) {
152 signalError(e, stackTrace);
153 } finally {
154 _pendingCallbacks--;
155 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) {
156 _noPendingCallbacks.complete();
157 _noPendingCallbacks = null;
158 }
159 }
160 };
161 }
162
163 /// Returns a [Future] that will complete once there are no pending
164 /// out-of-band callbacks.
165 Future _awaitNoPendingCallbacks() {
166 if (_pendingCallbacks == 0) return new Future.immediate(null);
167 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer();
168 return _noPendingCallbacks.future;
169 }
170 }
171
172 /// A queue of asynchronous tasks to execute in order.
173 class TaskQueue {
174 // TODO(nweiz): make this a read-only view when issue 8321 is fixed.
175 /// The tasks in the queue.
176 Collection<Task> get contents => _contents;
177 final _contents = new Queue<Task>();
178
179 /// The name of the queue, for debugging purposes.
180 final String name;
181
182 /// The [Schedule] that created this queue.
183 final Schedule _schedule;
184
185 /// An out-of-band error signaled by [_schedule]. If this is non-null, it
186 /// indicates that the queue should stop as soon as possible and re-throw this
187 /// error.
188 ScheduleError _error;
189
190 TaskQueue._(this.name, this._schedule);
191
192 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks
193 /// will be run in the order they're scheduled. In [fn] returns a [Future],
194 /// tasks after it won't be run until that [Future] completes.
195 ///
196 /// The return value will be completed once the scheduled task has finished
197 /// running. Its return value is the same as the return value of [fn], or the
198 /// value it completes to if it's a [Future].
199 ///
200 /// If [description] is passed, it's used to describe the task for debugging
201 /// purposes when an error occurs.
202 Future schedule(fn(), [String description]) {
203 var task = new Task(fn, this, description);
204 _contents.add(task);
205 return task.result;
206 }
207
208 /// Runs all the tasks in this queue in order.
209 Future _run() {
210 _schedule._currentQueue = this;
211 return Future.forEach(_contents, (task) {
212 _schedule._currentTask = task;
213 if (_error != null) throw _error;
214 return task.fn().catchError((e) {
215 throw new ScheduleError.from(_schedule, e, task: task);
216 });
217 }).whenComplete(() {
218 _schedule._currentTask = null;
219 return _schedule._awaitNoPendingCallbacks();
220 }).then((_) {
221 if (_error != null) throw _error;
222 });
223 }
224
225 /// Signals that an out-of-band error has been detected and the queue should
226 /// stop running as soon as possible.
227 void _signalError(ScheduleError error) {
228 _error = error;
229 }
230
231 String toString() => name;
232
233 /// Returns a detailed representation of the queue as a tree of tasks. If
234 /// [highlight] is passed, that task is specially highlighted.
235 ///
236 /// [highlight] must be a task in this queue.
237 String generateTree([Task highlight]) {
238 assert(highlight == null || highlight.queue == this);
239 return _contents.map((task) {
240 var lines = task.toString().split("\n");
241 var firstLine = task == highlight ?
242 "> ${lines.first}" : "* ${lines.first}";
243 lines = new List.from(lines.skip(1).map((line) => "| $line"));
244 lines.insertRange(0, 1, firstLine);
245 return lines.join("\n");
246 }).join("\n");
247 }
248 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698