lib/goog/testing/testcase.js

1// Copyright 2007 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/**
16 * @fileoverview A class representing a set of test functions to be run.
17 *
18 * Testing code should not have dependencies outside of goog.testing so as to
19 * reduce the chance of masking missing dependencies.
20 *
21 * This file does not compile correctly with --collapse_properties. Use
22 * --property_renaming=ALL_UNQUOTED instead.
23 *
24 */
25
26goog.provide('goog.testing.TestCase');
27goog.provide('goog.testing.TestCase.Error');
28goog.provide('goog.testing.TestCase.Order');
29goog.provide('goog.testing.TestCase.Result');
30goog.provide('goog.testing.TestCase.Test');
31
32
33goog.require('goog.Promise');
34goog.require('goog.Thenable');
35goog.require('goog.asserts');
36goog.require('goog.dom.TagName');
37goog.require('goog.object');
38goog.require('goog.testing.asserts');
39goog.require('goog.testing.stacktrace');
40
41
42
43/**
44 * A class representing a JsUnit test case. A TestCase is made up of a number
45 * of test functions which can be run. Individual test cases can override the
46 * following functions to set up their test environment:
47 * - runTests - completely override the test's runner
48 * - setUpPage - called before any of the test functions are run
49 * - tearDownPage - called after all tests are finished
50 * - setUp - called before each of the test functions
51 * - tearDown - called after each of the test functions
52 * - shouldRunTests - called before a test run, all tests are skipped if it
53 * returns false. Can be used to disable tests on browsers
54 * where they aren't expected to pass.
55 *
56 * Use {@link #autoDiscoverLifecycle} and {@link #autoDiscoverTests}
57 *
58 * @param {string=} opt_name The name of the test case, defaults to
59 * 'Untitled Test Case'.
60 * @constructor
61 */
62goog.testing.TestCase = function(opt_name) {
63 /**
64 * A name for the test case.
65 * @type {string}
66 * @private
67 */
68 this.name_ = opt_name || 'Untitled Test Case';
69
70 /**
71 * Array of test functions that can be executed.
72 * @type {!Array<!goog.testing.TestCase.Test>}
73 * @private
74 */
75 this.tests_ = [];
76
77 /**
78 * Set of test names and/or indices to execute, or null if all tests should
79 * be executed.
80 *
81 * Indices are included to allow automation tools to run a subset of the
82 * tests without knowing the exact contents of the test file.
83 *
84 * Indices should only be used with SORTED ordering.
85 *
86 * Example valid values:
87 * <ul>
88 * <li>[testName]
89 * <li>[testName1, testName2]
90 * <li>[2] - will run the 3rd test in the order specified
91 * <li>[1,3,5]
92 * <li>[testName1, testName2, 3, 5] - will work
93 * <ul>
94 * @type {Object}
95 * @private
96 */
97 this.testsToRun_ = null;
98
99 /**
100 * The order to run the auto-discovered tests in.
101 * @type {string}
102 */
103 this.order = goog.testing.TestCase.Order.SORTED;
104
105 /** @private {function(!goog.testing.TestCase.Result)} */
106 this.runNextTestCallback_ = goog.nullFunction;
107
108 /**
109 * The number of {@link runNextTest_} frames currently on the stack.
110 * When this exceeds {@link MAX_STACK_DEPTH_}, test execution is rescheduled
111 * for a later tick of the event loop.
112 * @see {finishTestInvocation_}
113 * @private {number}
114 */
115 this.depth_ = 0;
116
117 /** @private {goog.testing.TestCase.Test} */
118 this.curTest_ = null;
119
120 /**
121 * Object used to encapsulate the test results.
122 * @type {!goog.testing.TestCase.Result}
123 * @protected
124 * @suppress {underscore|visibility}
125 */
126 this.result_ = new goog.testing.TestCase.Result(this);
127
128 /**
129 * The maximum time in milliseconds a promise returned from a test function
130 * may remain pending before the test fails due to timeout.
131 * @type {number}
132 */
133 this.promiseTimeout = 1000; // 1s
134};
135
136
137/**
138 * The order to run the auto-discovered tests.
139 * @enum {string}
140 */
141goog.testing.TestCase.Order = {
142 /**
143 * This is browser dependent and known to be different in FF and Safari
144 * compared to others.
145 */
146 NATURAL: 'natural',
147
148 /** Random order. */
149 RANDOM: 'random',
150
151 /** Sorted based on the name. */
152 SORTED: 'sorted'
153};
154
155
156/**
157 * @return {string} The name of the test.
158 */
159goog.testing.TestCase.prototype.getName = function() {
160 return this.name_;
161};
162
163
164/**
165 * The maximum amount of time in milliseconds that the test case can take
166 * before it is forced to yield and reschedule. This prevents the test runner
167 * from blocking the browser and potentially hurting the test harness.
168 * @type {number}
169 */
170goog.testing.TestCase.maxRunTime = 200;
171
172
173/**
174 * The maximum number of {@link runNextTest_} frames that can be on the stack
175 * before the test case is forced to yield and reschedule. Although modern
176 * browsers can handle thousands of stack frames, this is set conservatively
177 * because maximum stack depth has never been standardized, and engine-specific
178 * techniques like tail cail optimization can affect the exact depth.
179 * @private @const
180 */
181goog.testing.TestCase.MAX_STACK_DEPTH_ = 50;
182
183
184/**
185 * Save a reference to {@code window.setTimeout}, so any code that overrides the
186 * default behavior (the MockClock, for example) doesn't affect our runner.
187 * @type {function((Function|string), number=, *=): number}
188 * @private
189 */
190goog.testing.TestCase.protectedSetTimeout_ = goog.global.setTimeout;
191
192
193/**
194 * Save a reference to {@code window.clearTimeout}, so any code that overrides
195 * the default behavior (e.g. MockClock) doesn't affect our runner.
196 * @type {function((null|number|undefined)): void}
197 * @private
198 */
199goog.testing.TestCase.protectedClearTimeout_ = goog.global.clearTimeout;
200
201
202/**
203 * Save a reference to {@code window.Date}, so any code that overrides
204 * the default behavior doesn't affect our runner.
205 * @type {function(new: Date)}
206 * @private
207 */
208goog.testing.TestCase.protectedDate_ = Date;
209
210
211/**
212 * Saved string referencing goog.global.setTimeout's string serialization. IE
213 * sometimes fails to uphold equality for setTimeout, but the string version
214 * stays the same.
215 * @type {string}
216 * @private
217 */
218goog.testing.TestCase.setTimeoutAsString_ = String(goog.global.setTimeout);
219
220
221/**
222 * TODO(user) replace this with prototype.currentTest.
223 * Name of the current test that is running, or null if none is running.
224 * @type {?string}
225 */
226goog.testing.TestCase.currentTestName = null;
227
228
229/**
230 * Avoid a dependency on goog.userAgent and keep our own reference of whether
231 * the browser is IE.
232 * @type {boolean}
233 */
234goog.testing.TestCase.IS_IE = typeof opera == 'undefined' &&
235 !!goog.global.navigator &&
236 goog.global.navigator.userAgent.indexOf('MSIE') != -1;
237
238
239/**
240 * Exception object that was detected before a test runs.
241 * @type {*}
242 * @protected
243 */
244goog.testing.TestCase.prototype.exceptionBeforeTest;
245
246
247/**
248 * Whether the test case has ever tried to execute.
249 * @type {boolean}
250 */
251goog.testing.TestCase.prototype.started = false;
252
253
254/**
255 * Whether the test case is running.
256 * @type {boolean}
257 */
258goog.testing.TestCase.prototype.running = false;
259
260
261/**
262 * Timestamp for when the test was started.
263 * @type {number}
264 * @private
265 */
266goog.testing.TestCase.prototype.startTime_ = 0;
267
268
269/**
270 * Time since the last batch of tests was started, if batchTime exceeds
271 * {@link #maxRunTime} a timeout will be used to stop the tests blocking the
272 * browser and a new batch will be started.
273 * @type {number}
274 * @private
275 */
276goog.testing.TestCase.prototype.batchTime_ = 0;
277
278
279/**
280 * Pointer to the current test.
281 * @type {number}
282 * @private
283 */
284goog.testing.TestCase.prototype.currentTestPointer_ = 0;
285
286
287/**
288 * Optional callback that will be executed when the test has finalized.
289 * @type {Function}
290 * @private
291 */
292goog.testing.TestCase.prototype.onCompleteCallback_ = null;
293
294
295/**
296 * Adds a new test to the test case.
297 * @param {!goog.testing.TestCase.Test} test The test to add.
298 */
299goog.testing.TestCase.prototype.add = function(test) {
300 goog.asserts.assert(test);
301 if (this.started) {
302 throw Error('Tests cannot be added after execute() has been called. ' +
303 'Test: ' + test.name);
304 }
305
306 this.tests_.push(test);
307};
308
309
310/**
311 * Creates and adds a new test.
312 *
313 * Convenience function to make syntax less awkward when not using automatic
314 * test discovery.
315 *
316 * @param {string} name The test name.
317 * @param {!Function} ref Reference to the test function.
318 * @param {!Object=} opt_scope Optional scope that the test function should be
319 * called in.
320 */
321goog.testing.TestCase.prototype.addNewTest = function(name, ref, opt_scope) {
322 var test = new goog.testing.TestCase.Test(name, ref, opt_scope || this);
323 this.add(test);
324};
325
326
327/**
328 * Sets the tests.
329 * @param {!Array<goog.testing.TestCase.Test>} tests A new test array.
330 * @protected
331 */
332goog.testing.TestCase.prototype.setTests = function(tests) {
333 this.tests_ = tests;
334};
335
336
337/**
338 * Gets the tests.
339 * @return {!Array<goog.testing.TestCase.Test>} The test array.
340 */
341goog.testing.TestCase.prototype.getTests = function() {
342 return this.tests_;
343};
344
345
346/**
347 * Returns the number of tests contained in the test case.
348 * @return {number} The number of tests.
349 */
350goog.testing.TestCase.prototype.getCount = function() {
351 return this.tests_.length;
352};
353
354
355/**
356 * Returns the number of tests actually run in the test case, i.e. subtracting
357 * any which are skipped.
358 * @return {number} The number of un-ignored tests.
359 */
360goog.testing.TestCase.prototype.getActuallyRunCount = function() {
361 return this.testsToRun_ ? goog.object.getCount(this.testsToRun_) : 0;
362};
363
364
365/**
366 * Returns the current test and increments the pointer.
367 * @return {goog.testing.TestCase.Test} The current test case.
368 */
369goog.testing.TestCase.prototype.next = function() {
370 var test;
371 while ((test = this.tests_[this.currentTestPointer_++])) {
372 if (!this.testsToRun_ || this.testsToRun_[test.name] ||
373 this.testsToRun_[this.currentTestPointer_ - 1]) {
374 return test;
375 }
376 }
377 return null;
378};
379
380
381/**
382 * Resets the test case pointer, so that next returns the first test.
383 */
384goog.testing.TestCase.prototype.reset = function() {
385 this.currentTestPointer_ = 0;
386 this.result_ = new goog.testing.TestCase.Result(this);
387};
388
389
390/**
391 * Sets the callback function that should be executed when the tests have
392 * completed.
393 * @param {Function} fn The callback function.
394 */
395goog.testing.TestCase.prototype.setCompletedCallback = function(fn) {
396 this.onCompleteCallback_ = fn;
397};
398
399
400/**
401 * @param {goog.testing.TestCase.Order} order The sort order for running tests.
402 */
403goog.testing.TestCase.prototype.setOrder = function(order) {
404 this.order = order;
405};
406
407
408/**
409 * @param {Object<string, boolean>} testsToRun Set of tests to run. Entries in
410 * the set may be test names, like "testFoo", or numeric indicies. Only
411 * tests identified by name or by index will be executed.
412 */
413goog.testing.TestCase.prototype.setTestsToRun = function(testsToRun) {
414 this.testsToRun_ = testsToRun;
415};
416
417
418/**
419 * Can be overridden in test classes to indicate whether the tests in a case
420 * should be run in that particular situation. For example, this could be used
421 * to stop tests running in a particular browser, where browser support for
422 * the class under test was absent.
423 * @return {boolean} Whether any of the tests in the case should be run.
424 */
425goog.testing.TestCase.prototype.shouldRunTests = function() {
426 return true;
427};
428
429
430/**
431 * Executes the tests, yielding asynchronously if execution time exceeds
432 * {@link maxRunTime}. There is no guarantee that the test case has finished
433 * once this method has returned. To be notified when the test case
434 * has finished, use {@link #setCompletedCallback} or
435 * {@link #runTestsReturningPromise}.
436 */
437goog.testing.TestCase.prototype.execute = function() {
438 if (!this.prepareForRun_()) {
439 return;
440 }
441 this.log('Starting tests: ' + this.name_);
442 this.cycleTests();
443};
444
445
446/**
447 * Sets up the internal state of the test case for a run.
448 * @return {boolean} If false, preparation failed because the test case
449 * is not supposed to run in the present environment.
450 * @private
451 */
452goog.testing.TestCase.prototype.prepareForRun_ = function() {
453 this.started = true;
454 this.reset();
455 this.startTime_ = this.now();
456 this.running = true;
457 this.result_.totalCount = this.getCount();
458 if (!this.shouldRunTests()) {
459 this.log('shouldRunTests() returned false, skipping these tests.');
460 this.result_.testSuppressed = true;
461 this.finalize();
462 return false;
463 }
464 return true;
465};
466
467
468/**
469 * Finalizes the test case, called when the tests have finished executing.
470 */
471goog.testing.TestCase.prototype.finalize = function() {
472 this.saveMessage('Done');
473
474 this.tearDownPage();
475
476 var restoredSetTimeout =
477 goog.testing.TestCase.protectedSetTimeout_ == goog.global.setTimeout &&
478 goog.testing.TestCase.protectedClearTimeout_ == goog.global.clearTimeout;
479 if (!restoredSetTimeout && goog.testing.TestCase.IS_IE &&
480 String(goog.global.setTimeout) ==
481 goog.testing.TestCase.setTimeoutAsString_) {
482 // In strange cases, IE's value of setTimeout *appears* to change, but
483 // the string representation stays stable.
484 restoredSetTimeout = true;
485 }
486
487 if (!restoredSetTimeout) {
488 var message = 'ERROR: Test did not restore setTimeout and clearTimeout';
489 this.saveMessage(message);
490 var err = new goog.testing.TestCase.Error(this.name_, message);
491 this.result_.errors.push(err);
492 }
493 goog.global.clearTimeout = goog.testing.TestCase.protectedClearTimeout_;
494 goog.global.setTimeout = goog.testing.TestCase.protectedSetTimeout_;
495 this.endTime_ = this.now();
496 this.running = false;
497 this.result_.runTime = this.endTime_ - this.startTime_;
498 this.result_.numFilesLoaded = this.countNumFilesLoaded_();
499 this.result_.complete = true;
500
501 this.log(this.result_.getSummary());
502 if (this.result_.isSuccess()) {
503 this.log('Tests complete');
504 } else {
505 this.log('Tests Failed');
506 }
507 if (this.onCompleteCallback_) {
508 var fn = this.onCompleteCallback_;
509 // Execute's the completed callback in the context of the global object.
510 fn();
511 this.onCompleteCallback_ = null;
512 }
513};
514
515
516/**
517 * Saves a message to the result set.
518 * @param {string} message The message to save.
519 */
520goog.testing.TestCase.prototype.saveMessage = function(message) {
521 this.result_.messages.push(this.getTimeStamp_() + ' ' + message);
522};
523
524
525/**
526 * @return {boolean} Whether the test case is running inside the multi test
527 * runner.
528 */
529goog.testing.TestCase.prototype.isInsideMultiTestRunner = function() {
530 var top = goog.global['top'];
531 return top && typeof top['_allTests'] != 'undefined';
532};
533
534
535/**
536 * Logs an object to the console, if available.
537 * @param {*} val The value to log. Will be ToString'd.
538 */
539goog.testing.TestCase.prototype.log = function(val) {
540 if (!this.isInsideMultiTestRunner() && goog.global.console) {
541 if (typeof val == 'string') {
542 val = this.getTimeStamp_() + ' : ' + val;
543 }
544 if (val instanceof Error && val.stack) {
545 // Chrome does console.log asynchronously in a different process
546 // (http://code.google.com/p/chromium/issues/detail?id=50316).
547 // This is an acute problem for Errors, which almost never survive.
548 // Grab references to the immutable strings so they survive.
549 goog.global.console.log(val, val.message, val.stack);
550 // TODO(gboyer): Consider for Chrome cloning any object if we can ensure
551 // there are no circular references.
552 } else {
553 goog.global.console.log(val);
554 }
555 }
556};
557
558
559/**
560 * @return {boolean} Whether the test was a success.
561 */
562goog.testing.TestCase.prototype.isSuccess = function() {
563 return !!this.result_ && this.result_.isSuccess();
564};
565
566
567/**
568 * Returns a string detailing the results from the test.
569 * @param {boolean=} opt_verbose If true results will include data about all
570 * tests, not just what failed.
571 * @return {string} The results from the test.
572 */
573goog.testing.TestCase.prototype.getReport = function(opt_verbose) {
574 var rv = [];
575
576 if (this.running) {
577 rv.push(this.name_ + ' [RUNNING]');
578 } else {
579 var label = this.result_.isSuccess() ? 'PASSED' : 'FAILED';
580 rv.push(this.name_ + ' [' + label + ']');
581 }
582
583 if (goog.global.location) {
584 rv.push(this.trimPath_(goog.global.location.href));
585 }
586
587 rv.push(this.result_.getSummary());
588
589 if (opt_verbose) {
590 rv.push('.', this.result_.messages.join('\n'));
591 } else if (!this.result_.isSuccess()) {
592 rv.push(this.result_.errors.join('\n'));
593 }
594
595 rv.push(' ');
596
597 return rv.join('\n');
598};
599
600
601/**
602 * Returns the test results.
603 * @return {!goog.testing.TestCase.Result}
604 * @package
605 */
606goog.testing.TestCase.prototype.getResult = function() {
607 return this.result_;
608};
609
610
611/**
612 * Returns the amount of time it took for the test to run.
613 * @return {number} The run time, in milliseconds.
614 */
615goog.testing.TestCase.prototype.getRunTime = function() {
616 return this.result_.runTime;
617};
618
619
620/**
621 * Returns the number of script files that were loaded in order to run the test.
622 * @return {number} The number of script files.
623 */
624goog.testing.TestCase.prototype.getNumFilesLoaded = function() {
625 return this.result_.numFilesLoaded;
626};
627
628
629/**
630 * Returns the test results object: a map from test names to a list of test
631 * failures (if any exist).
632 * @return {!Object<string, !Array<string>>} Tests results object.
633 */
634goog.testing.TestCase.prototype.getTestResults = function() {
635 return this.result_.resultsByName;
636};
637
638
639/**
640 * Executes each of the tests, yielding asynchronously if execution time
641 * exceeds {@link #maxRunTime}. There is no guarantee that the test case
642 * has finished execution once this method has returned.
643 * To be notified when the test case has finished execution, use
644 * {@link #setCompletedCallback} or {@link #runTestsReturningPromise}.
645 *
646 * Overridable by the individual test case. This allows test cases to defer
647 * when the test is actually started. If overridden, finalize must be called
648 * by the test to indicate it has finished.
649 */
650goog.testing.TestCase.prototype.runTests = function() {
651 try {
652 this.setUpPage();
653 } catch (e) {
654 this.exceptionBeforeTest = e;
655 }
656 this.execute();
657};
658
659
660/**
661 * Executes each of the tests, returning a promise that resolves with the
662 * test results once they are done running.
663 * @return {!IThenable<!goog.testing.TestCase.Result>}
664 * @final
665 * @package
666 */
667goog.testing.TestCase.prototype.runTestsReturningPromise = function() {
668 try {
669 this.setUpPage();
670 } catch (e) {
671 this.exceptionBeforeTest = e;
672 }
673 if (!this.prepareForRun_()) {
674 return goog.Promise.resolve(this.result_);
675 }
676 this.log('Starting tests: ' + this.name_);
677 this.saveMessage('Start');
678 this.batchTime_ = this.now();
679 return new goog.Promise(function(resolve) {
680 this.runNextTestCallback_ = resolve;
681 this.runNextTest_();
682 }, this);
683};
684
685
686/**
687 * Executes the next test method synchronously or with promises, depending on
688 * the test method's return value.
689 *
690 * If the test method returns a promise, the next test method will run once
691 * the promise is resolved or rejected. If the test method does not
692 * return a promise, it is assumed to be synchronous, and execution proceeds
693 * immediately to the next test method. This means that test cases can run
694 * partially synchronously and partially asynchronously, depending on
695 * the return values of their test methods. In particular, a test case
696 * executes synchronously until the first promise is returned from a
697 * test method (or until a resource limit is reached; see
698 * {@link finishTestInvocation_}).
699 * @private
700 */
701goog.testing.TestCase.prototype.runNextTest_ = function() {
702 this.curTest_ = this.next();
703 if (!this.curTest_ || !this.running) {
704 this.finalize();
705 this.runNextTestCallback_(this.result_);
706 return;
707 }
708 this.result_.runCount++;
709 this.log('Running test: ' + this.curTest_.name);
710 if (this.maybeFailTestEarly(this.curTest_)) {
711 this.finishTestInvocation_();
712 return;
713 }
714 goog.testing.TestCase.currentTestName = this.curTest_.name;
715 this.invokeTestFunction_(
716 this.setUp, this.safeRunTest_, this.safeTearDown_,
717 'setUp');
718};
719
720
721/**
722 * Calls the given test function, handling errors appropriately.
723 * @private
724 */
725goog.testing.TestCase.prototype.safeRunTest_ = function() {
726 this.invokeTestFunction_(
727 goog.bind(this.curTest_.ref, this.curTest_.scope),
728 this.safeTearDown_,
729 this.safeTearDown_,
730 this.curTest_.name);
731};
732
733
734/**
735 * Calls {@link tearDown}, handling errors appropriately.
736 * @param {*=} opt_error Error associated with the test, if any.
737 * @private
738 */
739goog.testing.TestCase.prototype.safeTearDown_ = function(opt_error) {
740 if (arguments.length == 1) {
741 this.doError(this.curTest_, opt_error);
742 }
743 this.invokeTestFunction_(
744 this.tearDown, this.finishTestInvocation_, this.finishTestInvocation_,
745 'tearDown');
746};
747
748
749/**
750 * Calls the given {@code fn}, then calls either {@code onSuccess} or
751 * {@code onFailure}, either synchronously or using promises, depending on
752 * {@code fn}'s return value.
753 *
754 * If {@code fn} throws an exception, {@code onFailure} is called immediately
755 * with the exception.
756 *
757 * If {@code fn} returns a promise, and the promise is eventually resolved,
758 * {@code onSuccess} is called with no arguments. If the promise is eventually
759 * rejected, {@code onFailure} is called with the rejection reason.
760 *
761 * Otherwise, if {@code fn} neither returns a promise nor throws an exception,
762 * {@code onSuccess} is called immediately with no arguments.
763 *
764 * {@code fn}, {@code onSuccess}, and {@code onFailure} are all called with
765 * the TestCase instance as the method receiver.
766 *
767 * @param {function()} fn The function to call.
768 * @param {function()} onSuccess Success callback.
769 * @param {function(*)} onFailure Failure callback.
770 * @param {string} fnName Name of the function being invoked e.g. 'setUp'.
771 * @private
772 */
773goog.testing.TestCase.prototype.invokeTestFunction_ = function(
774 fn, onSuccess, onFailure, fnName) {
775 try {
776 var retval = fn.call(this);
777 if (goog.Thenable.isImplementedBy(retval) ||
778 goog.isFunction(retval && retval['then'])) {
779 var self = this;
780 retval = this.rejectIfPromiseTimesOut_(
781 retval, self.promiseTimeout,
782 'Timed out while waiting for a promise returned from ' + fnName +
783 ' to resolve. Set goog.testing.TestCase.getActiveTestCase()' +
784 '.promiseTimeout to adjust the timeout.');
785 retval.then(
786 function() {
787 self.resetBatchTimeAfterPromise_();
788 onSuccess.call(self);
789 },
790 function(e) {
791 self.resetBatchTimeAfterPromise_();
792 onFailure.call(self, e);
793 });
794 } else {
795 onSuccess.call(this);
796 }
797 } catch (e) {
798 onFailure.call(this, e);
799 }
800};
801
802
803/**
804 * Resets the batch run timer. This should only be called after resolving a
805 * promise since Promise.then() has an implicit yield.
806 * @private
807 */
808goog.testing.TestCase.prototype.resetBatchTimeAfterPromise_ = function() {
809 this.batchTime_ = this.now();
810};
811
812
813/**
814 * Finishes up bookkeeping for the current test function, and schedules
815 * the next test function to run, either immediately or asychronously.
816 * @param {*=} opt_error Optional error resulting from the test invocation.
817 * @private
818 */
819goog.testing.TestCase.prototype.finishTestInvocation_ = function(opt_error) {
820 if (arguments.length == 1) {
821 this.doError(this.curTest_, opt_error);
822 }
823
824 // If no errors have been recorded for the test, it is a success.
825 if (!(this.curTest_.name in this.result_.resultsByName) ||
826 !this.result_.resultsByName[this.curTest_.name].length) {
827 this.doSuccess(this.curTest_);
828 }
829
830 goog.testing.TestCase.currentTestName = null;
831
832 // If the test case has consumed too much time or stack space,
833 // yield to avoid blocking the browser. Otherwise, proceed to the next test.
834 if (this.depth_ > goog.testing.TestCase.MAX_STACK_DEPTH_ ||
835 this.now() - this.batchTime_ > goog.testing.TestCase.maxRunTime) {
836 this.saveMessage('Breaking async');
837 this.timeout(goog.bind(this.startNextBatch_, this), 0);
838 } else {
839 ++this.depth_;
840 this.runNextTest_();
841 }
842};
843
844
845/**
846 * Start a new batch to tests after yielding, resetting batchTime and depth.
847 * @private
848 */
849goog.testing.TestCase.prototype.startNextBatch_ = function() {
850 this.batchTime_ = this.now();
851 this.depth_ = 0;
852 this.runNextTest_();
853};
854
855
856/**
857 * Reorders the tests depending on the {@code order} field.
858 * @private
859 */
860goog.testing.TestCase.prototype.orderTests_ = function() {
861 switch (this.order) {
862 case goog.testing.TestCase.Order.RANDOM:
863 // Fisher-Yates shuffle
864 var i = this.tests_.length;
865 while (i > 1) {
866 // goog.math.randomInt is inlined to reduce dependencies.
867 var j = Math.floor(Math.random() * i); // exclusive
868 i--;
869 var tmp = this.tests_[i];
870 this.tests_[i] = this.tests_[j];
871 this.tests_[j] = tmp;
872 }
873 break;
874
875 case goog.testing.TestCase.Order.SORTED:
876 this.tests_.sort(function(t1, t2) {
877 if (t1.name == t2.name) {
878 return 0;
879 }
880 return t1.name < t2.name ? -1 : 1;
881 });
882 break;
883
884 // Do nothing for NATURAL.
885 }
886};
887
888
889/**
890 * Gets list of objects that potentially contain test cases. For IE 8 and below,
891 * this is the global "this" (for properties set directly on the global this or
892 * window) and the RuntimeObject (for global variables and functions). For all
893 * other browsers, the array simply contains the global this.
894 *
895 * @param {string=} opt_prefix An optional prefix. If specified, only get things
896 * under this prefix. Note that the prefix is only honored in IE, since it
897 * supports the RuntimeObject:
898 * http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx
899 * TODO: Remove this option.
900 * @return {!Array<!Object>} A list of objects that should be inspected.
901 */
902goog.testing.TestCase.prototype.getGlobals = function(opt_prefix) {
903 return goog.testing.TestCase.getGlobals(opt_prefix);
904};
905
906
907/**
908 * Gets list of objects that potentially contain test cases. For IE 8 and below,
909 * this is the global "this" (for properties set directly on the global this or
910 * window) and the RuntimeObject (for global variables and functions). For all
911 * other browsers, the array simply contains the global this.
912 *
913 * @param {string=} opt_prefix An optional prefix. If specified, only get things
914 * under this prefix. Note that the prefix is only honored in IE, since it
915 * supports the RuntimeObject:
916 * http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx
917 * TODO: Remove this option.
918 * @return {!Array<!Object>} A list of objects that should be inspected.
919 */
920goog.testing.TestCase.getGlobals = function(opt_prefix) {
921 // Look in the global scope for most browsers, on IE we use the little known
922 // RuntimeObject which holds references to all globals. We reference this
923 // via goog.global so that there isn't an aliasing that throws an exception
924 // in Firefox.
925 return typeof goog.global['RuntimeObject'] != 'undefined' ?
926 [goog.global['RuntimeObject']((opt_prefix || '') + '*'), goog.global] :
927 [goog.global];
928};
929
930
931/**
932 * @return {?goog.testing.TestCase} currently active test case or null if not
933 * test is currently running.
934 */
935goog.testing.TestCase.getActiveTestCase = function() {
936 var gTestRunner = goog.global['G_testRunner'];
937 if (gTestRunner && gTestRunner.testCase) {
938 return gTestRunner.testCase;
939 } else {
940 return null;
941 }
942};
943
944
945/**
946 * Gets called before any tests are executed. Can be overridden to set up the
947 * environment for the whole test case.
948 */
949goog.testing.TestCase.prototype.setUpPage = function() {};
950
951
952/**
953 * Gets called after all tests have been executed. Can be overridden to tear
954 * down the entire test case.
955 */
956goog.testing.TestCase.prototype.tearDownPage = function() {};
957
958
959/**
960 * Gets called before every goog.testing.TestCase.Test is been executed. Can be
961 * overridden to add set up functionality to each test.
962 */
963goog.testing.TestCase.prototype.setUp = function() {};
964
965
966/**
967 * Gets called after every goog.testing.TestCase.Test has been executed. Can be
968 * overriden to add tear down functionality to each test.
969 */
970goog.testing.TestCase.prototype.tearDown = function() {};
971
972
973/**
974 * @return {string} The function name prefix used to auto-discover tests.
975 */
976goog.testing.TestCase.prototype.getAutoDiscoveryPrefix = function() {
977 return 'test';
978};
979
980
981/**
982 * @return {number} Time since the last batch of tests was started.
983 * @protected
984 */
985goog.testing.TestCase.prototype.getBatchTime = function() {
986 return this.batchTime_;
987};
988
989
990/**
991 * @param {number} batchTime Time since the last batch of tests was started.
992 * @protected
993 */
994goog.testing.TestCase.prototype.setBatchTime = function(batchTime) {
995 this.batchTime_ = batchTime;
996};
997
998
999/**
1000 * Creates a {@code goog.testing.TestCase.Test} from an auto-discovered
1001 * function.
1002 * @param {string} name The name of the function.
1003 * @param {function() : void} ref The auto-discovered function.
1004 * @return {!goog.testing.TestCase.Test} The newly created test.
1005 * @protected
1006 */
1007goog.testing.TestCase.prototype.createTestFromAutoDiscoveredFunction =
1008 function(name, ref) {
1009 return new goog.testing.TestCase.Test(name, ref, goog.global);
1010};
1011
1012
1013/**
1014 * Adds any functions defined on 'obj' (the global object, by default)
1015 * that correspond to lifecycle events for the test case. Overrides
1016 * setUp, tearDown, setUpPage, tearDownPage, runTests, and shouldRunTests
1017 * if they are defined on 'obj'.
1018 * @param {!Object=} opt_obj Defaults to goog.global.
1019 */
1020goog.testing.TestCase.prototype.autoDiscoverLifecycle = function(opt_obj) {
1021 var obj = opt_obj || goog.global;
1022 if (obj['setUp']) {
1023 this.setUp = goog.bind(obj['setUp'], obj);
1024 }
1025 if (obj['tearDown']) {
1026 this.tearDown = goog.bind(obj['tearDown'], obj);
1027 }
1028 if (obj['setUpPage']) {
1029 this.setUpPage = goog.bind(obj['setUpPage'], obj);
1030 }
1031 if (obj['tearDownPage']) {
1032 this.tearDownPage = goog.bind(obj['tearDownPage'], obj);
1033 }
1034 if (obj['runTests']) {
1035 this.runTests = goog.bind(obj['runTests'], obj);
1036 }
1037 if (obj['shouldRunTests']) {
1038 this.shouldRunTests = goog.bind(obj['shouldRunTests'], obj);
1039 }
1040};
1041
1042
1043// TODO(johnlenz): make this package private
1044/**
1045 * @param {!Object} obj An object from which to extract test and lifecycle
1046 * methods.
1047 */
1048goog.testing.TestCase.prototype.setTestObj = function(obj) {
1049 // Drop any previously added (likely auto-discovered) tests, only one source
1050 // of discovered test and life-cycle methods is allowed.
1051 goog.asserts.assert(this.tests_.length == 0,
1052 'Test methods have already been configured.');
1053
1054 var regex = new RegExp('^' + this.getAutoDiscoveryPrefix());
1055 for (var name in obj) {
1056 if (regex.test(name)) {
1057 var testMethod = obj[name];
1058 if (goog.isFunction(testMethod)) {
1059 this.addNewTest(name, testMethod, obj);
1060 }
1061 }
1062 }
1063
1064 this.autoDiscoverLifecycle(obj);
1065};
1066
1067
1068/**
1069 * Adds any functions defined in the global scope that are prefixed with "test"
1070 * to the test case.
1071 */
1072goog.testing.TestCase.prototype.autoDiscoverTests = function() {
1073 var prefix = this.getAutoDiscoveryPrefix();
1074 var testSources = this.getGlobals(prefix);
1075
1076 var foundTests = [];
1077
1078 for (var i = 0; i < testSources.length; i++) {
1079 var testSource = testSources[i];
1080 for (var name in testSource) {
1081 if ((new RegExp('^' + prefix)).test(name)) {
1082 var ref;
1083 try {
1084 ref = testSource[name];
1085 } catch (ex) {
1086 // NOTE(brenneman): When running tests from a file:// URL on Firefox
1087 // 3.5 for Windows, any reference to goog.global.sessionStorage raises
1088 // an "Operation is not supported" exception. Ignore any exceptions
1089 // raised by simply accessing global properties.
1090 ref = undefined;
1091 }
1092
1093 if (goog.isFunction(ref)) {
1094 foundTests.push(this.createTestFromAutoDiscoveredFunction(name, ref));
1095 }
1096 }
1097 }
1098 }
1099
1100 for (var i = 0; i < foundTests.length; i++) {
1101 this.add(foundTests[i]);
1102 }
1103 this.orderTests_();
1104
1105 this.log(this.getCount() + ' tests auto-discovered');
1106
1107 // TODO(user): Do this as a separate call. Unfortunately, a lot of projects
1108 // currently override autoDiscoverTests and expect lifecycle events to be
1109 // registered as a part of this call.
1110 this.autoDiscoverLifecycle();
1111};
1112
1113
1114/**
1115 * Checks to see if the test should be marked as failed before it is run.
1116 *
1117 * If there was an error in setUpPage, we treat that as a failure for all tests
1118 * and mark them all as having failed.
1119 *
1120 * @param {goog.testing.TestCase.Test} testCase The current test case.
1121 * @return {boolean} Whether the test was marked as failed.
1122 * @protected
1123 */
1124goog.testing.TestCase.prototype.maybeFailTestEarly = function(testCase) {
1125 if (this.exceptionBeforeTest) {
1126 // We just use the first error to report an error on a failed test.
1127 testCase.name = 'setUpPage for ' + testCase.name;
1128 this.doError(testCase, this.exceptionBeforeTest);
1129 return true;
1130 }
1131 return false;
1132};
1133
1134
1135/**
1136 * Cycles through the tests, yielding asynchronously if the execution time
1137 * execeeds {@link #maxRunTime}. In particular, there is no guarantee that
1138 * the test case has finished execution once this method has returned.
1139 * To be notified when the test case has finished execution, use
1140 * {@link #setCompletedCallback} or {@link #runTestsReturningPromise}.
1141 */
1142goog.testing.TestCase.prototype.cycleTests = function() {
1143 this.saveMessage('Start');
1144 this.batchTime_ = this.now();
1145 if (this.running) {
1146 this.runNextTestCallback_ = goog.nullFunction;
1147 // Kick off the tests. runNextTest_ will schedule all of the tests,
1148 // using a mixture of synchronous and asynchronous strategies.
1149 this.runNextTest_();
1150 }
1151};
1152
1153
1154/**
1155 * Counts the number of files that were loaded for dependencies that are
1156 * required to run the test.
1157 * @return {number} The number of files loaded.
1158 * @private
1159 */
1160goog.testing.TestCase.prototype.countNumFilesLoaded_ = function() {
1161 var scripts = document.getElementsByTagName(goog.dom.TagName.SCRIPT);
1162 var count = 0;
1163 for (var i = 0, n = scripts.length; i < n; i++) {
1164 if (scripts[i].src) {
1165 count++;
1166 }
1167 }
1168 return count;
1169};
1170
1171
1172/**
1173 * Calls a function after a delay, using the protected timeout.
1174 * @param {Function} fn The function to call.
1175 * @param {number} time Delay in milliseconds.
1176 * @return {number} The timeout id.
1177 * @protected
1178 */
1179goog.testing.TestCase.prototype.timeout = function(fn, time) {
1180 // NOTE: invoking protectedSetTimeout_ as a member of goog.testing.TestCase
1181 // would result in an Illegal Invocation error. The method must be executed
1182 // with the global context.
1183 var protectedSetTimeout = goog.testing.TestCase.protectedSetTimeout_;
1184 return protectedSetTimeout(fn, time);
1185};
1186
1187
1188/**
1189 * Clears a timeout created by {@code this.timeout()}.
1190 * @param {number} id A timeout id.
1191 * @protected
1192 */
1193goog.testing.TestCase.prototype.clearTimeout = function(id) {
1194 // NOTE: see execution note for protectedSetTimeout above.
1195 var protectedClearTimeout = goog.testing.TestCase.protectedClearTimeout_;
1196 protectedClearTimeout(id);
1197};
1198
1199
1200/**
1201 * @return {number} The current time in milliseconds, don't use goog.now as some
1202 * tests override it.
1203 * @protected
1204 */
1205goog.testing.TestCase.prototype.now = function() {
1206 // Cannot use "new goog.testing.TestCase.protectedDate_()" due to b/8323223.
1207 var protectedDate = goog.testing.TestCase.protectedDate_;
1208 return new protectedDate().getTime();
1209};
1210
1211
1212/**
1213 * Returns the current time.
1214 * @return {string} HH:MM:SS.
1215 * @private
1216 */
1217goog.testing.TestCase.prototype.getTimeStamp_ = function() {
1218 // Cannot use "new goog.testing.TestCase.protectedDate_()" due to b/8323223.
1219 var protectedDate = goog.testing.TestCase.protectedDate_;
1220 var d = new protectedDate();
1221
1222 // Ensure millis are always 3-digits
1223 var millis = '00' + d.getMilliseconds();
1224 millis = millis.substr(millis.length - 3);
1225
1226 return this.pad_(d.getHours()) + ':' + this.pad_(d.getMinutes()) + ':' +
1227 this.pad_(d.getSeconds()) + '.' + millis;
1228};
1229
1230
1231/**
1232 * Pads a number to make it have a leading zero if it's less than 10.
1233 * @param {number} number The number to pad.
1234 * @return {string} The resulting string.
1235 * @private
1236 */
1237goog.testing.TestCase.prototype.pad_ = function(number) {
1238 return number < 10 ? '0' + number : String(number);
1239};
1240
1241
1242/**
1243 * Trims a path to be only that after google3.
1244 * @param {string} path The path to trim.
1245 * @return {string} The resulting string.
1246 * @private
1247 */
1248goog.testing.TestCase.prototype.trimPath_ = function(path) {
1249 return path.substring(path.indexOf('google3') + 8);
1250};
1251
1252
1253/**
1254 * Handles a test that passed.
1255 * @param {goog.testing.TestCase.Test} test The test that passed.
1256 * @protected
1257 */
1258goog.testing.TestCase.prototype.doSuccess = function(test) {
1259 this.result_.successCount++;
1260 // An empty list of error messages indicates that the test passed.
1261 // If we already have a failure for this test, do not set to empty list.
1262 if (!(test.name in this.result_.resultsByName)) {
1263 this.result_.resultsByName[test.name] = [];
1264 }
1265 var message = test.name + ' : PASSED';
1266 this.saveMessage(message);
1267 this.log(message);
1268};
1269
1270
1271/**
1272 * Handles a test that failed.
1273 * @param {goog.testing.TestCase.Test} test The test that failed.
1274 * @param {*=} opt_e The exception object associated with the
1275 * failure or a string.
1276 * @protected
1277 */
1278goog.testing.TestCase.prototype.doError = function(test, opt_e) {
1279 var message = test.name + ' : FAILED';
1280 this.log(message);
1281 this.saveMessage(message);
1282 var err = this.logError(test.name, opt_e);
1283 this.result_.errors.push(err);
1284 if (test.name in this.result_.resultsByName) {
1285 this.result_.resultsByName[test.name].push(err.toString());
1286 } else {
1287 this.result_.resultsByName[test.name] = [err.toString()];
1288 }
1289};
1290
1291
1292/**
1293 * @param {string} name Failed test name.
1294 * @param {*=} opt_e The exception object associated with the
1295 * failure or a string.
1296 * @return {!goog.testing.TestCase.Error} Error object.
1297 */
1298goog.testing.TestCase.prototype.logError = function(name, opt_e) {
1299 var errMsg = null;
1300 var stack = null;
1301 if (opt_e) {
1302 this.log(opt_e);
1303 if (goog.isString(opt_e)) {
1304 errMsg = opt_e;
1305 } else {
1306 errMsg = opt_e.message || opt_e.description || opt_e.toString();
1307 stack = opt_e.stack ? goog.testing.stacktrace.canonicalize(opt_e.stack) :
1308 opt_e['stackTrace'];
1309 }
1310 } else {
1311 errMsg = 'An unknown error occurred';
1312 }
1313 var err = new goog.testing.TestCase.Error(name, errMsg, stack);
1314
1315 // Avoid double logging.
1316 if (!opt_e || !opt_e['isJsUnitException'] ||
1317 !opt_e['loggedJsUnitException']) {
1318 this.saveMessage(err.toString());
1319 }
1320 if (opt_e && opt_e['isJsUnitException']) {
1321 opt_e['loggedJsUnitException'] = true;
1322 }
1323
1324 return err;
1325};
1326
1327
1328
1329/**
1330 * A class representing a single test function.
1331 * @param {string} name The test name.
1332 * @param {Function} ref Reference to the test function.
1333 * @param {Object=} opt_scope Optional scope that the test function should be
1334 * called in.
1335 * @constructor
1336 */
1337goog.testing.TestCase.Test = function(name, ref, opt_scope) {
1338 /**
1339 * The name of the test.
1340 * @type {string}
1341 */
1342 this.name = name;
1343
1344 /**
1345 * Reference to the test function.
1346 * @type {Function}
1347 */
1348 this.ref = ref;
1349
1350 /**
1351 * Scope that the test function should be called in.
1352 * @type {Object}
1353 */
1354 this.scope = opt_scope || null;
1355};
1356
1357
1358/**
1359 * Executes the test function.
1360 * @package
1361 */
1362goog.testing.TestCase.Test.prototype.execute = function() {
1363 this.ref.call(this.scope);
1364};
1365
1366
1367
1368/**
1369 * A class for representing test results. A bag of public properties.
1370 * @param {goog.testing.TestCase} testCase The test case that owns this result.
1371 * @constructor
1372 * @final
1373 */
1374goog.testing.TestCase.Result = function(testCase) {
1375 /**
1376 * The test case that owns this result.
1377 * @type {goog.testing.TestCase}
1378 * @private
1379 */
1380 this.testCase_ = testCase;
1381
1382 /**
1383 * Total number of tests that should have been run.
1384 * @type {number}
1385 */
1386 this.totalCount = 0;
1387
1388 /**
1389 * Total number of tests that were actually run.
1390 * @type {number}
1391 */
1392 this.runCount = 0;
1393
1394 /**
1395 * Number of successful tests.
1396 * @type {number}
1397 */
1398 this.successCount = 0;
1399
1400 /**
1401 * The amount of time the tests took to run.
1402 * @type {number}
1403 */
1404 this.runTime = 0;
1405
1406 /**
1407 * The number of files loaded to run this test.
1408 * @type {number}
1409 */
1410 this.numFilesLoaded = 0;
1411
1412 /**
1413 * Whether this test case was suppressed by shouldRunTests() returning false.
1414 * @type {boolean}
1415 */
1416 this.testSuppressed = false;
1417
1418 /**
1419 * Test results for each test that was run. The test name is always added
1420 * as the key in the map, and the array of strings is an optional list
1421 * of failure messages. If the array is empty, the test passed. Otherwise,
1422 * the test failed.
1423 * @type {!Object<string, !Array<string>>}
1424 */
1425 this.resultsByName = {};
1426
1427 /**
1428 * Errors encountered while running the test.
1429 * @type {!Array<goog.testing.TestCase.Error>}
1430 */
1431 this.errors = [];
1432
1433 /**
1434 * Messages to show the user after running the test.
1435 * @type {!Array<string>}
1436 */
1437 this.messages = [];
1438
1439 /**
1440 * Whether the tests have completed.
1441 * @type {boolean}
1442 */
1443 this.complete = false;
1444};
1445
1446
1447/**
1448 * @return {boolean} Whether the test was successful.
1449 */
1450goog.testing.TestCase.Result.prototype.isSuccess = function() {
1451 return this.complete && this.errors.length == 0;
1452};
1453
1454
1455/**
1456 * @return {string} A summary of the tests, including total number of tests that
1457 * passed, failed, and the time taken.
1458 */
1459goog.testing.TestCase.Result.prototype.getSummary = function() {
1460 var summary = this.runCount + ' of ' + this.totalCount + ' tests run in ' +
1461 this.runTime + 'ms.\n';
1462 if (this.testSuppressed) {
1463 summary += 'Tests not run because shouldRunTests() returned false.';
1464 } else {
1465 var failures = this.totalCount - this.successCount;
1466 var suppressionMessage = '';
1467
1468 var countOfRunTests = this.testCase_.getActuallyRunCount();
1469 if (countOfRunTests) {
1470 failures = countOfRunTests - this.successCount;
1471 suppressionMessage = ', ' +
1472 (this.totalCount - countOfRunTests) + ' suppressed by querystring';
1473 }
1474 summary += this.successCount + ' passed, ' +
1475 failures + ' failed' + suppressionMessage + '.\n' +
1476 Math.round(this.runTime / this.runCount) + ' ms/test. ' +
1477 this.numFilesLoaded + ' files loaded.';
1478 }
1479
1480 return summary;
1481};
1482
1483
1484/**
1485 * Initializes the given test case with the global test runner 'G_testRunner'.
1486 * @param {goog.testing.TestCase} testCase The test case to install.
1487 */
1488goog.testing.TestCase.initializeTestRunner = function(testCase) {
1489 testCase.autoDiscoverTests();
1490
1491 if (goog.global.location) {
1492 var search = goog.global.location.search;
1493 testCase.setOrder(goog.testing.TestCase.parseOrder_(search) ||
1494 goog.testing.TestCase.Order.SORTED);
1495 testCase.setTestsToRun(goog.testing.TestCase.parseRunTests_(search));
1496 }
1497
1498 var gTestRunner = goog.global['G_testRunner'];
1499 if (gTestRunner) {
1500 gTestRunner['initialize'](testCase);
1501 } else {
1502 throw Error('G_testRunner is undefined. Please ensure goog.testing.jsunit' +
1503 ' is included.');
1504 }
1505};
1506
1507
1508/**
1509 * Parses URL query parameters for the 'order' parameter.
1510 * @param {string} search The URL query string.
1511 * @return {?goog.testing.TestCase.Order} The sort order for running tests.
1512 * @private
1513 */
1514goog.testing.TestCase.parseOrder_ = function(search) {
1515 var order = null;
1516 var orderMatch = search.match(
1517 /(?:\?|&)order=(natural|random|sorted)/i);
1518 if (orderMatch) {
1519 order = /** @type {goog.testing.TestCase.Order} */ (
1520 orderMatch[1].toLowerCase());
1521 }
1522 return order;
1523};
1524
1525
1526/**
1527 * Parses URL query parameters for the 'runTests' parameter.
1528 * @param {string} search The URL query string.
1529 * @return {Object<string, boolean>} A set of test names or test indices to be
1530 * run by the test runner.
1531 * @private
1532 */
1533goog.testing.TestCase.parseRunTests_ = function(search) {
1534 var testsToRun = null;
1535 var runTestsMatch = search.match(/(?:\?|&)runTests=([^?&]+)/i);
1536 if (runTestsMatch) {
1537 testsToRun = {};
1538 var arr = runTestsMatch[1].split(',');
1539 for (var i = 0, len = arr.length; i < len; i++) {
1540 testsToRun[arr[i]] = true;
1541 }
1542 }
1543 return testsToRun;
1544};
1545
1546
1547/**
1548 * Wraps provided promise and returns a new promise which will be rejected
1549 * if the original promise does not settle within the given timeout.
1550 * @param {!IThenable<T>} promise
1551 * @param {number} timeoutInMs Number of milliseconds to wait for the promise to
1552 * settle before failing it with a timeout error.
1553 * @param {string} errorMsg Error message to use if the promise times out.
1554 * @return {!goog.Promise<T>} A promise that will settle with the original
1555 promise unless the timeout is exceeded.
1556 * errror.
1557 * @template T
1558 * @private
1559 */
1560goog.testing.TestCase.prototype.rejectIfPromiseTimesOut_ =
1561 function(promise, timeoutInMs, errorMsg) {
1562 var self = this;
1563 var start = this.now();
1564 return new goog.Promise(function(resolve, reject) {
1565 var timeoutId = self.timeout(function() {
1566 var elapsed = self.now() - start;
1567 reject(new Error(errorMsg + '\nElapsed time: ' + elapsed + 'ms.'));
1568 }, timeoutInMs);
1569 promise.then(resolve, reject);
1570 var clearTimeout = goog.bind(self.clearTimeout, self, timeoutId);
1571 promise.then(clearTimeout, clearTimeout);
1572 });
1573};
1574
1575
1576
1577/**
1578 * A class representing an error thrown by the test
1579 * @param {string} source The name of the test which threw the error.
1580 * @param {string} message The error message.
1581 * @param {string=} opt_stack A string showing the execution stack.
1582 * @constructor
1583 * @final
1584 */
1585goog.testing.TestCase.Error = function(source, message, opt_stack) {
1586 /**
1587 * The name of the test which threw the error.
1588 * @type {string}
1589 */
1590 this.source = source;
1591
1592 /**
1593 * Reference to the test function.
1594 * @type {string}
1595 */
1596 this.message = message;
1597
1598 /**
1599 * The stack.
1600 * @type {?string}
1601 */
1602 this.stack = null;
1603
1604 if (opt_stack) {
1605 this.stack = opt_stack;
1606 } else {
1607 // Attempt to capture a stack trace.
1608 if (Error.captureStackTrace) {
1609 // See https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi
1610 Error.captureStackTrace(this, goog.testing.TestCase.Error);
1611 } else {
1612 var stack = new Error().stack;
1613 if (stack) {
1614 this.stack = stack;
1615 }
1616 }
1617 }
1618};
1619
1620
1621/**
1622 * Returns a string representing the error object.
1623 * @return {string} A string representation of the error.
1624 * @override
1625 */
1626goog.testing.TestCase.Error.prototype.toString = function() {
1627 return 'ERROR in ' + this.source + '\n' +
1628 this.message + (this.stack ? '\n' + this.stack : '');
1629};