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

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

Issue 12218102: Add built-in timeouts to scheduled_test. (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
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;
11 11
12 import 'schedule_error.dart'; 12 import 'schedule_error.dart';
13 import 'substitute_future.dart';
13 import 'task.dart'; 14 import 'task.dart';
14 15
15 /// The schedule of tasks to run for a single test. This has three separate task 16 /// 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 /// queues: [tasks], [onComplete], and [onException]. It also provides
17 /// visibility into the current state of the schedule. 18 /// visibility into the current state of the schedule.
18 class Schedule { 19 class Schedule {
19 /// The main task queue for the schedule. These tasks are run before the other 20 /// The main task queue for the schedule. These tasks are run before the other
20 /// queues and generally constitute the main test body. 21 /// queues and generally constitute the main test body.
21 TaskQueue get tasks => _tasks; 22 TaskQueue get tasks => _tasks;
22 TaskQueue _tasks; 23 TaskQueue _tasks;
(...skipping 17 matching lines...) Expand all
40 /// 41 ///
41 /// This queue runs after [onException]. If an error occurs while running 42 /// This queue runs after [onException]. If an error occurs while running
42 /// [onException], that error will be available in [errors] after the original 43 /// [onException], that error will be available in [errors] after the original
43 /// error. 44 /// error.
44 /// 45 ///
45 /// If an error occurs in a task in this queue, all further tasks will be 46 /// If an error occurs in a task in this queue, all further tasks will be
46 /// skipped. 47 /// skipped.
47 TaskQueue get onComplete => _onComplete; 48 TaskQueue get onComplete => _onComplete;
48 TaskQueue _onComplete; 49 TaskQueue _onComplete;
49 50
50 /// Returns the [Task] that's currently executing, or `null` if there is no 51 /// The task queue that's currently being run. One of [tasks], [onException],
51 /// such task. This will be `null` both before the schedule starts running and 52 /// or [onComplete]. This starts as [tasks], and will only be `null` after the
52 /// after it's finished. 53 /// schedule has finished running.
Bob Nystrom 2013/02/12 00:34:17 Wrong doc comment?
nweiz 2013/02/12 01:15:57 Done.
53 Task get currentTask => _currentTask; 54 Task get currentTask => _currentTask;
54 Task _currentTask; 55 Task _currentTask;
55 56
56 /// Whether the schedule has finished running. This is only set once 57 /// The current state of the schedule.
57 /// [onComplete] has finished running. It will be set whether or not an 58 ScheduleState get state => _state;
58 /// exception has occurred. 59 ScheduleState _state = ScheduleState.SET_UP;
59 bool get done => _done;
60 bool _done = false;
61 60
62 // TODO(nweiz): make this a read-only view once issue 8321 is fixed. 61 // TODO(nweiz): make this a read-only view once issue 8321 is fixed.
63 62
64 /// Errors thrown by the task queues. 63 /// Errors thrown by the task queues.
65 /// 64 ///
66 /// When running tasks in [tasks], this will always be empty. If an error 65 /// When running tasks in [tasks], this will always be empty. If an error
67 /// occurs in [tasks], it will be added to this list and then [onException] 66 /// occurs in [tasks], it will be added to this list and then [onException]
68 /// will be run. If an error occurs there as well, it will be added to this 67 /// will be run. If an error occurs there as well, it will be added to this
69 /// list and [onComplete] will be run. Errors thrown during [onComplete] will 68 /// list and [onComplete] will be run. Errors thrown during [onComplete] will
70 /// also be added to this list, although no scheduled tasks will be run 69 /// also be added to this list, although no scheduled tasks will be run
71 /// afterwards. 70 /// afterwards.
72 /// 71 ///
73 /// Any out-of-band callbacks that throw errors will also have those errors 72 /// Any out-of-band callbacks that throw errors will also have those errors
74 /// added to this list. 73 /// added to this list.
75 final errors = <ScheduleError>[]; 74 final errors = <ScheduleError>[];
76 75
77 /// The task queue that's currently being run, or `null` if there is no such 76 /// The task queue that's currently being run. One of [tasks], [onException],
78 /// queue. One of [tasks], [onException], or [onComplete]. This will be `null` 77 /// or [onComplete]. This starts as [tasks], and can only be `null` after the
79 /// before the schedule starts running. 78 /// schedule is done.
80 TaskQueue get currentQueue => _done ? null : _currentQueue; 79 TaskQueue get currentQueue =>
80 _state == ScheduleState.DONE ? null : _currentQueue;
81 TaskQueue _currentQueue; 81 TaskQueue _currentQueue;
82 82
83 /// The time, in milliseconds, to wait before terminating a task queue for
84 /// inactivity. Defaults to 30 seconds. This can be set to `null` to disable
85 /// timeouts entirely.
Bob Nystrom 2013/02/12 00:34:17 I think we should use zero for no timeout. We tend
nweiz 2013/02/12 01:15:57 See offline discussion.
86 ///
87 /// If a task queue times out, an error will be raised that can be handled as
88 /// usual in the [onException] and [onComplete] queues. If [onException] times
89 /// out, that can only be handles in [onComplete]; if [onComplete] times out,
Bob Nystrom 2013/02/12 00:34:17 "handles" -> "handled".
nweiz 2013/02/12 01:15:57 Done.
90 /// that cannot be handled.
91 ///
92 /// If a task times out and then later completes with an error, that error
93 /// will not be handlable. The user will still be notified of it.
Bob Nystrom 2013/02/12 00:34:17 "will not be handlable" -> "cannot be handled".
nweiz 2013/02/12 01:15:57 Done.
94 int get timeoutLength => _timeoutLength;
Bob Nystrom 2013/02/12 00:34:17 The "length" seems unhelpful. How about "timeoutMs
nweiz 2013/02/12 01:15:57 Done.
95 int _timeoutLength = 30 * 1000;
96 set timeoutLength(int value) {
97 _timeoutLength = value;
98 ping();
99 }
100
83 /// The number of out-of-band callbacks that have been registered with 101 /// The number of out-of-band callbacks that have been registered with
84 /// [wrapAsync] but have yet to be called. 102 /// [wrapAsync] but have yet to be called.
85 int _pendingCallbacks = 0; 103 int _pendingCallbacks = 0;
86 104
87 /// A completer that will be completed once [_pendingCallbacks] reaches zero. 105 /// A completer that will be completed once [_pendingCallbacks] reaches zero.
88 /// This will only be non-`null` if [_awaitPendingCallbacks] has been called 106 /// This will only be non-`null` if [_awaitPendingCallbacks] has been called
89 /// while [_pendingCallbacks] is non-zero. 107 /// while [_pendingCallbacks] is non-zero.
90 Completer _noPendingCallbacks; 108 Completer _noPendingCallbacks;
91 109
110 /// The timer for keeping track of task timeouts. This may be null.
111 Timer _timeoutTimer;
112
92 /// Creates a new schedule with empty task queues. 113 /// Creates a new schedule with empty task queues.
93 Schedule() { 114 Schedule() {
94 _tasks = new TaskQueue._("tasks", this); 115 _tasks = new TaskQueue._("tasks", this);
95 _onComplete = new TaskQueue._("onComplete", this); 116 _onComplete = new TaskQueue._("onComplete", this);
96 _onException = new TaskQueue._("onException", this); 117 _onException = new TaskQueue._("onException", this);
118 _currentQueue = _tasks;
119
120 ping();
97 } 121 }
98 122
99 /// Sets up this schedule by running [setUp], then runs all the task queues in 123 /// Sets up this schedule by running [setUp], then runs all the task queues in
100 /// order. Any errors in [setUp] will cause [onException] to run. 124 /// order. Any errors in [setUp] will cause [onException] to run.
101 Future run(void setUp()) { 125 Future run(void setUp()) {
102 return new Future.immediate(null).then((_) { 126 return new Future.immediate(null).then((_) {
103 try { 127 try {
104 setUp(); 128 setUp();
105 } catch (e, stackTrace) { 129 } catch (e, stackTrace) {
106 throw new ScheduleError.from(this, e, stackTrace: stackTrace); 130 throw new ScheduleError.from(this, e, stackTrace: stackTrace);
107 } 131 }
108 132
133 _state = ScheduleState.RUNNING;
109 return tasks._run(); 134 return tasks._run();
110 }).catchError((e) { 135 }).catchError((e) {
111 errors.add(e); 136 errors.add(e);
112 return onException._run().catchError((innerError) { 137 return onException._run().catchError((innerError) {
113 // If an error occurs in a task in the onException queue, make sure it's 138 // If an error occurs in a task in the onException queue, make sure it's
114 // registered in the error list and re-throw it. We could also re-throw 139 // registered in the error list and re-throw it. We could also re-throw
115 // `e`; ultimately, all the errors will be shown to the user if any 140 // `e`; ultimately, all the errors will be shown to the user if any
116 // ScheduleError is thrown. 141 // ScheduleError is thrown.
117 errors.add(innerError); 142 errors.add(innerError);
118 throw innerError; 143 throw innerError;
119 }).then((_) { 144 }).then((_) {
120 // If there are no errors in the onException queue, re-throw the 145 // If there are no errors in the onException queue, re-throw the
121 // original error that caused it to run. 146 // original error that caused it to run.
122 throw e; 147 throw e;
123 }); 148 });
124 }).whenComplete(() { 149 }).whenComplete(() {
125 return onComplete._run().catchError((e) { 150 return onComplete._run().catchError((e) {
126 // If an error occurs in a task in the onComplete queue, make sure it's 151 // If an error occurs in a task in the onComplete queue, make sure it's
127 // registered in the error list and re-throw it. 152 // registered in the error list and re-throw it.
128 errors.add(e); 153 errors.add(e);
129 throw e; 154 throw e;
130 }); 155 });
131 }).whenComplete(() { 156 }).whenComplete(() {
132 _done = true; 157 if (_timeoutTimer != null) _timeoutTimer.cancel();
158 _state = ScheduleState.DONE;
133 }); 159 });
134 } 160 }
135 161
136 /// Signals that an out-of-band error has occurred. Using [wrapAsync] along 162 /// Signals that an out-of-band error has occurred. Using [wrapAsync] along
137 /// with `throw` is usually preferable to calling this directly. 163 /// with `throw` is usually preferable to calling this directly.
138 /// 164 ///
139 /// The metadata in [AsyncError]s and [ScheduleError]s will be preserved. 165 /// The metadata in [AsyncError]s and [ScheduleError]s will be preserved.
140 void signalError(error, [stackTrace]) { 166 void signalError(error, [stackTrace]) {
167 ping();
168
141 var scheduleError = new ScheduleError.from(this, error, 169 var scheduleError = new ScheduleError.from(this, error,
142 stackTrace: stackTrace, task: currentTask); 170 stackTrace: stackTrace, task: currentTask);
143 if (_done) { 171 if (_state == ScheduleState.DONE) {
144 errors.add(scheduleError);
145 throw new StateError( 172 throw new StateError(
146 "An out-of-band error was signaled outside of wrapAsync after the " 173 "An out-of-band error was signaled outside of wrapAsync after the "
147 "schedule finished running.\n" 174 "schedule finished running.\n"
148 "${errorString()}"); 175 "${errorString()}");
149 } else if (currentQueue == null) { 176 } else if (state == ScheduleState.SET_UP) {
150 // If we're not done but there's no current queue, that means we haven't 177 // If we're setting up, throwing the error will pipe it into the main
151 // started yet and thus we're in setUp or the synchronous body of the
152 // function. Throwing the error will thus pipe it into the main
153 // error-handling code. 178 // error-handling code.
154 throw scheduleError; 179 throw scheduleError;
155 } else { 180 } else {
156 _currentQueue._signalError(scheduleError); 181 _currentQueue._signalError(scheduleError);
157 } 182 }
158 } 183 }
159 184
185 /// Notifies the schedule of an error that occurred in a task or out-of-band
186 /// callback after the appropriate queue has timed out. If this schedule is
187 /// still running, the error will be added to the errors list to be shown
188 /// along with the timeout error; otherwise, a top-level error will be thrown.
189 void _signalPostTimeoutError(error, [stackTrace]) {
190 var scheduleError = new ScheduleError.from(this, error,
191 stackTrace: stackTrace);
192 errors.add(scheduleError);
193 if (_state == ScheduleState.DONE) {
194 throw new StateError(
195 "An out-of-band error was caught after the test timed out.\n"
196 "${errorString()}");
197 }
198 }
199
160 /// Returns a function wrapping [fn] that pipes any errors into the schedule 200 /// Returns a function wrapping [fn] that pipes any errors into the schedule
161 /// chain. This will also block the current task queue from completing until 201 /// chain. This will also block the current task queue from completing until
162 /// the returned function has been called. It's used to ensure that 202 /// the returned function has been called. It's used to ensure that
163 /// out-of-band callbacks are properly handled by the scheduled test. 203 /// out-of-band callbacks are properly handled by the scheduled test.
164 /// 204 ///
165 /// The top-level `wrapAsync` function should usually be used in preference to 205 /// The top-level `wrapAsync` function should usually be used in preference to
166 /// this. 206 /// this.
167 Function wrapAsync(fn(arg)) { 207 Function wrapAsync(fn(arg)) {
168 if (_done) { 208 if (_state == ScheduleState.DONE) {
169 throw new StateError("wrapAsync called after the schedule has finished " 209 throw new StateError("wrapAsync called after the schedule has finished "
170 "running."); 210 "running.");
171 } 211 }
212 ping();
213
214 var queue = currentQueue;
215 // It's possible that the queue timed out before this
Bob Nystrom 2013/02/12 00:34:17 "."
nweiz 2013/02/12 01:15:57 Done.
216 bool _timedOut() => queue != currentQueue || _pendingCallbacks == 0;
172 217
173 _pendingCallbacks++; 218 _pendingCallbacks++;
174 return (arg) { 219 return (arg) {
175 try { 220 try {
176 return fn(arg); 221 return fn(arg);
177 } catch (e, stackTrace) { 222 } catch (e, stackTrace) {
178 signalError(e, stackTrace); 223 if (_timedOut()) {
224 _signalPostTimeoutError(e, stackTrace);
225 } else {
226 signalError(e, stackTrace);
227 }
179 } finally { 228 } finally {
229 if (_timedOut()) return;
230
180 _pendingCallbacks--; 231 _pendingCallbacks--;
181 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) { 232 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) {
182 _noPendingCallbacks.complete(); 233 _noPendingCallbacks.complete();
183 _noPendingCallbacks = null; 234 _noPendingCallbacks = null;
184 } 235 }
185 } 236 }
186 }; 237 };
187 } 238 }
188 239
189 /// Returns a string representation of all errors registered on this schedule. 240 /// Returns a string representation of all errors registered on this schedule.
190 String errorString() { 241 String errorString() {
191 if (errors.isEmpty) return "The schedule had no errors."; 242 if (errors.isEmpty) return "The schedule had no errors.";
192 if (errors.length == 1) return errors.first.toString(); 243 if (errors.length == 1) return errors.first.toString();
193 var errorStrings = errors.map((e) => e.toString()).join("\n================" 244 var errorStrings = errors.map((e) => e.toString()).join("\n================"
194 "================================================================\n"); 245 "================================================================\n");
195 return "The schedule had ${errors.length} errors:\n$errorStrings"; 246 return "The schedule had ${errors.length} errors:\n$errorStrings";
196 } 247 }
197 248
249 /// Notifies the schedule that progress is being made on an asynchronous task.
250 /// This resets the timeout timer, and can be used in long-running tasks to
251 /// keep them from timing out.
252 void ping() {
Bob Nystrom 2013/02/12 00:34:17 "ping" already means something else in common use.
nweiz 2013/02/12 01:15:57 I was using it in the IRC sense of "are you still
253 if (_timeoutTimer != null) _timeoutTimer.cancel();
254 if (_timeoutLength == null) {
255 _timeoutTimer = null;
256 } else {
257 _timeoutTimer = new Timer(_timeoutLength, _signalTimeout);
258 }
259 }
260
261 /// The callback to run when the timeout timer fires. Notifies the current
262 /// queue that a timeout has occurred.
263 void _signalTimeout(_) {
264 // Reset the timer so that we can detect timeouts in the onException and
265 // onComplete queues.
266 _timeoutTimer = null;
267
268 var error = new ScheduleError.from(this, "The schedule timed out after "
269 "${_timeoutLength}ms of inactivity.", task: currentTask);
270
271 _pendingCallbacks = 0;
272 if (_noPendingCallbacks != null) {
273 var noPendingCallbacks = _noPendingCallbacks;
274 _noPendingCallbacks = null;
275 noPendingCallbacks.completeError(error);
276 } else {
277 currentQueue._signalTimeout(error);
278 }
279 }
280
198 /// Returns a [Future] that will complete once there are no pending 281 /// Returns a [Future] that will complete once there are no pending
199 /// out-of-band callbacks. 282 /// out-of-band callbacks.
200 Future _awaitNoPendingCallbacks() { 283 Future _awaitNoPendingCallbacks() {
201 if (_pendingCallbacks == 0) return new Future.immediate(null); 284 if (_pendingCallbacks == 0) return new Future.immediate(null);
202 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer(); 285 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer();
203 return _noPendingCallbacks.future; 286 return _noPendingCallbacks.future;
204 } 287 }
205 } 288 }
206 289
290 /// An enum of states for a [Schedule].
291 class ScheduleState {
292 /// The schedule can have tasks added to its queue, but is not yet running
293 /// them.
294 static const SET_UP = const ScheduleState._("SET_UP");
295
296 /// The schedule is actively running tasks. This includes running tasks in
297 /// [Schedule.onException] and [Schedule.onComplete].
298 static const RUNNING = const ScheduleState._("RUNNING");
299
300 /// The schedule has finished running all its tasks, either successfully or
301 /// with an error.
302 static const DONE = const ScheduleState._("DONE");
303
304 /// The name of the state.
305 final String name;
306
307 const ScheduleState._(this.name);
308
309 String toString() => name;
310 }
311
207 /// A queue of asynchronous tasks to execute in order. 312 /// A queue of asynchronous tasks to execute in order.
208 class TaskQueue { 313 class TaskQueue {
209 // TODO(nweiz): make this a read-only view when issue 8321 is fixed. 314 // TODO(nweiz): make this a read-only view when issue 8321 is fixed.
210 /// The tasks in the queue. 315 /// The tasks in the queue.
211 Collection<Task> get contents => _contents; 316 Collection<Task> get contents => _contents;
212 final _contents = new Queue<Task>(); 317 final _contents = new Queue<Task>();
213 318
214 /// The name of the queue, for debugging purposes. 319 /// The name of the queue, for debugging purposes.
215 final String name; 320 final String name;
216 321
217 /// The [Schedule] that created this queue. 322 /// The [Schedule] that created this queue.
218 final Schedule _schedule; 323 final Schedule _schedule;
219 324
220 /// An out-of-band error signaled by [_schedule]. If this is non-null, it 325 /// An out-of-band error signaled by [_schedule]. If this is non-null, it
221 /// indicates that the queue should stop as soon as possible and re-throw this 326 /// indicates that the queue should stop as soon as possible and re-throw this
222 /// error. 327 /// error.
223 ScheduleError _error; 328 ScheduleError _error;
224 329
330 /// The [SubstituteFuture] for the currently-running task in the queue, or
331 /// null if no task is currently running.
332 SubstituteFuture _taskFuture;
333
225 TaskQueue._(this.name, this._schedule); 334 TaskQueue._(this.name, this._schedule);
226 335
227 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks 336 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks
228 /// will be run in the order they're scheduled. In [fn] returns a [Future], 337 /// will be run in the order they're scheduled. In [fn] returns a [Future],
229 /// tasks after it won't be run until that [Future] completes. 338 /// tasks after it won't be run until that [Future] completes.
230 /// 339 ///
231 /// The return value will be completed once the scheduled task has finished 340 /// The return value will be completed once the scheduled task has finished
232 /// running. Its return value is the same as the return value of [fn], or the 341 /// running. Its return value is the same as the return value of [fn], or the
233 /// value it completes to if it's a [Future]. 342 /// value it completes to if it's a [Future].
234 /// 343 ///
235 /// If [description] is passed, it's used to describe the task for debugging 344 /// If [description] is passed, it's used to describe the task for debugging
236 /// purposes when an error occurs. 345 /// purposes when an error occurs.
237 Future schedule(fn(), [String description]) { 346 Future schedule(fn(), [String description]) {
238 var task = new Task(fn, this, description); 347 var task = new Task(fn, this, description);
239 _contents.add(task); 348 _contents.add(task);
240 return task.result; 349 return task.result;
241 } 350 }
242 351
243 /// Runs all the tasks in this queue in order. 352 /// Runs all the tasks in this queue in order.
244 Future _run() { 353 Future _run() {
245 _schedule._currentQueue = this; 354 _schedule._currentQueue = this;
355 _schedule.ping();
246 return Future.forEach(_contents, (task) { 356 return Future.forEach(_contents, (task) {
247 _schedule._currentTask = task; 357 _schedule._currentTask = task;
248 if (_error != null) throw _error; 358 if (_error != null) throw _error;
249 return task.fn().catchError((e) { 359
360 _taskFuture = new SubstituteFuture(task.fn());
361 return _taskFuture.whenComplete(() {
362 _taskFuture = null;
363 _schedule.ping();
364 }).catchError((e) {
250 if (_error != null) _schedule.errors.add(_error); 365 if (_error != null) _schedule.errors.add(_error);
251 throw new ScheduleError.from(_schedule, e, task: task); 366 throw new ScheduleError.from(_schedule, e, task: task);
252 }); 367 });
253 }).whenComplete(() { 368 }).whenComplete(() {
254 _schedule._currentTask = null; 369 _schedule._currentTask = null;
255 return _schedule._awaitNoPendingCallbacks(); 370 return _schedule._awaitNoPendingCallbacks();
256 }).then((_) { 371 }).then((_) {
372 _schedule.ping();
257 if (_error != null) throw _error; 373 if (_error != null) throw _error;
258 }); 374 });
259 } 375 }
260 376
261 /// Signals that an out-of-band error has been detected and the queue should 377 /// Signals that an out-of-band error has been detected and the queue should
262 /// stop running as soon as possible. 378 /// stop running as soon as possible.
263 void _signalError(ScheduleError error) { 379 void _signalError(ScheduleError error) {
264 // If multiple errors are detected while a task is running, make sure the 380 // If multiple errors are detected while a task is running, make sure the
265 // earlier ones are recorded in the schedule. 381 // earlier ones are recorded in the schedule.
266 if (_error != null) _schedule.errors.add(_error); 382 if (_error != null) _schedule.errors.add(_error);
267 _error = error; 383 _error = error;
268 } 384 }
269 385
386 /// Notifies the queue that it has timed out and it needs to terminate
387 /// immediately with a timeout error.
388 void _signalTimeout(ScheduleError error) {
389 if (_taskFuture != null) {
390 // Catch errors coming off the old task future, in case it completes after
391 // timing out.
392 _taskFuture.substitute(new Future.immediateError(error)).catchError((e) {
393 _schedule._signalPostTimeoutError(e);
394 });
395 } else {
396 // This branch probably won't be reached, but it's conceivable that the
397 // event loop might get pumped when _taskFuture is null but we haven't yet
398 // called _awaitNoPendingCallbacks.
399 _signalError(error);
400 }
401 }
402
270 String toString() => name; 403 String toString() => name;
271 404
272 /// Returns a detailed representation of the queue as a tree of tasks. If 405 /// Returns a detailed representation of the queue as a tree of tasks. If
273 /// [highlight] is passed, that task is specially highlighted. 406 /// [highlight] is passed, that task is specially highlighted.
274 /// 407 ///
275 /// [highlight] must be a task in this queue. 408 /// [highlight] must be a task in this queue.
276 String generateTree([Task highlight]) { 409 String generateTree([Task highlight]) {
277 assert(highlight == null || highlight.queue == this); 410 assert(highlight == null || highlight.queue == this);
278 return _contents.map((task) { 411 return _contents.map((task) {
279 var lines = task.toString().split("\n"); 412 var lines = task.toString().split("\n");
280 var firstLine = task == highlight ? 413 var firstLine = task == highlight ?
281 "> ${lines.first}" : "* ${lines.first}"; 414 "> ${lines.first}" : "* ${lines.first}";
282 lines = new List.from(lines.skip(1).map((line) => "| $line")); 415 lines = new List.from(lines.skip(1).map((line) => "| $line"));
283 lines.insertRange(0, 1, firstLine); 416 lines.insertRange(0, 1, firstLine);
284 return lines.join("\n"); 417 return lines.join("\n");
285 }).join("\n"); 418 }).join("\n");
286 } 419 }
287 } 420 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698