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