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