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

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

Issue 12208081: Add a scheduled test library. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: 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 unittest.registerException(new ExpectException(scheduleError.toString()));
Bob Nystrom 2013/02/08 16:15:37 Are we ever supposed to get here? I think this may
nweiz 2013/02/08 22:14:38 Yeah, a StateError is probably better here. I real
120 } else if (currentQueue == null) {
121 throw scheduleError;
122 } else {
123 _currentQueue._signalError(scheduleError);
124 }
125 }
126
127 /// Returns a function wrapping [fn] that pipes any errors into the schedule
128 /// chain. This will also block the current task queue from completing until
129 /// the returned function has been called. It's used to ensure that
130 /// out-of-band callbacks are properly handled by the scheduled test.
131 ///
132 /// The top-level `wrapAsync` function should usually be used in preference to
133 /// this.
Bob Nystrom 2013/02/08 16:15:37 Make this one private then?
nweiz 2013/02/08 22:14:38 "Usually", not necessarily "always". Also, schedul
134 Function wrapAsync(fn(arg)) {
Bob Nystrom 2013/02/08 16:15:37 Should check that _noPendingCallbacks isn't alread
nweiz 2013/02/08 22:14:38 It's possible to wait for pending callbacks multip
135 _pendingCallbacks++;
136 return (arg) {
137 try {
138 return fn(arg);
139 } catch (e, stackTrace) {
140 signalError(e, stackTrace);
141 } finally {
142 _pendingCallbacks--;
143 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) {
144 _noPendingCallbacks.complete();
145 }
146 }
147 };
148 }
149
150 /// Returns a [Future] that will complete once there are no pending
151 /// out-of-band callbacks.
152 Future _awaitNoPendingCallbacks() {
153 if (_pendingCallbacks == 0) return new Future.immediate(null);
Bob Nystrom 2013/02/08 16:15:37 What if callbacks are added after this is called?
nweiz 2013/02/08 22:14:38 Then they're not pending :p. In seriousness, this
154 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer();
155 return _noPendingCallbacks.future;
156 }
157 }
158
159 /// A queue of asynchronous tasks to execute in order.
160 class TaskQueue {
161 // TODO(nweiz): make this a read-only view when issue 8321 is fixed.
162 /// The tasks in the queue.
163 Collection<Task> get contents => _contents;
164 final _contents = new Queue<Task>();
165
166 /// The name of the queue, for debugging purposes.
167 final String name;
168
169 /// The [Schedule] that created this queue.
170 final Schedule _schedule;
171
172 /// An out-of-band error signaled by [_schedule]. If this is non-null, it
173 /// indicates that the queue should stop as soon as possible and re-throw this
174 /// error.
175 ScheduleError _error;
176
177 TaskQueue._(this.name, this._schedule);
178
179 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks
180 /// will be run in the order they're scheduled. In [fn] returns a [Future],
181 /// tasks after it won't be run until that [Future] completes.
182 ///
183 /// The return value will be completed once the scheduled task has finished
184 /// running. Its return value is the same as the return value of [fn], or the
185 /// value it completes to if it's a [Future].
186 ///
187 /// If [description] is passed, it's used to describe the task for debugging
188 /// purposes when an error occurs.
189 Future schedule(fn(), [String description]) {
190 var task = new Task(fn, this, description);
191 _contents.add(task);
192 return task.result;
193 }
194
195 /// Runs all the tasks in this queue in order.
196 Future _run() {
197 _schedule._currentQueue = this;
198 return Future.forEach(_contents, (task) {
199 _schedule._currentTask = task;
200 if (_error != null) throw _error;
201 return task.fn().catchError((e) {
202 throw new ScheduleError.from(_schedule, e, task: task);
203 });
204 }).whenComplete(() {
205 _schedule._currentTask = null;
206 return _schedule._awaitNoPendingCallbacks();
207 }).then((_) {
208 if (_error != null) throw _error;
209 });
210 }
211
212 /// Signals that an out-of-band error has been detected and the queue should
213 /// stop running as soon as possible.
214 void _signalError(ScheduleError error) {
215 _error = error;
216 }
217
218 String toString() => name;
219
220 /// Returns a detailed representation of the queue as a tree of tasks. If
221 /// [highlight] is passed, that task is specially highlighted.
222 ///
223 /// [highlight] must be a task in this queue.
224 String generateTree([Task highlight]) {
225 assert(highlight == null || highlight.queue == this);
226 return _contents.map((task) {
227 var lines = task.toString().split("\n");
228 var firstLine = task == highlight ?
229 "> ${lines.first}" : "* ${lines.first}";
230 lines = new List.from(lines.skip(1).map((line) => "| $line"));
231 lines.insertRange(0, 1, firstLine);
232 return lines.join("\n");
233 }).join("\n");
234 }
235 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698