asynctestcase.js 26 KB

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