OLD | NEW |
---|---|
(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 } | |
OLD | NEW |