continuationtestcase.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. // Copyright 2009 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. /**
  15. * @fileoverview Defines test classes for tests that can wait for conditions.
  16. *
  17. * Normal unit tests must complete their test logic within a single function
  18. * execution. This is ideal for most tests, but makes it difficult to test
  19. * routines that require real time to complete. The tests and TestCase in this
  20. * file allow for tests that can wait until a condition is true before
  21. * continuing execution.
  22. *
  23. * Each test has the typical three phases of execution: setUp, the test itself,
  24. * and tearDown. During each phase, the test function may add wait conditions,
  25. * which result in new test steps being added for that phase. All steps in a
  26. * given phase must complete before moving on to the next phase. An error in
  27. * any phase will stop that test and report the error to the test runner.
  28. *
  29. * This class should not be used where adequate mocks exist. Time-based routines
  30. * should use the MockClock, which runs much faster and provides equivalent
  31. * results. Continuation tests should be used for testing code that depends on
  32. * browser behaviors that are difficult to mock. For example, testing code that
  33. * relies on Iframe load events, event or layout code that requires a setTimeout
  34. * to become valid, and other browser-dependent native object interactions for
  35. * which mocks are insufficient.
  36. *
  37. * Sample usage:
  38. *
  39. * <pre>
  40. * var testCase = new goog.testing.ContinuationTestCase();
  41. * testCase.autoDiscoverTests();
  42. *
  43. * if (typeof G_testRunner != 'undefined') {
  44. * G_testRunner.initialize(testCase);
  45. * }
  46. *
  47. * function testWaiting() {
  48. * var someVar = true;
  49. * waitForTimeout(function() {
  50. * assertTrue(someVar)
  51. * }, 500);
  52. * }
  53. *
  54. * function testWaitForEvent() {
  55. * var et = goog.events.EventTarget();
  56. * waitForEvent(et, 'test', function() {
  57. * // Test step runs after the event fires.
  58. * })
  59. * et.dispatchEvent(et, 'test');
  60. * }
  61. *
  62. * function testWaitForCondition() {
  63. * var counter = 0;
  64. *
  65. * waitForCondition(function() {
  66. * // This function is evaluated periodically until it returns true, or it
  67. * // times out.
  68. * return ++counter >= 3;
  69. * }, function() {
  70. * // This test step is run once the condition becomes true.
  71. * assertEquals(3, counter);
  72. * });
  73. * }
  74. * </pre>
  75. *
  76. * @author brenneman@google.com (Shawn Brenneman)
  77. */
  78. goog.setTestOnly('goog.testing.ContinuationTestCase');
  79. goog.provide('goog.testing.ContinuationTestCase');
  80. goog.provide('goog.testing.ContinuationTestCase.ContinuationTest');
  81. goog.provide('goog.testing.ContinuationTestCase.Step');
  82. goog.require('goog.array');
  83. goog.require('goog.events.EventHandler');
  84. goog.require('goog.testing.TestCase');
  85. goog.require('goog.testing.asserts');
  86. /**
  87. * Constructs a test case that supports tests with continuations. Test functions
  88. * may issue "wait" commands that suspend the test temporarily and continue once
  89. * the wait condition is met.
  90. *
  91. * @param {string=} opt_name Optional name for the test case.
  92. * @constructor
  93. * @extends {goog.testing.TestCase}
  94. * @deprecated ContinuationTestCase is deprecated. Prefer returning Promises
  95. * for tests that assert Asynchronous behavior.
  96. * @final
  97. */
  98. goog.testing.ContinuationTestCase = function(opt_name) {
  99. goog.testing.TestCase.call(this, opt_name);
  100. /**
  101. * An event handler for waiting on Closure or browser events during tests.
  102. * @type {goog.events.EventHandler<!goog.testing.ContinuationTestCase>}
  103. * @private
  104. */
  105. this.handler_ = new goog.events.EventHandler(this);
  106. };
  107. goog.inherits(goog.testing.ContinuationTestCase, goog.testing.TestCase);
  108. /**
  109. * The default maximum time to wait for a single test step in milliseconds.
  110. * @type {number}
  111. */
  112. goog.testing.ContinuationTestCase.MAX_TIMEOUT = 1000;
  113. /**
  114. * Lock used to prevent multiple test steps from running recursively.
  115. * @type {boolean}
  116. * @private
  117. */
  118. goog.testing.ContinuationTestCase.locked_ = false;
  119. /**
  120. * The current test being run.
  121. * @type {goog.testing.ContinuationTestCase.ContinuationTest}
  122. * @private
  123. */
  124. goog.testing.ContinuationTestCase.prototype.currentTest_ = null;
  125. /**
  126. * Enables or disables the wait functions in the global scope.
  127. * @param {boolean} enable Whether the wait functions should be exported.
  128. * @private
  129. */
  130. goog.testing.ContinuationTestCase.prototype.enableWaitFunctions_ = function(
  131. enable) {
  132. if (enable) {
  133. goog.exportSymbol(
  134. 'waitForCondition', goog.bind(this.waitForCondition, this));
  135. goog.exportSymbol('waitForEvent', goog.bind(this.waitForEvent, this));
  136. goog.exportSymbol('waitForTimeout', goog.bind(this.waitForTimeout, this));
  137. } else {
  138. // Internet Explorer doesn't allow deletion of properties on the window.
  139. goog.global['waitForCondition'] = undefined;
  140. goog.global['waitForEvent'] = undefined;
  141. goog.global['waitForTimeout'] = undefined;
  142. }
  143. };
  144. /** @override */
  145. goog.testing.ContinuationTestCase.prototype.runTests = function() {
  146. this.enableWaitFunctions_(true);
  147. goog.testing.ContinuationTestCase.superClass_.runTests.call(this);
  148. };
  149. /** @override */
  150. goog.testing.ContinuationTestCase.prototype.finalize = function() {
  151. this.enableWaitFunctions_(false);
  152. goog.testing.ContinuationTestCase.superClass_.finalize.call(this);
  153. };
  154. /** @override */
  155. goog.testing.ContinuationTestCase.prototype.cycleTests = function() {
  156. // Get the next test in the queue.
  157. if (!this.currentTest_) {
  158. this.currentTest_ = this.createNextTest_();
  159. }
  160. // Run the next step of the current test, or exit if all tests are complete.
  161. if (this.currentTest_) {
  162. this.runNextStep_();
  163. } else {
  164. this.finalize();
  165. }
  166. };
  167. /**
  168. * Creates the next test in the queue.
  169. * @return {goog.testing.ContinuationTestCase.ContinuationTest} The next test to
  170. * execute, or null if no pending tests remain.
  171. * @private
  172. */
  173. goog.testing.ContinuationTestCase.prototype.createNextTest_ = function() {
  174. var test = this.next();
  175. if (!test) {
  176. return null;
  177. }
  178. var name = test.name;
  179. goog.testing.TestCase.currentTestName = name;
  180. this.result_.runCount++;
  181. this.log('Running test: ' + name);
  182. return new goog.testing.ContinuationTestCase.ContinuationTest(
  183. new goog.testing.TestCase.Test(name, this.setUp, this), test,
  184. new goog.testing.TestCase.Test(name, this.tearDown, this));
  185. };
  186. /**
  187. * Cleans up a finished test and cycles to the next test.
  188. * @private
  189. */
  190. goog.testing.ContinuationTestCase.prototype.finishTest_ = function() {
  191. var err = this.currentTest_.getError();
  192. if (err) {
  193. this.doError(this.currentTest_, err);
  194. } else {
  195. this.doSuccess(this.currentTest_);
  196. }
  197. goog.testing.TestCase.currentTestName = null;
  198. this.currentTest_ = null;
  199. this.locked_ = false;
  200. this.handler_.removeAll();
  201. this.timeout(goog.bind(this.cycleTests, this), 0);
  202. };
  203. /**
  204. * Executes the next step in the current phase, advancing through each phase as
  205. * all steps are completed.
  206. * @private
  207. */
  208. goog.testing.ContinuationTestCase.prototype.runNextStep_ = function() {
  209. if (this.locked_) {
  210. // Attempting to run a step before the previous step has finished. Try again
  211. // after that step has released the lock.
  212. return;
  213. }
  214. var phase = this.currentTest_.getCurrentPhase();
  215. if (!phase || !phase.length) {
  216. // No more steps for this test.
  217. this.finishTest_();
  218. return;
  219. }
  220. // Find the next step that is not in a wait state.
  221. var stepIndex =
  222. goog.array.findIndex(phase, function(step) { return !step.waiting; });
  223. if (stepIndex < 0) {
  224. // All active steps are currently waiting. Return until one wakes up.
  225. return;
  226. }
  227. this.locked_ = true;
  228. var step = phase[stepIndex];
  229. try {
  230. step.execute();
  231. // Remove the successfully completed step. If an error is thrown, all steps
  232. // will be removed for this phase.
  233. goog.array.removeAt(phase, stepIndex);
  234. } catch (e) {
  235. this.currentTest_.setError(e);
  236. // An assertion has failed, or an exception was raised. Clear the current
  237. // phase, whether it is setUp, test, or tearDown.
  238. this.currentTest_.cancelCurrentPhase();
  239. // Cancel the setUp and test phase no matter where the error occurred. The
  240. // tearDown phase will still run if it has pending steps.
  241. this.currentTest_.cancelTestPhase();
  242. }
  243. this.locked_ = false;
  244. this.runNextStep_();
  245. };
  246. /**
  247. * Creates a new test step that will run after a user-specified
  248. * timeout. No guarantee is made on the execution order of the
  249. * continuation, except for those provided by each browser's
  250. * window.setTimeout. In particular, if two continuations are
  251. * registered at the same time with very small delta for their
  252. * durations, this class can not guarantee that the continuation with
  253. * the smaller duration will be executed first.
  254. * @param {Function} continuation The test function to invoke after the timeout.
  255. * @param {number=} opt_duration The length of the timeout in milliseconds.
  256. */
  257. goog.testing.ContinuationTestCase.prototype.waitForTimeout = function(
  258. continuation, opt_duration) {
  259. var step = this.addStep_(continuation);
  260. step.setTimeout(
  261. goog.bind(this.handleComplete_, this, step), opt_duration || 0);
  262. };
  263. /**
  264. * Creates a new test step that will run after an event has fired. If the event
  265. * does not fire within a reasonable timeout, the test will fail.
  266. * @param {goog.events.EventTarget|EventTarget} eventTarget The target that will
  267. * fire the event.
  268. * @param {string} eventType The type of event to listen for.
  269. * @param {Function} continuation The test function to invoke after the event
  270. * fires.
  271. */
  272. goog.testing.ContinuationTestCase.prototype.waitForEvent = function(
  273. eventTarget, eventType, continuation) {
  274. var step = this.addStep_(continuation);
  275. var duration = goog.testing.ContinuationTestCase.MAX_TIMEOUT;
  276. step.setTimeout(
  277. goog.bind(this.handleTimeout_, this, step, duration), duration);
  278. this.handler_.listenOnce(
  279. eventTarget, eventType, goog.bind(this.handleComplete_, this, step));
  280. };
  281. /**
  282. * Creates a new test step which will run once a condition becomes true. The
  283. * condition will be polled at a user-specified interval until it becomes true,
  284. * or until a maximum timeout is reached.
  285. * @param {Function} condition The condition to poll.
  286. * @param {Function} continuation The test code to evaluate once the condition
  287. * becomes true.
  288. * @param {number=} opt_interval The polling interval in milliseconds.
  289. * @param {number=} opt_maxTimeout The maximum amount of time to wait for the
  290. * condition in milliseconds (defaults to 1000).
  291. */
  292. goog.testing.ContinuationTestCase.prototype.waitForCondition = function(
  293. condition, continuation, opt_interval, opt_maxTimeout) {
  294. var interval = opt_interval || 100;
  295. var timeout = opt_maxTimeout || goog.testing.ContinuationTestCase.MAX_TIMEOUT;
  296. var step = this.addStep_(continuation);
  297. this.testCondition_(step, condition, goog.now(), interval, timeout);
  298. };
  299. /**
  300. * Creates a new asynchronous test step which will be added to the current test
  301. * phase.
  302. * @param {Function} func The test function that will be executed for this step.
  303. * @return {!goog.testing.ContinuationTestCase.Step} A new test step.
  304. * @private
  305. */
  306. goog.testing.ContinuationTestCase.prototype.addStep_ = function(func) {
  307. if (!this.currentTest_) {
  308. throw Error('Cannot add test steps outside of a running test.');
  309. }
  310. var step = new goog.testing.ContinuationTestCase.Step(
  311. this.currentTest_.name, func, this.currentTest_.scope);
  312. this.currentTest_.addStep(step);
  313. return step;
  314. };
  315. /**
  316. * Handles completion of a step's wait condition. Advances the test, allowing
  317. * the step's test method to run.
  318. * @param {goog.testing.ContinuationTestCase.Step} step The step that has
  319. * finished waiting.
  320. * @private
  321. */
  322. goog.testing.ContinuationTestCase.prototype.handleComplete_ = function(step) {
  323. step.clearTimeout();
  324. step.waiting = false;
  325. this.runNextStep_();
  326. };
  327. /**
  328. * Handles the timeout event for a step that has exceeded the maximum time. This
  329. * causes the current test to fail.
  330. * @param {goog.testing.ContinuationTestCase.Step} step The timed-out step.
  331. * @param {number} duration The length of the timeout in milliseconds.
  332. * @private
  333. */
  334. goog.testing.ContinuationTestCase.prototype.handleTimeout_ = function(
  335. step, duration) {
  336. step.ref = function() {
  337. fail('Continuation timed out after ' + duration + 'ms.');
  338. };
  339. // Since the test is failing, cancel any other pending event listeners.
  340. this.handler_.removeAll();
  341. this.handleComplete_(step);
  342. };
  343. /**
  344. * Tests a wait condition and executes the associated test step once the
  345. * condition is true.
  346. *
  347. * If the condition does not become true before the maximum duration, the
  348. * interval will stop and the test step will fail in the kill timer.
  349. *
  350. * @param {goog.testing.ContinuationTestCase.Step} step The waiting test step.
  351. * @param {Function} condition The test condition.
  352. * @param {number} startTime Time when the test step began waiting.
  353. * @param {number} interval The duration in milliseconds to wait between tests.
  354. * @param {number} timeout The maximum amount of time to wait for the condition
  355. * to become true. Measured from the startTime in milliseconds.
  356. * @private
  357. */
  358. goog.testing.ContinuationTestCase.prototype.testCondition_ = function(
  359. step, condition, startTime, interval, timeout) {
  360. var duration = goog.now() - startTime;
  361. if (condition()) {
  362. this.handleComplete_(step);
  363. } else if (duration < timeout) {
  364. step.setTimeout(
  365. goog.bind(
  366. this.testCondition_, this, step, condition, startTime, interval,
  367. timeout),
  368. interval);
  369. } else {
  370. this.handleTimeout_(step, duration);
  371. }
  372. };
  373. /**
  374. * Creates a continuation test case, which consists of multiple test steps that
  375. * occur in several phases.
  376. *
  377. * The steps are distributed between setUp, test, and tearDown phases. During
  378. * the execution of each step, 0 or more steps may be added to the current
  379. * phase. Once all steps in a phase have completed, the next phase will be
  380. * executed.
  381. *
  382. * If any errors occur (such as an assertion failure), the setUp and Test phases
  383. * will be cancelled immediately. The tearDown phase will always start, but may
  384. * be cancelled as well if it raises an error.
  385. *
  386. * @param {goog.testing.TestCase.Test} setUp A setUp test method to run before
  387. * the main test phase.
  388. * @param {goog.testing.TestCase.Test} test A test method to run.
  389. * @param {goog.testing.TestCase.Test} tearDown A tearDown test method to run
  390. * after the test method completes or fails.
  391. * @constructor
  392. * @extends {goog.testing.TestCase.Test}
  393. * @final
  394. */
  395. goog.testing.ContinuationTestCase.ContinuationTest = function(
  396. setUp, test, tearDown) {
  397. // This test container has a name, but no evaluation function or scope.
  398. goog.testing.TestCase.Test.call(this, test.name, null, null);
  399. /**
  400. * The list of test steps to run during setUp.
  401. * @type {Array<goog.testing.TestCase.Test>}
  402. * @private
  403. */
  404. this.setUp_ = [setUp];
  405. /**
  406. * The list of test steps to run for the actual test.
  407. * @type {Array<goog.testing.TestCase.Test>}
  408. * @private
  409. */
  410. this.test_ = [test];
  411. /**
  412. * The list of test steps to run during the tearDown phase.
  413. * @type {Array<goog.testing.TestCase.Test>}
  414. * @private
  415. */
  416. this.tearDown_ = [tearDown];
  417. };
  418. goog.inherits(
  419. goog.testing.ContinuationTestCase.ContinuationTest,
  420. goog.testing.TestCase.Test);
  421. /**
  422. * The first error encountered during the test run, if any.
  423. * @type {Error}
  424. * @private
  425. */
  426. goog.testing.ContinuationTestCase.ContinuationTest.prototype.error_ = null;
  427. /**
  428. * @return {Error} The first error to be raised during the test run or null if
  429. * no errors occurred.
  430. */
  431. goog.testing.ContinuationTestCase.ContinuationTest.prototype.getError =
  432. function() {
  433. return this.error_;
  434. };
  435. /**
  436. * Sets an error for the test so it can be reported. Only the first error set
  437. * during a test will be reported. Additional errors that occur in later test
  438. * phases will be discarded.
  439. * @param {Error} e An error.
  440. */
  441. goog.testing.ContinuationTestCase.ContinuationTest.prototype.setError =
  442. function(e) {
  443. this.error_ = this.error_ || e;
  444. };
  445. /**
  446. * @return {Array<goog.testing.TestCase.Test>} The current phase of steps
  447. * being processed. Returns null if all steps have been completed.
  448. */
  449. goog.testing.ContinuationTestCase.ContinuationTest.prototype.getCurrentPhase =
  450. function() {
  451. if (this.setUp_.length) {
  452. return this.setUp_;
  453. }
  454. if (this.test_.length) {
  455. return this.test_;
  456. }
  457. if (this.tearDown_.length) {
  458. return this.tearDown_;
  459. }
  460. return null;
  461. };
  462. /**
  463. * Adds a new test step to the end of the current phase. The new step will wait
  464. * for a condition to be met before running, or will fail after a timeout.
  465. * @param {goog.testing.ContinuationTestCase.Step} step The test step to add.
  466. */
  467. goog.testing.ContinuationTestCase.ContinuationTest.prototype.addStep = function(
  468. step) {
  469. var phase = this.getCurrentPhase();
  470. if (phase) {
  471. phase.push(step);
  472. } else {
  473. throw Error('Attempted to add a step to a completed test.');
  474. }
  475. };
  476. /**
  477. * Cancels all remaining steps in the current phase. Called after an error in
  478. * any phase occurs.
  479. */
  480. goog.testing.ContinuationTestCase.ContinuationTest.prototype
  481. .cancelCurrentPhase = function() {
  482. this.cancelPhase_(this.getCurrentPhase());
  483. };
  484. /**
  485. * Skips the rest of the setUp and test phases, but leaves the tearDown phase to
  486. * clean up.
  487. */
  488. goog.testing.ContinuationTestCase.ContinuationTest.prototype.cancelTestPhase =
  489. function() {
  490. this.cancelPhase_(this.setUp_);
  491. this.cancelPhase_(this.test_);
  492. };
  493. /**
  494. * Clears a test phase and cancels any pending steps found.
  495. * @param {Array<goog.testing.TestCase.Test>} phase A list of test steps.
  496. * @private
  497. */
  498. goog.testing.ContinuationTestCase.ContinuationTest.prototype.cancelPhase_ =
  499. function(phase) {
  500. while (phase && phase.length) {
  501. var step = phase.pop();
  502. if (step instanceof goog.testing.ContinuationTestCase.Step) {
  503. step.clearTimeout();
  504. }
  505. }
  506. };
  507. /**
  508. * Constructs a single step in a larger continuation test. Each step is similar
  509. * to a typical TestCase test, except it may wait for an event or timeout to
  510. * occur before running the test function.
  511. *
  512. * @param {string} name The test name.
  513. * @param {Function} ref The test function to run.
  514. * @param {Object=} opt_scope The object context to run the test in.
  515. * @constructor
  516. * @extends {goog.testing.TestCase.Test}
  517. * @final
  518. */
  519. goog.testing.ContinuationTestCase.Step = function(name, ref, opt_scope) {
  520. goog.testing.TestCase.Test.call(this, name, ref, opt_scope);
  521. };
  522. goog.inherits(
  523. goog.testing.ContinuationTestCase.Step, goog.testing.TestCase.Test);
  524. /**
  525. * Whether the step is currently waiting for a condition to continue. All new
  526. * steps begin in wait state.
  527. * @type {boolean}
  528. */
  529. goog.testing.ContinuationTestCase.Step.prototype.waiting = true;
  530. /**
  531. * A saved reference to window.clearTimeout so that MockClock or other overrides
  532. * don't affect continuation timeouts.
  533. * @type {Function}
  534. * @private
  535. */
  536. goog.testing.ContinuationTestCase.Step.protectedClearTimeout_ =
  537. window.clearTimeout;
  538. /**
  539. * A saved reference to window.setTimeout so that MockClock or other overrides
  540. * don't affect continuation timeouts.
  541. * @type {Function}
  542. * @private
  543. */
  544. goog.testing.ContinuationTestCase.Step.protectedSetTimeout_ = window.setTimeout;
  545. /**
  546. * Key to this step's timeout. If the step is waiting for an event, the timeout
  547. * will be used as a kill timer. If the step is waiting
  548. * @type {number}
  549. * @private
  550. */
  551. goog.testing.ContinuationTestCase.Step.prototype.timeout_;
  552. /**
  553. * Starts a timeout for this step. Each step may have only one timeout active at
  554. * a time.
  555. * @param {Function} func The function to call after the timeout.
  556. * @param {number} duration The number of milliseconds to wait before invoking
  557. * the function.
  558. */
  559. goog.testing.ContinuationTestCase.Step.prototype.setTimeout = function(
  560. func, duration) {
  561. this.clearTimeout();
  562. var setTimeout = goog.testing.ContinuationTestCase.Step.protectedSetTimeout_;
  563. this.timeout_ = setTimeout(func, duration);
  564. };
  565. /**
  566. * Clears the current timeout if it is active.
  567. */
  568. goog.testing.ContinuationTestCase.Step.prototype.clearTimeout = function() {
  569. if (this.timeout_) {
  570. var clear = goog.testing.ContinuationTestCase.Step.protectedClearTimeout_;
  571. clear(this.timeout_);
  572. delete this.timeout_;
  573. }
  574. };