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

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

Powered by Google App Engine
This is Rietveld 408576698