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

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

Issue 12288061: Support nested tasks in 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;
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after
120 heartbeat(); 120 heartbeat();
121 } 121 }
122 122
123 /// 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
124 /// order. Any errors in [setUp] will cause [onException] to run. 124 /// order. Any errors in [setUp] will cause [onException] to run.
125 Future run(void setUp()) { 125 Future run(void setUp()) {
126 return new Future.immediate(null).then((_) { 126 return new Future.immediate(null).then((_) {
127 try { 127 try {
128 setUp(); 128 setUp();
129 } catch (e, stackTrace) { 129 } catch (e, stackTrace) {
130 _state = ScheduleState.RUNNING;
Bob Nystrom 2013/02/19 23:15:04 Document why you're setting this state even though
nweiz 2013/02/20 00:23:12 Done.
130 throw new ScheduleError.from(this, e, stackTrace: stackTrace); 131 throw new ScheduleError.from(this, e, stackTrace: stackTrace);
131 } 132 }
132 133
133 _state = ScheduleState.RUNNING; 134 _state = ScheduleState.RUNNING;
134 return tasks._run(); 135 return tasks._run();
135 }).catchError((e) { 136 }).catchError((e) {
136 errors.add(e); 137 _addError(e);
137 return onException._run().catchError((innerError) { 138 return onException._run().catchError((innerError) {
138 // If an error occurs in a task in the onException queue, make sure it's 139 // If an error occurs in a task in the onException queue, make sure it's
139 // registered in the error list and re-throw it. We could also re-throw 140 // registered in the error list and re-throw it. We could also re-throw
140 // `e`; ultimately, all the errors will be shown to the user if any 141 // `e`; ultimately, all the errors will be shown to the user if any
141 // ScheduleError is thrown. 142 // ScheduleError is thrown.
142 errors.add(innerError); 143 _addError(innerError);
143 throw innerError; 144 throw innerError;
144 }).then((_) { 145 }).then((_) {
145 // If there are no errors in the onException queue, re-throw the 146 // If there are no errors in the onException queue, re-throw the
146 // original error that caused it to run. 147 // original error that caused it to run.
147 throw e; 148 throw e;
148 }); 149 });
149 }).whenComplete(() { 150 }).whenComplete(() {
150 return onComplete._run().catchError((e) { 151 return onComplete._run().catchError((e) {
151 // If an error occurs in a task in the onComplete queue, make sure it's 152 // If an error occurs in a task in the onComplete queue, make sure it's
152 // registered in the error list and re-throw it. 153 // registered in the error list and re-throw it.
153 errors.add(e); 154 _addError(e);
154 throw e; 155 throw e;
155 }); 156 });
156 }).whenComplete(() { 157 }).whenComplete(() {
157 if (_timeoutTimer != null) _timeoutTimer.cancel(); 158 if (_timeoutTimer != null) _timeoutTimer.cancel();
158 _state = ScheduleState.DONE; 159 _state = ScheduleState.DONE;
159 }); 160 });
160 } 161 }
161 162
162 /// Signals that an out-of-band error has occurred. Using [wrapAsync] along 163 /// Signals that an out-of-band error has occurred. Using [wrapAsync] along
163 /// with `throw` is usually preferable to calling this directly. 164 /// with `throw` is usually preferable to calling this directly.
(...skipping 18 matching lines...) Expand all
182 } 183 }
183 } 184 }
184 185
185 /// Notifies the schedule of an error that occurred in a task or out-of-band 186 /// 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 /// 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 /// 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 /// along with the timeout error; otherwise, a top-level error will be thrown.
189 void _signalPostTimeoutError(error, [stackTrace]) { 190 void _signalPostTimeoutError(error, [stackTrace]) {
190 var scheduleError = new ScheduleError.from(this, error, 191 var scheduleError = new ScheduleError.from(this, error,
191 stackTrace: stackTrace); 192 stackTrace: stackTrace);
192 errors.add(scheduleError); 193 _addError(scheduleError);
193 if (_state == ScheduleState.DONE) { 194 if (_state == ScheduleState.DONE) {
194 throw new StateError( 195 throw new StateError(
195 "An out-of-band error was caught after the test timed out.\n" 196 "An out-of-band error was caught after the test timed out.\n"
196 "${errorString()}"); 197 "${errorString()}");
197 } 198 }
198 } 199 }
199 200
200 /// Returns a function wrapping [fn] that pipes any errors into the schedule 201 /// Returns a function wrapping [fn] that pipes any errors into the schedule
201 /// chain. This will also block the current task queue from completing until 202 /// chain. This will also block the current task queue from completing until
202 /// the returned function has been called. It's used to ensure that 203 /// the returned function has been called. It's used to ensure that
203 /// out-of-band callbacks are properly handled by the scheduled test. 204 /// out-of-band callbacks are properly handled by the scheduled test.
204 /// 205 ///
205 /// The top-level `wrapAsync` function should usually be used in preference to 206 /// The top-level `wrapAsync` function should usually be used in preference to
206 /// this. 207 /// this in test code.
207 Function wrapAsync(fn(arg)) { 208 Function wrapAsync(fn(arg)) {
208 if (_state == ScheduleState.DONE) { 209 if (_state == ScheduleState.DONE) {
209 throw new StateError("wrapAsync called after the schedule has finished " 210 throw new StateError("wrapAsync called after the schedule has finished "
210 "running."); 211 "running.");
211 } 212 }
212 heartbeat(); 213 heartbeat();
213 214
214 var queue = currentQueue; 215 var queue = currentQueue;
215 // It's possible that the queue timed out before this. 216 // It's possible that the queue timed out before this.
216 bool _timedOut() => queue != currentQueue || _pendingCallbacks == 0; 217 bool _timedOut() => queue != currentQueue || _pendingCallbacks == 0;
(...skipping 13 matching lines...) Expand all
230 231
231 _pendingCallbacks--; 232 _pendingCallbacks--;
232 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) { 233 if (_pendingCallbacks == 0 && _noPendingCallbacks != null) {
233 _noPendingCallbacks.complete(); 234 _noPendingCallbacks.complete();
234 _noPendingCallbacks = null; 235 _noPendingCallbacks = null;
235 } 236 }
236 } 237 }
237 }; 238 };
238 } 239 }
239 240
241 /// Like [wrapAsync], this ensures that the current task queue waits for
242 /// out-of-band asynchronous code, and that errors raised in that code are
243 /// handled correctly. However, [wrapFuture] wraps a [Future] chain rather
244 /// than a single callback.
245 ///
246 /// The returned [Future] completes to the same value or error as [future].
247 ///
248 /// The top-level `wrapFuture` function should usually be used in preference
249 /// to this in test code.
250 Future wrapFuture(Future future) {
251 var doneCb = wrapAsync((_) => null);
Bob Nystrom 2013/02/19 23:15:04 Cb -> Callback Or just inline this function in th
nweiz 2013/02/20 00:23:12 Done.
252 done() => new Future.immediate(null).then(doneCb);
253
254 future = future.then((result) {
255 done();
256 return result;
257 }).catchError((e) {
258 signalError(e);
259 done();
260 throw e;
261 });
262
263 // Don't top-level the error, since it's already been signaled to the
264 // schedule.
265 future.catchError((_) => null);
266
267 return future;
268 }
269
240 /// Returns a string representation of all errors registered on this schedule. 270 /// Returns a string representation of all errors registered on this schedule.
241 String errorString() { 271 String errorString() {
242 if (errors.isEmpty) return "The schedule had no errors."; 272 if (errors.isEmpty) return "The schedule had no errors.";
243 if (errors.length == 1) return errors.first.toString(); 273 if (errors.length == 1) return errors.first.toString();
244 var errorStrings = errors.map((e) => e.toString()).join("\n================" 274 var errorStrings = errors.map((e) => e.toString()).join("\n================"
245 "================================================================\n"); 275 "================================================================\n");
246 return "The schedule had ${errors.length} errors:\n$errorStrings"; 276 return "The schedule had ${errors.length} errors:\n$errorStrings";
247 } 277 }
248 278
249 /// Notifies the schedule that progress is being made on an asynchronous task. 279 /// Notifies the schedule that progress is being made on an asynchronous task.
(...skipping 28 matching lines...) Expand all
278 } 308 }
279 } 309 }
280 310
281 /// Returns a [Future] that will complete once there are no pending 311 /// Returns a [Future] that will complete once there are no pending
282 /// out-of-band callbacks. 312 /// out-of-band callbacks.
283 Future _awaitNoPendingCallbacks() { 313 Future _awaitNoPendingCallbacks() {
284 if (_pendingCallbacks == 0) return new Future.immediate(null); 314 if (_pendingCallbacks == 0) return new Future.immediate(null);
285 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer(); 315 if (_noPendingCallbacks == null) _noPendingCallbacks = new Completer();
286 return _noPendingCallbacks.future; 316 return _noPendingCallbacks.future;
287 } 317 }
318
319 /// Register an error in the schedule's error list. This ensures that there
320 /// are no duplicate errors, and that all errors are wrapped in
321 /// [ScheduleError].
322 void _addError(error) {
323 if (errors.contains(error)) return;
324 errors.add(new ScheduleError.from(this, error));
325 }
288 } 326 }
289 327
290 /// An enum of states for a [Schedule]. 328 /// An enum of states for a [Schedule].
291 class ScheduleState { 329 class ScheduleState {
292 /// The schedule can have tasks added to its queue, but is not yet running 330 /// The schedule can have tasks added to its queue, but is not yet running
293 /// them. 331 /// them.
294 static const SET_UP = const ScheduleState._("SET_UP"); 332 static const SET_UP = const ScheduleState._("SET_UP");
295 333
296 /// The schedule is actively running tasks. This includes running tasks in 334 /// The schedule is actively running tasks. This includes running tasks in
297 /// [Schedule.onException] and [Schedule.onComplete]. 335 /// [Schedule.onException] and [Schedule.onComplete].
(...skipping 28 matching lines...) Expand all
326 /// indicates that the queue should stop as soon as possible and re-throw this 364 /// indicates that the queue should stop as soon as possible and re-throw this
327 /// error. 365 /// error.
328 ScheduleError _error; 366 ScheduleError _error;
329 367
330 /// The [SubstituteFuture] for the currently-running task in the queue, or 368 /// The [SubstituteFuture] for the currently-running task in the queue, or
331 /// null if no task is currently running. 369 /// null if no task is currently running.
332 SubstituteFuture _taskFuture; 370 SubstituteFuture _taskFuture;
333 371
334 TaskQueue._(this.name, this._schedule); 372 TaskQueue._(this.name, this._schedule);
335 373
374 /// Whether this queue is currently running.
375 bool get isRunning => _schedule.state == ScheduleState.RUNNING &&
376 _schedule.currentQueue == this;
Bob Nystrom 2013/02/19 23:15:04 Indent +2.
nweiz 2013/02/20 00:23:12 Done.
377
336 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks 378 /// Schedules a task, [fn], to run asynchronously as part of this queue. Tasks
337 /// will be run in the order they're scheduled. In [fn] returns a [Future], 379 /// will be run in the order they're scheduled. In [fn] returns a [Future],
338 /// tasks after it won't be run until that [Future] completes. 380 /// tasks after it won't be run until that [Future] completes.
339 /// 381 ///
340 /// The return value will be completed once the scheduled task has finished 382 /// The return value will be completed once the scheduled task has finished
341 /// running. Its return value is the same as the return value of [fn], or the 383 /// running. Its return value is the same as the return value of [fn], or the
342 /// value it completes to if it's a [Future]. 384 /// value it completes to if it's a [Future].
343 /// 385 ///
344 /// If [description] is passed, it's used to describe the task for debugging 386 /// If [description] is passed, it's used to describe the task for debugging
345 /// purposes when an error occurs. 387 /// purposes when an error occurs.
388 ///
389 /// If this is called when this queue is currently running, it will run [fn]
390 /// on the next event loop iteration rather than adding it to a queue. The
391 /// current task will not complete until [fn] (and any [Future] it returns)
392 /// has finished running. Any errors in [fn] will automatically be handled.
346 Future schedule(fn(), [String description]) { 393 Future schedule(fn(), [String description]) {
347 var task = new Task(fn, this, description); 394 if (isRunning) {
395 var task = _schedule.currentTask;
396 var wrappedFn = () => _schedule.wrapFuture(
397 new Future.immediate(null).then((_) => fn()));
398 if (task == null) return wrappedFn();
399 return task.runChild(wrappedFn, description);
400 }
401
402 var task = new Task(fn, description, this);
348 _contents.add(task); 403 _contents.add(task);
349 return task.result; 404 return task.result;
350 } 405 }
351 406
352 /// Runs all the tasks in this queue in order. 407 /// Runs all the tasks in this queue in order.
353 Future _run() { 408 Future _run() {
354 _schedule._currentQueue = this; 409 _schedule._currentQueue = this;
355 _schedule.heartbeat(); 410 _schedule.heartbeat();
356 return Future.forEach(_contents, (task) { 411 return Future.forEach(_contents, (task) {
357 _schedule._currentTask = task; 412 _schedule._currentTask = task;
358 if (_error != null) throw _error; 413 if (_error != null) throw _error;
359 414
360 _taskFuture = new SubstituteFuture(task.fn()); 415 _taskFuture = new SubstituteFuture(task.fn());
361 return _taskFuture.whenComplete(() { 416 return _taskFuture.whenComplete(() {
362 _taskFuture = null; 417 _taskFuture = null;
363 _schedule.heartbeat(); 418 _schedule.heartbeat();
364 }).catchError((e) { 419 }).catchError((e) {
365 if (_error != null) _schedule.errors.add(_error); 420 if (_error != null) _schedule._addError(_error);
366 throw new ScheduleError.from(_schedule, e); 421 throw new ScheduleError.from(_schedule, e);
367 }); 422 });
368 }).whenComplete(() { 423 }).whenComplete(() {
369 _schedule._currentTask = null; 424 _schedule._currentTask = null;
370 return _schedule._awaitNoPendingCallbacks(); 425 return _schedule._awaitNoPendingCallbacks();
371 }).then((_) { 426 }).then((_) {
372 _schedule.heartbeat(); 427 _schedule.heartbeat();
373 if (_error != null) throw _error; 428 if (_error != null) throw _error;
374 }); 429 });
375 } 430 }
376 431
377 /// Signals that an out-of-band error has been detected and the queue should 432 /// Signals that an out-of-band error has been detected and the queue should
378 /// stop running as soon as possible. 433 /// stop running as soon as possible.
379 void _signalError(ScheduleError error) { 434 void _signalError(ScheduleError error) {
380 // If multiple errors are detected while a task is running, make sure the 435 // If multiple errors are detected while a task is running, make sure the
381 // earlier ones are recorded in the schedule. 436 // earlier ones are recorded in the schedule.
382 if (_error != null) _schedule.errors.add(_error); 437 if (_error != null) _schedule._addError(_error);
383 _error = error; 438 _error = error;
384 } 439 }
385 440
386 /// Notifies the queue that it has timed out and it needs to terminate 441 /// Notifies the queue that it has timed out and it needs to terminate
387 /// immediately with a timeout error. 442 /// immediately with a timeout error.
388 void _signalTimeout(ScheduleError error) { 443 void _signalTimeout(ScheduleError error) {
389 if (_taskFuture != null) { 444 if (_taskFuture != null) {
390 // Catch errors coming off the old task future, in case it completes after 445 // Catch errors coming off the old task future, in case it completes after
391 // timing out. 446 // timing out.
392 _taskFuture.substitute(new Future.immediateError(error)).catchError((e) { 447 _taskFuture.substitute(new Future.immediateError(error)).catchError((e) {
(...skipping 18 matching lines...) Expand all
411 return _contents.map((task) { 466 return _contents.map((task) {
412 var lines = task.toString().split("\n"); 467 var lines = task.toString().split("\n");
413 var firstLine = task == highlight ? 468 var firstLine = task == highlight ?
414 "> ${lines.first}" : "* ${lines.first}"; 469 "> ${lines.first}" : "* ${lines.first}";
415 lines = new List.from(lines.skip(1).map((line) => "| $line")); 470 lines = new List.from(lines.skip(1).map((line) => "| $line"));
416 lines.insertRange(0, 1, firstLine); 471 lines.insertRange(0, 1, firstLine);
417 return lines.join("\n"); 472 return lines.join("\n");
418 }).join("\n"); 473 }).join("\n");
419 } 474 }
420 } 475 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698