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