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 | |
110 | goog.provide('goog.testing.AsyncTestCase'); |
111 | goog.provide('goog.testing.AsyncTestCase.ControlBreakingException'); |
112 | |
113 | goog.require('goog.testing.TestCase'); |
114 | goog.require('goog.testing.TestCase.Test'); |
115 | goog.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 | */ |
125 | goog.testing.AsyncTestCase = function(opt_name) { |
126 | goog.testing.TestCase.call(this, opt_name); |
127 | }; |
128 | goog.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 | */ |
136 | goog.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 | */ |
146 | goog.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 | */ |
159 | goog.testing.AsyncTestCase.ControlBreakingException.TO_STRING = |
160 | '[AsyncTestCase.ControlBreakingException]'; |
161 | |
162 | |
163 | /** |
164 | * Marks this object as a ControlBreakingException |
165 | * @type {boolean} |
166 | */ |
167 | goog.testing.AsyncTestCase.ControlBreakingException.prototype. |
168 | isControlBreakingException = true; |
169 | |
170 | |
171 | /** @override */ |
172 | goog.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 | */ |
184 | goog.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 | */ |
193 | goog.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 | */ |
201 | goog.testing.AsyncTestCase.prototype.enableDebugLogs_ = false; |
202 | |
203 | |
204 | /** |
205 | * A reference to the original asserts.js assert_() function. |
206 | * @private |
207 | */ |
208 | goog.testing.AsyncTestCase.prototype.origAssert_; |
209 | |
210 | |
211 | /** |
212 | * A reference to the original asserts.js fail() function. |
213 | * @private |
214 | */ |
215 | goog.testing.AsyncTestCase.prototype.origFail_; |
216 | |
217 | |
218 | /** |
219 | * A reference to the original window.onerror function. |
220 | * @type {Function|undefined} |
221 | * @private |
222 | */ |
223 | goog.testing.AsyncTestCase.prototype.origOnError_; |
224 | |
225 | |
226 | /** |
227 | * The stage of the test we are currently on. |
228 | * @type {Function|undefined}} |
229 | * @private |
230 | */ |
231 | goog.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 | */ |
239 | goog.testing.AsyncTestCase.prototype.curStepName_ = ''; |
240 | |
241 | |
242 | /** |
243 | * The stage of the test we should run next. |
244 | * @type {Function|undefined} |
245 | * @private |
246 | */ |
247 | goog.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 | */ |
255 | goog.testing.AsyncTestCase.prototype.nextStepName_ = ''; |
256 | |
257 | |
258 | /** |
259 | * The handle to the current setTimeout timer. |
260 | * @type {number} |
261 | * @private |
262 | */ |
263 | goog.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 | */ |
272 | goog.testing.AsyncTestCase.prototype.cleanedUp_ = false; |
273 | |
274 | |
275 | /** |
276 | * The currently active test. |
277 | * @type {goog.testing.TestCase.Test|undefined} |
278 | * @protected |
279 | */ |
280 | goog.testing.AsyncTestCase.prototype.activeTest; |
281 | |
282 | |
283 | /** |
284 | * A flag to prevent recursive exception handling. |
285 | * @type {boolean} |
286 | * @private |
287 | */ |
288 | goog.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 | */ |
296 | goog.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 | */ |
305 | goog.testing.AsyncTestCase.prototype.expectedSignalCount_ = 0; |
306 | |
307 | |
308 | /** |
309 | * Number of signals received. |
310 | * @type {number} |
311 | * @private |
312 | */ |
313 | goog.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 | */ |
322 | goog.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 | */ |
333 | goog.testing.AsyncTestCase.prototype.numControlExceptionsExpected_ = 0; |
334 | |
335 | |
336 | /** |
337 | * The current step name. |
338 | * @return {!string} Step name. |
339 | * @protected |
340 | */ |
341 | goog.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 | */ |
352 | goog.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 | */ |
364 | goog.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 | */ |
377 | goog.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 | */ |
390 | goog.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 | */ |
412 | goog.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 | */ |
425 | goog.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 | */ |
439 | goog.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 | */ |
498 | goog.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 | */ |
512 | goog.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 | */ |
524 | goog.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 | */ |
534 | goog.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 | */ |
544 | goog.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 | */ |
558 | goog.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 | */ |
582 | goog.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 | */ |
622 | goog.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 | */ |
654 | goog.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 | */ |
686 | goog.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 | */ |
703 | goog.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 | */ |
721 | goog.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 | */ |
736 | goog.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 | */ |
749 | goog.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 | */ |
776 | goog.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 | */ |
826 | goog.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 | */ |
836 | goog.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 | */ |
860 | goog.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 | */ |
872 | goog.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 | */ |
882 | goog.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 | */ |
893 | goog.testing.AsyncTestCase.prototype.doNext_ = function() { |
894 | this.setNextStep_(this.doIteration_, 'doIteration'); |
895 | this.doSuccess(/** @type {goog.testing.TestCase.Test} */(this.activeTest)); |
896 | }; |