lib/goog/testing/asynctestcase.js

1// Copyright 2010 The Closure Library Authors. All Rights Reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS-IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// All Rights Reserved.
15
16/**
17 * @fileoverview A class representing a set of test functions that use
18 * asynchronous functions that cannot be meaningfully mocked.
19 *
20 * To create a Google-compatable JsUnit test using this test case, put the
21 * following snippet in your test:
22 *
23 * var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
24 *
25 * To make the test runner wait for your asynchronous behaviour, use:
26 *
27 * asyncTestCase.waitForAsync('Waiting for xhr to respond');
28 *
29 * The next test will not start until the following call is made, or a
30 * timeout occurs:
31 *
32 * asyncTestCase.continueTesting();
33 *
34 * There does NOT need to be a 1:1 mapping of waitForAsync calls and
35 * continueTesting calls. The next test will be run after a single call to
36 * continueTesting is made, as long as there is no subsequent call to
37 * waitForAsync in the same thread.
38 *
39 * Example:
40 * // Returning here would cause the next test to be run.
41 * asyncTestCase.waitForAsync('description 1');
42 * // Returning here would *not* cause the next test to be run.
43 * // Only effect of additional waitForAsync() calls is an updated
44 * // description in the case of a timeout.
45 * asyncTestCase.waitForAsync('updated description');
46 * asyncTestCase.continueTesting();
47 * // Returning here would cause the next test to be run.
48 * asyncTestCase.waitForAsync('just kidding, still running.');
49 * // Returning here would *not* cause the next test to be run.
50 *
51 * The test runner can also be made to wait for more than one asynchronous
52 * event with:
53 *
54 * asyncTestCase.waitForSignals(n);
55 *
56 * The next test will not start until asyncTestCase.signal() is called n times,
57 * or the test step timeout is exceeded.
58 *
59 * This class supports asynchronous behaviour in all test functions except for
60 * tearDownPage. If such support is needed, it can be added.
61 *
62 * Example Usage:
63 *
64 * var asyncTestCase = goog.testing.AsyncTestCase.createAndInstall();
65 * // Optionally, set a longer-than-normal step timeout.
66 * asyncTestCase.stepTimeout = 30 * 1000;
67 *
68 * function testSetTimeout() {
69 * var step = 0;
70 * function stepCallback() {
71 * step++;
72 * switch (step) {
73 * case 1:
74 * var startTime = goog.now();
75 * asyncTestCase.waitForAsync('step 1');
76 * window.setTimeout(stepCallback, 100);
77 * break;
78 * case 2:
79 * assertTrue('Timeout fired too soon',
80 * goog.now() - startTime >= 100);
81 * asyncTestCase.waitForAsync('step 2');
82 * window.setTimeout(stepCallback, 100);
83 * break;
84 * case 3:
85 * assertTrue('Timeout fired too soon',
86 * goog.now() - startTime >= 200);
87 * asyncTestCase.continueTesting();
88 * break;
89 * default:
90 * fail('Unexpected call to stepCallback');
91 * }
92 * }
93 * stepCallback();
94 * }
95 *
96 * Known Issues:
97 * IE7 Exceptions:
98 * As the failingtest.html will show, it appears as though ie7 does not
99 * propagate an exception past a function called using the func.call()
100 * syntax. This causes case 3 of the failing tests (exceptions) to show up
101 * as timeouts in IE.
102 * window.onerror:
103 * This seems to catch errors only in ff2/ff3. It does not work in Safari or
104 * IE7. The consequence of this is that exceptions that would have been
105 * caught by window.onerror show up as timeouts.
106 *
107 * @author agrieve@google.com (Andrew Grieve)
108 */
109
110goog.provide('goog.testing.AsyncTestCase');
111goog.provide('goog.testing.AsyncTestCase.ControlBreakingException');
112
113goog.require('goog.testing.TestCase');
114goog.require('goog.testing.TestCase.Test');
115goog.require('goog.testing.asserts');
116
117
118
119/**
120 * A test case that is capable of running tests the contain asynchronous logic.
121 * @param {string=} opt_name A descriptive name for the test case.
122 * @extends {goog.testing.TestCase}
123 * @constructor
124 */
125goog.testing.AsyncTestCase = function(opt_name) {
126 goog.testing.TestCase.call(this, opt_name);
127};
128goog.inherits(goog.testing.AsyncTestCase, goog.testing.TestCase);
129
130
131/**
132 * Represents result of top stack function call.
133 * @typedef {{controlBreakingExceptionThrown: boolean, message: string}}
134 * @private
135 */
136goog.testing.AsyncTestCase.TopStackFuncResult_;
137
138
139
140/**
141 * An exception class used solely for control flow.
142 * @param {string=} opt_message Error message.
143 * @constructor
144 * @final
145 */
146goog.testing.AsyncTestCase.ControlBreakingException = function(opt_message) {
147 /**
148 * The exception message.
149 * @type {string}
150 */
151 this.message = opt_message || '';
152};
153
154
155/**
156 * Return value for .toString().
157 * @type {string}
158 */
159goog.testing.AsyncTestCase.ControlBreakingException.TO_STRING =
160 '[AsyncTestCase.ControlBreakingException]';
161
162
163/**
164 * Marks this object as a ControlBreakingException
165 * @type {boolean}
166 */
167goog.testing.AsyncTestCase.ControlBreakingException.prototype.
168 isControlBreakingException = true;
169
170
171/** @override */
172goog.testing.AsyncTestCase.ControlBreakingException.prototype.toString =
173 function() {
174 // This shows up in the console when the exception is not caught.
175 return goog.testing.AsyncTestCase.ControlBreakingException.TO_STRING;
176};
177
178
179/**
180 * How long to wait for a single step of a test to complete in milliseconds.
181 * A step starts when a call to waitForAsync() is made.
182 * @type {number}
183 */
184goog.testing.AsyncTestCase.prototype.stepTimeout = 1000;
185
186
187/**
188 * How long to wait after a failed test before moving onto the next one.
189 * The purpose of this is to allow any pending async callbacks from the failing
190 * test to finish up and not cause the next test to fail.
191 * @type {number}
192 */
193goog.testing.AsyncTestCase.prototype.timeToSleepAfterFailure = 500;
194
195
196/**
197 * Turn on extra logging to help debug failing async. tests.
198 * @type {boolean}
199 * @private
200 */
201goog.testing.AsyncTestCase.prototype.enableDebugLogs_ = false;
202
203
204/**
205 * A reference to the original asserts.js assert_() function.
206 * @private
207 */
208goog.testing.AsyncTestCase.prototype.origAssert_;
209
210
211/**
212 * A reference to the original asserts.js fail() function.
213 * @private
214 */
215goog.testing.AsyncTestCase.prototype.origFail_;
216
217
218/**
219 * A reference to the original window.onerror function.
220 * @type {Function|undefined}
221 * @private
222 */
223goog.testing.AsyncTestCase.prototype.origOnError_;
224
225
226/**
227 * The stage of the test we are currently on.
228 * @type {Function|undefined}}
229 * @private
230 */
231goog.testing.AsyncTestCase.prototype.curStepFunc_;
232
233
234/**
235 * The name of the stage of the test we are currently on.
236 * @type {string}
237 * @private
238 */
239goog.testing.AsyncTestCase.prototype.curStepName_ = '';
240
241
242/**
243 * The stage of the test we should run next.
244 * @type {Function|undefined}
245 * @private
246 */
247goog.testing.AsyncTestCase.prototype.nextStepFunc;
248
249
250/**
251 * The name of the stage of the test we should run next.
252 * @type {string}
253 * @private
254 */
255goog.testing.AsyncTestCase.prototype.nextStepName_ = '';
256
257
258/**
259 * The handle to the current setTimeout timer.
260 * @type {number}
261 * @private
262 */
263goog.testing.AsyncTestCase.prototype.timeoutHandle_ = 0;
264
265
266/**
267 * Marks if the cleanUp() function has been called for the currently running
268 * test.
269 * @type {boolean}
270 * @private
271 */
272goog.testing.AsyncTestCase.prototype.cleanedUp_ = false;
273
274
275/**
276 * The currently active test.
277 * @type {goog.testing.TestCase.Test|undefined}
278 * @protected
279 */
280goog.testing.AsyncTestCase.prototype.activeTest;
281
282
283/**
284 * A flag to prevent recursive exception handling.
285 * @type {boolean}
286 * @private
287 */
288goog.testing.AsyncTestCase.prototype.inException_ = false;
289
290
291/**
292 * Flag used to determine if we can move to the next step in the testing loop.
293 * @type {boolean}
294 * @private
295 */
296goog.testing.AsyncTestCase.prototype.isReady_ = true;
297
298
299/**
300 * Number of signals to wait for before continuing testing when waitForSignals
301 * is used.
302 * @type {number}
303 * @private
304 */
305goog.testing.AsyncTestCase.prototype.expectedSignalCount_ = 0;
306
307
308/**
309 * Number of signals received.
310 * @type {number}
311 * @private
312 */
313goog.testing.AsyncTestCase.prototype.receivedSignalCount_ = 0;
314
315
316/**
317 * Flag that tells us if there is a function in the call stack that will make
318 * a call to pump_().
319 * @type {boolean}
320 * @private
321 */
322goog.testing.AsyncTestCase.prototype.returnWillPump_ = false;
323
324
325/**
326 * The number of times we have thrown a ControlBreakingException so that we
327 * know not to complain in our window.onerror handler. In Webkit, window.onerror
328 * is not supported, and so this counter will keep going up but we won't care
329 * about it.
330 * @type {number}
331 * @private
332 */
333goog.testing.AsyncTestCase.prototype.numControlExceptionsExpected_ = 0;
334
335
336/**
337 * The current step name.
338 * @return {!string} Step name.
339 * @protected
340 */
341goog.testing.AsyncTestCase.prototype.getCurrentStepName = function() {
342 return this.curStepName_;
343};
344
345
346/**
347 * Preferred way of creating an AsyncTestCase. Creates one and initializes it
348 * with the G_testRunner.
349 * @param {string=} opt_name A descriptive name for the test case.
350 * @return {!goog.testing.AsyncTestCase} The created AsyncTestCase.
351 */
352goog.testing.AsyncTestCase.createAndInstall = function(opt_name) {
353 var asyncTestCase = new goog.testing.AsyncTestCase(opt_name);
354 goog.testing.TestCase.initializeTestRunner(asyncTestCase);
355 return asyncTestCase;
356};
357
358
359/**
360 * Informs the testcase not to continue to the next step in the test cycle
361 * until continueTesting is called.
362 * @param {string=} opt_name A description of what we are waiting for.
363 */
364goog.testing.AsyncTestCase.prototype.waitForAsync = function(opt_name) {
365 this.isReady_ = false;
366 this.curStepName_ = opt_name || this.curStepName_;
367
368 // Reset the timer that tracks if the async test takes too long.
369 this.stopTimeoutTimer_();
370 this.startTimeoutTimer_();
371};
372
373
374/**
375 * Continue with the next step in the test cycle.
376 */
377goog.testing.AsyncTestCase.prototype.continueTesting = function() {
378 if (this.receivedSignalCount_ < this.expectedSignalCount_) {
379 var remaining = this.expectedSignalCount_ - this.receivedSignalCount_;
380 throw Error('Still waiting for ' + remaining + ' signals.');
381 }
382 this.endCurrentStep_();
383};
384
385
386/**
387 * Ends the current test step and queues the next test step to run.
388 * @private
389 */
390goog.testing.AsyncTestCase.prototype.endCurrentStep_ = function() {
391 if (!this.isReady_) {
392 // We are a potential entry point, so we pump.
393 this.isReady_ = true;
394 this.stopTimeoutTimer_();
395 // Run this in a setTimeout so that the caller has a chance to call
396 // waitForAsync() again before we continue.
397 this.timeout(goog.bind(this.pump_, this, null), 0);
398 }
399};
400
401
402/**
403 * Informs the testcase not to continue to the next step in the test cycle
404 * until signal is called the specified number of times. Within a test, this
405 * function behaves additively if called multiple times; the number of signals
406 * to wait for will be the sum of all expected number of signals this function
407 * was called with.
408 * @param {number} times The number of signals to receive before
409 * continuing testing.
410 * @param {string=} opt_name A description of what we are waiting for.
411 */
412goog.testing.AsyncTestCase.prototype.waitForSignals =
413 function(times, opt_name) {
414 this.expectedSignalCount_ += times;
415 if (this.receivedSignalCount_ < this.expectedSignalCount_) {
416 this.waitForAsync(opt_name);
417 }
418};
419
420
421/**
422 * Signals once to continue with the test. If this is the last signal that the
423 * test was waiting on, call continueTesting.
424 */
425goog.testing.AsyncTestCase.prototype.signal = function() {
426 if (++this.receivedSignalCount_ === this.expectedSignalCount_ &&
427 this.expectedSignalCount_ > 0) {
428 this.endCurrentStep_();
429 }
430};
431
432
433/**
434 * Handles an exception thrown by a test.
435 * @param {*=} opt_e The exception object associated with the failure
436 * or a string.
437 * @throws Always throws a ControlBreakingException.
438 */
439goog.testing.AsyncTestCase.prototype.doAsyncError = function(opt_e) {
440 // If we've caught an exception that we threw, then just pass it along. This
441 // can happen if doAsyncError() was called from a call to assert and then
442 // again by pump_().
443 if (opt_e && opt_e.isControlBreakingException) {
444 throw opt_e;
445 }
446
447 // Prevent another timeout error from triggering for this test step.
448 this.stopTimeoutTimer_();
449
450 // doError() uses test.name. Here, we create a dummy test and give it a more
451 // helpful name based on the step we're currently on.
452 var fakeTestObj = new goog.testing.TestCase.Test(this.curStepName_,
453 goog.nullFunction);
454 if (this.activeTest) {
455 fakeTestObj.name = this.activeTest.name + ' [' + fakeTestObj.name + ']';
456 }
457
458 if (this.activeTest) {
459 // Note: if the test has an error, and then tearDown has an error, they will
460 // both be reported.
461 this.doError(fakeTestObj, opt_e);
462 } else {
463 this.exceptionBeforeTest = opt_e;
464 }
465
466 // This is a potential entry point, so we pump. We also add in a bit of a
467 // delay to try and prevent any async behavior from the failed test from
468 // causing the next test to fail.
469 this.timeout(goog.bind(this.pump_, this, this.doAsyncErrorTearDown_),
470 this.timeToSleepAfterFailure);
471
472 // We just caught an exception, so we do not want the code above us on the
473 // stack to continue executing. If pump_ is in our call-stack, then it will
474 // batch together multiple errors, so we only increment the count if pump_ is
475 // not in the stack and let pump_ increment the count when it batches them.
476 if (!this.returnWillPump_) {
477 this.numControlExceptionsExpected_ += 1;
478 this.dbgLog_('doAsynError: numControlExceptionsExpected_ = ' +
479 this.numControlExceptionsExpected_ + ' and throwing exception.');
480 }
481
482 // Copy the error message to ControlBreakingException.
483 var message = '';
484 if (typeof opt_e == 'string') {
485 message = opt_e;
486 } else if (opt_e && opt_e.message) {
487 message = opt_e.message;
488 }
489 throw new goog.testing.AsyncTestCase.ControlBreakingException(message);
490};
491
492
493/**
494 * Sets up the test page and then waits until the test case has been marked
495 * as ready before executing the tests.
496 * @override
497 */
498goog.testing.AsyncTestCase.prototype.runTests = function() {
499 this.hookAssert_();
500 this.hookOnError_();
501
502 this.setNextStep_(this.doSetUpPage_, 'setUpPage');
503 // We are an entry point, so we pump.
504 this.pump_();
505};
506
507
508/**
509 * Starts the tests.
510 * @override
511 */
512goog.testing.AsyncTestCase.prototype.cycleTests = function() {
513 // We are an entry point, so we pump.
514 this.saveMessage('Start');
515 this.setNextStep_(this.doIteration_, 'doIteration');
516 this.pump_();
517};
518
519
520/**
521 * Finalizes the test case, called when the tests have finished executing.
522 * @override
523 */
524goog.testing.AsyncTestCase.prototype.finalize = function() {
525 this.unhookAll_();
526 this.setNextStep_(null, 'finalized');
527 goog.testing.AsyncTestCase.superClass_.finalize.call(this);
528};
529
530
531/**
532 * Enables verbose logging of what is happening inside of the AsyncTestCase.
533 */
534goog.testing.AsyncTestCase.prototype.enableDebugLogging = function() {
535 this.enableDebugLogs_ = true;
536};
537
538
539/**
540 * Logs the given debug message to the console (when enabled).
541 * @param {string} message The message to log.
542 * @private
543 */
544goog.testing.AsyncTestCase.prototype.dbgLog_ = function(message) {
545 if (this.enableDebugLogs_) {
546 this.log('AsyncTestCase - ' + message);
547 }
548};
549
550
551/**
552 * Wraps doAsyncError() for when we are sure that the test runner has no user
553 * code above it in the stack.
554 * @param {string|Error=} opt_e The exception object associated with the
555 * failure or a string.
556 * @private
557 */
558goog.testing.AsyncTestCase.prototype.doTopOfStackAsyncError_ =
559 function(opt_e) {
560 /** @preserveTry */
561 try {
562 this.doAsyncError(opt_e);
563 } catch (e) {
564 // We know that we are on the top of the stack, so there is no need to
565 // throw this exception in this case.
566 if (e.isControlBreakingException) {
567 this.numControlExceptionsExpected_ -= 1;
568 this.dbgLog_('doTopOfStackAsyncError_: numControlExceptionsExpected_ = ' +
569 this.numControlExceptionsExpected_ + ' and catching exception.');
570 } else {
571 throw e;
572 }
573 }
574};
575
576
577/**
578 * Calls the tearDown function, catching any errors, and then moves on to
579 * the next step in the testing cycle.
580 * @private
581 */
582goog.testing.AsyncTestCase.prototype.doAsyncErrorTearDown_ = function() {
583 if (this.inException_) {
584 // We get here if tearDown is throwing the error.
585 // Upon calling continueTesting, the inline function 'doAsyncError' (set
586 // below) is run.
587 this.endCurrentStep_();
588 } else {
589 this.inException_ = true;
590 this.isReady_ = true;
591
592 // The continue point is different depending on if the error happened in
593 // setUpPage() or in setUp()/test*()/tearDown().
594 var stepFuncAfterError = this.nextStepFunc_;
595 var stepNameAfterError = 'TestCase.execute (after error)';
596 if (this.activeTest) {
597 stepFuncAfterError = this.doIteration_;
598 stepNameAfterError = 'doIteration (after error)';
599 }
600
601 // We must set the next step before calling tearDown.
602 this.setNextStep_(function() {
603 this.inException_ = false;
604 // This is null when an error happens in setUpPage.
605 this.setNextStep_(stepFuncAfterError, stepNameAfterError);
606 }, 'doAsyncError');
607
608 // Call the test's tearDown().
609 if (!this.cleanedUp_) {
610 this.cleanedUp_ = true;
611 this.tearDown();
612 }
613 }
614};
615
616
617/**
618 * Replaces the asserts.js assert_() and fail() functions with a wrappers to
619 * catch the exceptions.
620 * @private
621 */
622goog.testing.AsyncTestCase.prototype.hookAssert_ = function() {
623 if (!this.origAssert_) {
624 this.origAssert_ = _assert;
625 this.origFail_ = fail;
626 var self = this;
627 _assert = function() {
628 /** @preserveTry */
629 try {
630 self.origAssert_.apply(this, arguments);
631 } catch (e) {
632 self.dbgLog_('Wrapping failed assert()');
633 self.doAsyncError(e);
634 }
635 };
636 fail = function() {
637 /** @preserveTry */
638 try {
639 self.origFail_.apply(this, arguments);
640 } catch (e) {
641 self.dbgLog_('Wrapping fail()');
642 self.doAsyncError(e);
643 }
644 };
645 }
646};
647
648
649/**
650 * Sets a window.onerror handler for catching exceptions that happen in async
651 * callbacks. Note that as of Safari 3.1, Safari does not support this.
652 * @private
653 */
654goog.testing.AsyncTestCase.prototype.hookOnError_ = function() {
655 if (!this.origOnError_) {
656 this.origOnError_ = window.onerror;
657 var self = this;
658 window.onerror = function(error, url, line) {
659 // Ignore exceptions that we threw on purpose.
660 var cbe =
661 goog.testing.AsyncTestCase.ControlBreakingException.TO_STRING;
662 if (String(error).indexOf(cbe) != -1 &&
663 self.numControlExceptionsExpected_) {
664 self.numControlExceptionsExpected_ -= 1;
665 self.dbgLog_('window.onerror: numControlExceptionsExpected_ = ' +
666 self.numControlExceptionsExpected_ + ' and ignoring exception. ' +
667 error);
668 // Tell the browser not to compain about the error.
669 return true;
670 } else {
671 self.dbgLog_('window.onerror caught exception.');
672 var message = error + '\nURL: ' + url + '\nLine: ' + line;
673 self.doTopOfStackAsyncError_(message);
674 // Tell the browser to complain about the error.
675 return false;
676 }
677 };
678 }
679};
680
681
682/**
683 * Unhooks window.onerror and _assert.
684 * @private
685 */
686goog.testing.AsyncTestCase.prototype.unhookAll_ = function() {
687 if (this.origOnError_) {
688 window.onerror = this.origOnError_;
689 this.origOnError_ = null;
690 _assert = this.origAssert_;
691 this.origAssert_ = null;
692 fail = this.origFail_;
693 this.origFail_ = null;
694 }
695};
696
697
698/**
699 * Enables the timeout timer. This timer fires unless continueTesting is
700 * called.
701 * @private
702 */
703goog.testing.AsyncTestCase.prototype.startTimeoutTimer_ = function() {
704 if (!this.timeoutHandle_ && this.stepTimeout > 0) {
705 this.timeoutHandle_ = this.timeout(goog.bind(function() {
706 this.dbgLog_('Timeout timer fired with id ' + this.timeoutHandle_);
707 this.timeoutHandle_ = 0;
708
709 this.doTopOfStackAsyncError_('Timed out while waiting for ' +
710 'continueTesting() to be called.');
711 }, this, null), this.stepTimeout);
712 this.dbgLog_('Started timeout timer with id ' + this.timeoutHandle_);
713 }
714};
715
716
717/**
718 * Disables the timeout timer.
719 * @private
720 */
721goog.testing.AsyncTestCase.prototype.stopTimeoutTimer_ = function() {
722 if (this.timeoutHandle_) {
723 this.dbgLog_('Clearing timeout timer with id ' + this.timeoutHandle_);
724 this.clearTimeout(this.timeoutHandle_);
725 this.timeoutHandle_ = 0;
726 }
727};
728
729
730/**
731 * Sets the next function to call in our sequence of async callbacks.
732 * @param {Function} func The function that executes the next step.
733 * @param {string} name A description of the next step.
734 * @private
735 */
736goog.testing.AsyncTestCase.prototype.setNextStep_ = function(func, name) {
737 this.nextStepFunc_ = func && goog.bind(func, this);
738 this.nextStepName_ = name;
739};
740
741
742/**
743 * Calls the given function, redirecting any exceptions to doAsyncError.
744 * @param {Function} func The function to call.
745 * @return {!goog.testing.AsyncTestCase.TopStackFuncResult_} Returns a
746 * TopStackFuncResult_.
747 * @private
748 */
749goog.testing.AsyncTestCase.prototype.callTopOfStackFunc_ = function(func) {
750 /** @preserveTry */
751 try {
752 func.call(this);
753 return {controlBreakingExceptionThrown: false, message: ''};
754 } catch (e) {
755 this.dbgLog_('Caught exception in callTopOfStackFunc_');
756 /** @preserveTry */
757 try {
758 this.doAsyncError(e);
759 return {controlBreakingExceptionThrown: false, message: ''};
760 } catch (e2) {
761 if (!e2.isControlBreakingException) {
762 throw e2;
763 }
764 return {controlBreakingExceptionThrown: true, message: e2.message};
765 }
766 }
767};
768
769
770/**
771 * Calls the next callback when the isReady_ flag is true.
772 * @param {Function=} opt_doFirst A function to call before pumping.
773 * @private
774 * @throws Throws a ControlBreakingException if there were any failing steps.
775 */
776goog.testing.AsyncTestCase.prototype.pump_ = function(opt_doFirst) {
777 // If this function is already above us in the call-stack, then we should
778 // return rather than pumping in order to minimize call-stack depth.
779 if (!this.returnWillPump_) {
780 this.setBatchTime(this.now());
781 this.returnWillPump_ = true;
782 var topFuncResult = {};
783
784 if (opt_doFirst) {
785 topFuncResult = this.callTopOfStackFunc_(opt_doFirst);
786 }
787 // Note: we don't check for this.running here because it is not set to true
788 // while executing setUpPage and tearDownPage.
789 // Also, if isReady_ is false, then one of two things will happen:
790 // 1. Our timeout callback will be called.
791 // 2. The tests will call continueTesting(), which will call pump_() again.
792 while (this.isReady_ && this.nextStepFunc_ &&
793 !topFuncResult.controlBreakingExceptionThrown) {
794 this.curStepFunc_ = this.nextStepFunc_;
795 this.curStepName_ = this.nextStepName_;
796 this.nextStepFunc_ = null;
797 this.nextStepName_ = '';
798
799 this.dbgLog_('Performing step: ' + this.curStepName_);
800 topFuncResult =
801 this.callTopOfStackFunc_(/** @type {Function} */(this.curStepFunc_));
802
803 // If the max run time is exceeded call this function again async so as
804 // not to block the browser.
805 var delta = this.now() - this.getBatchTime();
806 if (delta > goog.testing.TestCase.maxRunTime &&
807 !topFuncResult.controlBreakingExceptionThrown) {
808 this.saveMessage('Breaking async');
809 var self = this;
810 this.timeout(function() { self.pump_(); }, 100);
811 break;
812 }
813 }
814 this.returnWillPump_ = false;
815 } else if (opt_doFirst) {
816 opt_doFirst.call(this);
817 }
818};
819
820
821/**
822 * Sets up the test page and then waits untill the test case has been marked
823 * as ready before executing the tests.
824 * @private
825 */
826goog.testing.AsyncTestCase.prototype.doSetUpPage_ = function() {
827 this.setNextStep_(this.execute, 'TestCase.execute');
828 this.setUpPage();
829};
830
831
832/**
833 * Step 1: Move to the next test.
834 * @private
835 */
836goog.testing.AsyncTestCase.prototype.doIteration_ = function() {
837 this.expectedSignalCount_ = 0;
838 this.receivedSignalCount_ = 0;
839 this.activeTest = this.next();
840 if (this.activeTest && this.running) {
841 this.result_.runCount++;
842 // If this test should be marked as having failed, doIteration will go
843 // straight to the next test.
844 if (this.maybeFailTestEarly(this.activeTest)) {
845 this.setNextStep_(this.doIteration_, 'doIteration');
846 } else {
847 this.setNextStep_(this.doSetUp_, 'setUp');
848 }
849 } else {
850 // All tests done.
851 this.finalize();
852 }
853};
854
855
856/**
857 * Step 2: Call setUp().
858 * @private
859 */
860goog.testing.AsyncTestCase.prototype.doSetUp_ = function() {
861 this.log('Running test: ' + this.activeTest.name);
862 this.cleanedUp_ = false;
863 this.setNextStep_(this.doExecute_, this.activeTest.name);
864 this.setUp();
865};
866
867
868/**
869 * Step 3: Call test.execute().
870 * @private
871 */
872goog.testing.AsyncTestCase.prototype.doExecute_ = function() {
873 this.setNextStep_(this.doTearDown_, 'tearDown');
874 this.activeTest.execute();
875};
876
877
878/**
879 * Step 4: Call tearDown().
880 * @private
881 */
882goog.testing.AsyncTestCase.prototype.doTearDown_ = function() {
883 this.cleanedUp_ = true;
884 this.setNextStep_(this.doNext_, 'doNext');
885 this.tearDown();
886};
887
888
889/**
890 * Step 5: Call doSuccess()
891 * @private
892 */
893goog.testing.AsyncTestCase.prototype.doNext_ = function() {
894 this.setNextStep_(this.doIteration_, 'doIteration');
895 this.doSuccess(/** @type {goog.testing.TestCase.Test} */(this.activeTest));
896};