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