Path: blob/trunk/third_party/closure/goog/testing/testcase.js
4501 views
/**1* @license2* Copyright The Closure Library Authors.3* SPDX-License-Identifier: Apache-2.04*/56/**7* @fileoverview A class representing a set of test functions to be run.8*9* Testing code should not have dependencies outside of goog.testing so as to10* reduce the chance of masking missing dependencies.11*12* This file does not compile correctly with --collapse_properties. Use13* --property_renaming=ALL_UNQUOTED instead.14*/1516goog.setTestOnly('goog.testing.TestCase');17goog.provide('goog.testing.TestCase');18goog.provide('goog.testing.TestCase.Error');19goog.provide('goog.testing.TestCase.Order');20goog.provide('goog.testing.TestCase.Result');21goog.provide('goog.testing.TestCase.Test');222324goog.require('goog.Promise');25goog.require('goog.Thenable');26goog.require('goog.array');27goog.require('goog.asserts');28goog.require('goog.debug');29goog.require('goog.dom');30goog.require('goog.dom.TagName');31goog.require('goog.object');32goog.require('goog.testing.CspViolationObserver');33goog.require('goog.testing.JsUnitException');34goog.require('goog.url');35363738/**39* A class representing a JsUnit test case. A TestCase is made up of a number40* of test functions which can be run. Individual test cases can override the41* following functions to set up their test environment:42* - runTests - completely override the test's runner43* - setUpPage - called before any of the test functions are run44* - tearDownPage - called after all tests are finished45* - setUp - called before each of the test functions46* - tearDown - called after each of the test functions47* - shouldRunTests - called before a test run, all tests are skipped if it48* returns false. Can be used to disable tests on browsers49* where they aren't expected to pass.50* <p>51* TestCase objects are usually constructed by inspecting the global environment52* to discover functions that begin with the prefix <code>test</code>.53* (See {@link #autoDiscoverLifecycle} and {@link #autoDiscoverTests}.)54* </p>55*56* <h2>Testing asychronous code with promises</h2>57*58* <p>59* In the simplest cases, the behavior that the developer wants to test60* is synchronous, and the test functions exercising the behavior execute61* synchronously. But TestCase can also be used to exercise asynchronous code62* through the use of <a63* href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">64* promises</a>. If a test function returns an object that has a65* <code>then</code> method defined on it, the test framework switches to an66* asynchronous execution strategy: the next test function will not begin67* execution until the returned promise is resolved or rejected. Instead of68* writing test assertions at the top level inside a test function, the test69* author chains them on the end of the returned promise. For example:70* </p>71* <pre>72* function testPromiseBasedAPI() {73* return promiseBasedAPI().then(function(value) {74* // Will run when the promise resolves, and before the next75* // test function begins execution.76* assertEquals('foo', value.bar);77* });78* }79* </pre>80* <p>81* Synchronous and asynchronous tests can be mixed in the same TestCase.82* Test functions that return an object with a <code>then</code> method are83* executed asynchronously, and all other test functions are executed84* synchronously. While this is convenient for test authors (since it doesn't85* require any explicit configuration for asynchronous tests), it can lead to86* confusion if the test author forgets to return the promise from the test87* function. For example:88* </p>89* <pre>90* function testPromiseBasedAPI() {91* // This test should never succeed.92* promiseBasedAPI().then(fail, fail);93* // Oops! The promise isn't returned to the framework,94* // so this test actually does succeed.95* }96* </pre>97* <p>98* Since the test framework knows nothing about the promise created99* in the test function, it will run the function synchronously, record100* a success, and proceed immediately to the next test function.101* </p>102* <p>103* Promises returned from test functions can time out. If a returned promise104* is not resolved or rejected within {@link promiseTimeout} milliseconds,105* the test framework rejects the promise without a timeout error message.106* Test cases can configure the value of `promiseTimeout` by setting107* <pre>108* goog.testing.TestCase.getActiveTestCase().promiseTimeout = ...109* </pre>110* in their `setUpPage` methods.111* </p>112*113* @param {string=} opt_name The name of the test case, defaults to114* 'Untitled Test Case'.115* @constructor116*/117goog.testing.TestCase = function(opt_name) {118'use strict';119/**120* A name for the test case.121* @type {string}122* @private123*/124this.name_ = opt_name || 'Untitled Test Case';125126/**127* If the test should be auto discovered via {@link #autoDiscoverTests} when128* test case is initialized.129* @type {boolean}130* @private131*/132this.shouldAutoDiscoverTests_ = true;133134/**135* Array of test functions that can be executed.136* @type {!Array<!goog.testing.TestCase.Test>}137* @private138*/139this.tests_ = [];140141/**142* Set of test names and/or indices to execute, or null if all tests should143* be executed.144*145* Indices are included to allow automation tools to run a subset of the146* tests without knowing the exact contents of the test file.147*148* Indices should only be used with SORTED ordering.149*150* Example valid values:151* <ul>152* <li>[testName]153* <li>[testName1, testName2]154* <li>[2] - will run the 3rd test in the order specified155* <li>[1,3,5]156* <li>[testName1, testName2, 3, 5] - will work157* <ul>158* @type {?Object}159* @private160*/161this.testsToRun_ = null;162163/**164* A call back for each test.165* @private {?function(?goog.testing.TestCase.Test, !Array<string>)}166*/167this.testDone_ = null;168169/**170* The order to run the auto-discovered tests in.171* @type {string}172*/173this.order = goog.testing.TestCase.Order.SORTED;174175/** @private {function(!goog.testing.TestCase.Result)} */176this.runNextTestCallback_ = () => {};177178/**179* The currently executing test case or null.180* @private {?goog.testing.TestCase.Test}181*/182this.curTest_ = null;183184/**185* Object used to encapsulate the test results.186* @type {!goog.testing.TestCase.Result}187* @protected188* @suppress {underscore|visibility}189*/190this.result_ = new goog.testing.TestCase.Result(this);191192/**193* An array of exceptions generated by `assert` statements.194* @private {!Array<!goog.testing.JsUnitException>}195*/196this.thrownAssertionExceptions_ = [];197198/**199* The maximum time in milliseconds a promise returned from a test function200* may remain pending before the test fails due to timeout.201* @type {number}202*/203this.promiseTimeout = 1000; // 1s204205/**206* Callbacks that will be executed when the test has finalized.207* @private {!Array<function()>}208*/209this.onCompletedCallbacks_ = [];210211/** @type {number|undefined} */212this.endTime_;213214/** @private {number} */215this.testsRanSoFar_ = 0;216217/** @private {!goog.testing.CspViolationObserver} */218this.cspViolationObserver_ = new goog.testing.CspViolationObserver();219220/** @private {boolean} */221this.ignoreStartupCspViolations_ = false;222};223224225/**226* The order to run the auto-discovered tests.227* @enum {string}228*/229goog.testing.TestCase.Order = {230/**231* This is browser dependent and known to be different in FF and Safari232* compared to others.233*/234NATURAL: 'natural',235236/** Random order. */237RANDOM: 'random',238239/** Sorted based on the name. */240SORTED: 'sorted'241};242243244/**245* @return {string} The name of the test.246*/247goog.testing.TestCase.prototype.getName = function() {248'use strict';249return this.name_;250};251252/**253* Returns the current test or null.254* @return {?goog.testing.TestCase.Test}255* @protected256*/257goog.testing.TestCase.prototype.getCurrentTest = function() {258'use strict';259return this.curTest_;260};261262/**263* The maximum amount of time in milliseconds that the test case can take264* before it is forced to yield and reschedule. This prevents the test runner265* from blocking the browser and potentially hurting the test harness.266* @type {number}267*/268goog.testing.TestCase.maxRunTime = 200;269270271/**272* Save a reference to `window.setTimeout`, so any code that overrides the273* default behavior (the MockClock, for example) doesn't affect our runner.274* @type {function((Function|string), number=, *=): number}275* @private276*/277goog.testing.TestCase.protectedSetTimeout_ = goog.global.setTimeout;278279280/**281* Save a reference to `window.clearTimeout`, so any code that overrides282* the default behavior (e.g. MockClock) doesn't affect our runner.283* @type {function((null|number|undefined)): void}284* @private285*/286goog.testing.TestCase.protectedClearTimeout_ = goog.global.clearTimeout;287288289/**290* Save a reference to `window.Date`, so any code that overrides291* the default behavior doesn't affect our runner.292* @type {function(new: Date)}293* @private294*/295goog.testing.TestCase.protectedDate_ = Date;296297/**298* Save a reference to `window.performance`, so any code that overrides299* the default behavior doesn't affect our runner.300* @type {?Performance}301* @private302*/303goog.testing.TestCase.protectedPerformance_ = typeof window !== 'undefined' &&304window.performance && window.performance.now ?305performance :306null;307308309/**310* Name of the current test that is running, or null if none is running.311* @type {?string}312*/313goog.testing.TestCase.currentTestName = null;314315316/**317* Avoid a dependency on goog.userAgent and keep our own reference of whether318* the browser is IE.319* @type {boolean}320*/321goog.testing.TestCase.IS_IE = typeof opera == 'undefined' &&322!!goog.global.navigator &&323goog.global.navigator.userAgent.indexOf('MSIE') != -1;324325326/**327* Exception object that was detected before a test runs.328* @type {*}329* @protected330*/331goog.testing.TestCase.prototype.exceptionBeforeTest;332333334/**335* Whether the test case has ever tried to execute.336* @type {boolean}337*/338goog.testing.TestCase.prototype.started = false;339340341/**342* Whether the test case is running.343* @type {boolean}344*/345goog.testing.TestCase.prototype.running = false;346347348/**349* Timestamp for when the test was started.350* @type {number}351* @private352*/353goog.testing.TestCase.prototype.startTime_ = 0;354355356/**357* Time since the last batch of tests was started, if batchTime exceeds358* {@link #maxRunTime} a timeout will be used to stop the tests blocking the359* browser and a new batch will be started.360* @type {number}361* @private362*/363goog.testing.TestCase.prototype.batchTime_ = 0;364365366/**367* Pointer to the current test.368* @type {number}369* @private370*/371goog.testing.TestCase.prototype.currentTestPointer_ = 0;372373374/**375* Whether to use Native Promises or goog.Promise.376* @type {boolean}377* @private378*/379goog.testing.TestCase.prototype.useNativePromise_ = false;380381382/**383* Adds a new test to the test case.384* @param {!goog.testing.TestCase.Test} test The test to add.385*/386goog.testing.TestCase.prototype.add = function(test) {387'use strict';388goog.asserts.assert(test);389if (this.started) {390throw new Error(391'Tests cannot be added after execute() has been called. ' +392'Test: ' + test.name);393}394395this.tests_.push(test);396};397398399/**400* Creates and adds a new test.401*402* Convenience function to make syntax less awkward when not using automatic403* test discovery.404*405* @param {string} name The test name.406* @param {function()} ref Reference to the test function.407* @param {!Object=} scope Optional scope that the test function should be408* called in.409* @param {!Array<!Object>=} objChain An array of Objects that may have410* additional set up/tear down logic for a particular test.411*/412goog.testing.TestCase.prototype.addNewTest = function(413name, ref, scope, objChain) {414'use strict';415this.add(this.createTest(name, ref, scope || this, objChain));416};417418419/**420* Sets the tests.421* @param {!Array<goog.testing.TestCase.Test>} tests A new test array.422* @protected423*/424goog.testing.TestCase.prototype.setTests = function(tests) {425'use strict';426this.tests_ = tests;427};428429430/**431* Gets the tests.432* @return {!Array<goog.testing.TestCase.Test>} The test array.433*/434goog.testing.TestCase.prototype.getTests = function() {435'use strict';436return this.tests_;437};438439440/**441* Returns the number of tests contained in the test case.442* @return {number} The number of tests.443*/444goog.testing.TestCase.prototype.getCount = function() {445'use strict';446return this.tests_.length;447};448449450/**451* Returns the number of tests actually run in the test case, i.e. subtracting452* any which are skipped.453* @return {number} The number of un-ignored tests.454*/455goog.testing.TestCase.prototype.getActuallyRunCount = function() {456'use strict';457return this.testsToRun_ ? goog.object.getCount(this.testsToRun_) : 0;458};459460461/**462* Returns the current test and increments the pointer.463* @return {goog.testing.TestCase.Test} The current test case.464*/465goog.testing.TestCase.prototype.next = function() {466'use strict';467var test;468while ((test = this.tests_[this.currentTestPointer_++])) {469if (!this.testsToRun_ || this.testsToRun_[test.name] ||470this.testsToRun_[this.currentTestPointer_ - 1]) {471return test;472}473}474return null;475};476477478/**479* Resets the test case pointer, so that next returns the first test.480*/481goog.testing.TestCase.prototype.reset = function() {482'use strict';483this.currentTestPointer_ = 0;484this.result_ = new goog.testing.TestCase.Result(this);485};486487488/**489* Adds a callback function that should be executed when the tests have490* completed.491* @param {function()} fn The callback function.492*/493goog.testing.TestCase.prototype.addCompletedCallback = function(fn) {494'use strict';495this.onCompletedCallbacks_.push(fn);496};497498499/**500* @param {goog.testing.TestCase.Order} order The sort order for running tests.501*/502goog.testing.TestCase.prototype.setOrder = function(order) {503'use strict';504this.order = order;505};506507508/**509* @param {Object<string, boolean>} testsToRun Set of tests to run. Entries in510* the set may be test names, like "testFoo", or numeric indices. Only511* tests identified by name or by index will be executed.512*/513goog.testing.TestCase.prototype.setTestsToRun = function(testsToRun) {514'use strict';515this.testsToRun_ = testsToRun;516};517518519/**520* Can be overridden in test classes to indicate whether the tests in a case521* should be run in that particular situation. For example, this could be used522* to stop tests running in a particular browser, where browser support for523* the class under test was absent.524* @return {boolean} Whether any of the tests in the case should be run.525*/526goog.testing.TestCase.prototype.shouldRunTests = function() {527'use strict';528return true;529};530531532/**533* Executes the tests, yielding asynchronously if execution time exceeds534* {@link maxRunTime}. There is no guarantee that the test case has finished535* once this method has returned. To be notified when the test case536* has finished, use {@link #addCompletedCallback} or537* {@link #runTestsReturningPromise}.538*/539goog.testing.TestCase.prototype.execute = function() {540'use strict';541if (!this.prepareForRun_()) {542return;543}544this.groupLogsStart();545this.log('Starting tests: ' + this.name_);546this.cycleTests();547};548549550/**551* Sets up the internal state of the test case for a run.552* @return {boolean} If false, preparation failed because the test case553* is not supposed to run in the present environment.554* @private555*/556goog.testing.TestCase.prototype.prepareForRun_ = function() {557'use strict';558this.started = true;559this.reset();560this.startTime_ = this.now();561this.running = true;562this.result_.totalCount = this.getCount();563this.cspViolationObserver_.start();564if (!this.shouldRunTests()) {565this.log('shouldRunTests() returned false, skipping these tests.');566this.result_.testSuppressed = true;567this.finalize();568return false;569}570this.checkCspViolations_('shouldRunTests');571return true;572};573574575/**576* Finalizes the test case, called when the tests have finished executing.577*/578goog.testing.TestCase.prototype.finalize = function() {579'use strict';580this.saveMessage('Done');581582try {583this.tearDownPage();584} catch (e) {585// Report the error and continue with tests.586window['onerror'](e.toString(), document.location.href, 0, 0, e);587}588589this.endTime_ = this.now();590this.running = false;591this.result_.runTime = this.endTime_ - this.startTime_;592this.result_.numFilesLoaded = this.countNumFilesLoaded_();593this.result_.complete = true;594this.testsRanSoFar_++;595596this.log(this.result_.getSummary());597if (this.result_.isSuccess()) {598this.log('Tests complete');599} else {600this.log('Tests Failed');601}602this.onCompletedCallbacks_.forEach(function(cb) {603'use strict';604cb();605});606this.onCompletedCallbacks_ = [];607this.groupLogsEnd();608this.cspViolationObserver_.stop();609};610611612/**613* Saves a message to the result set.614* @param {string} message The message to save.615*/616goog.testing.TestCase.prototype.saveMessage = function(message) {617'use strict';618this.result_.messages.push(this.getTimeStamp_() + ' ' + message);619};620621622/**623* @return {boolean} Whether the test case is running inside the multi test624* runner.625*/626goog.testing.TestCase.prototype.isInsideMultiTestRunner = function() {627'use strict';628var top = goog.global['top'];629return top && typeof top['_allTests'] != 'undefined';630};631632/**633* @return {boolean} Whether the test-progress should be logged to the console.634*/635goog.testing.TestCase.prototype.shouldLogTestProgress = function() {636'use strict';637return !goog.global['skipClosureTestProgress'] &&638!this.isInsideMultiTestRunner();639};640641/**642* Logs an object to the console, if available.643* @param {*} val The value to log. Will be ToString'd.644*/645goog.testing.TestCase.prototype.log = function(val) {646'use strict';647if (this.shouldLogTestProgress() && goog.global.console) {648if (typeof val == 'string') {649val = this.getTimeStamp_() + ' : ' + val;650}651if (val instanceof Error && val.stack) {652goog.global.console.log(val.stack);653} else {654goog.global.console.log(val);655}656}657};658659660/**661* Groups the upcoming logs in the same log group662*/663goog.testing.TestCase.prototype.groupLogsStart = function() {664'use strict';665if (!this.isInsideMultiTestRunner() && goog.global.console &&666goog.global.console.group) {667goog.global.console.group(668'Test #' + (this.testsRanSoFar_ + 1) + ': ' + this.name_);669}670};671672673/**674* Closes the group of the upcoming logs675*/676goog.testing.TestCase.prototype.groupLogsEnd = function() {677'use strict';678if (!this.isInsideMultiTestRunner() && goog.global.console &&679goog.global.console.groupEnd) {680goog.global.console.groupEnd();681}682};683684685/**686* @return {boolean} Whether the test was a success.687*/688goog.testing.TestCase.prototype.isSuccess = function() {689'use strict';690return !!this.result_ && this.result_.isSuccess();691};692693694/**695* Returns a string detailing the results from the test.696* @param {boolean=} opt_verbose If true results will include data about all697* tests, not just what failed.698* @return {string} The results from the test.699*/700goog.testing.TestCase.prototype.getReport = function(opt_verbose) {701'use strict';702var rv = [];703704if (this.running) {705rv.push(this.name_ + ' [RUNNING]');706} else if (this.result_.runCount == 0) {707rv.push(this.name_ + ' [NO TESTS RUN]');708} else {709var label = this.result_.isSuccess() ? 'PASSED' : 'FAILED';710rv.push(this.name_ + ' [' + label + ']');711}712713if (goog.global.location) {714rv.push(this.trimPath_(goog.global.location.href));715}716717rv.push(this.result_.getSummary());718719if (opt_verbose) {720rv.push('.', this.result_.messages.join('\n'));721} else if (!this.result_.isSuccess()) {722rv.push(this.result_.errors.join('\n'));723}724725rv.push(' ');726727return rv.join('\n');728};729730731/**732* Returns the test results.733* @return {!goog.testing.TestCase.Result}734* @package735*/736goog.testing.TestCase.prototype.getResult = function() {737'use strict';738return this.result_;739};740741742/**743* Returns the amount of time it took for the test to run.744* @return {number} The run time, in milliseconds.745*/746goog.testing.TestCase.prototype.getRunTime = function() {747'use strict';748return this.result_.runTime;749};750751752/**753* Returns the number of script files that were loaded in order to run the test.754* @return {number} The number of script files.755*/756goog.testing.TestCase.prototype.getNumFilesLoaded = function() {757'use strict';758return this.result_.numFilesLoaded;759};760761762/**763* Represents a test result.764* @typedef {{765* 'source': string,766* 'message': string,767* 'stacktrace': string768* }}769*/770goog.testing.TestCase.IResult;771772/**773* Returns the test results object: a map from test names to a list of test774* failures (if any exist).775* @return {!Object<string, !Array<goog.testing.TestCase.IResult>>} Test776* results object.777*/778goog.testing.TestCase.prototype.getTestResults = function() {779'use strict';780var map = {};781goog.object.forEach(this.result_.resultsByName, function(resultArray, key) {782'use strict';783// Make sure we only use properties on the actual map784if (!Object.prototype.hasOwnProperty.call(785this.result_.resultsByName, key)) {786return;787}788map[key] = [];789for (var j = 0; j < resultArray.length; j++) {790map[key].push(resultArray[j].toObject_());791}792}, this);793return map;794};795796/**797* Executes each of the tests, yielding asynchronously if execution time798* exceeds {@link #maxRunTime}. There is no guarantee that the test case799* has finished execution once this method has returned.800* To be notified when the test case has finished execution, use801* {@link #addCompletedCallback} or {@link #runTestsReturningPromise}.802*803* Overridable by the individual test case. This allows test cases to defer804* when the test is actually started. If overridden, finalize must be805* called by the test to indicate it has finished.806*/807goog.testing.TestCase.prototype.runTests = function() {808'use strict';809goog.testing.TestCase.Continuation_.run(this.runSetUpPage_(this.execute));810};811812/**813* Configures the TestCase to use native Promises when waiting for methods that814* return Thenables.815*/816goog.testing.TestCase.prototype.useNativePromise = function() {817this.useNativePromise_ = true;818};819820/**821* Configures the TestCase to use goog.Promise when waiting for methods that822* return Thenables.823*/824goog.testing.TestCase.prototype.useGoogPromise = function() {825this.useNativePromise_ = false;826};827828829/**830* Executes each of the tests, returning a promise that resolves with the831* test results once they are done running.832* @return {!IThenable<!goog.testing.TestCase.Result>}833* @final834* @package835*/836goog.testing.TestCase.prototype.runTestsReturningPromise = function() {837'use strict';838/**839* @param {function(!goog.testing.TestCase.Result)} resolve840*/841const resolver = (resolve) => {842'use strict';843goog.testing.TestCase.Continuation_.run(this.runSetUpPage_(() => {844'use strict';845if (!this.prepareForRun_()) {846resolve(this.result_);847return;848}849this.groupLogsStart();850this.log('Starting tests: ' + this.name_);851this.saveMessage('Start');852this.batchTime_ = this.now();853this.runNextTestCallback_ = resolve;854goog.testing.TestCase.Continuation_.run(this.runNextTest_());855}));856};857if (this.useNativePromise_) {858return new Promise(resolver);859}860return new goog.Promise(resolver);861};862863864/**865* Runs the setUpPage methods.866* @param {function(this:goog.testing.TestCase)} runTestsFn Callback to invoke867* after setUpPage has completed.868* @return {?goog.testing.TestCase.Continuation_}869* @private870*/871goog.testing.TestCase.prototype.runSetUpPage_ = function(runTestsFn) {872'use strict';873const reports = goog.testing.CspViolationObserver.getBufferedReports();874875const ret = this.invokeFunction_(this.setUpPage, runTestsFn, function(e) {876'use strict';877this.exceptionBeforeTest = e;878runTestsFn.call(this);879}, 'setUpPage');880881if (!this.ignoreStartupCspViolations_ && reports.length > 0) {882const msg =883'One or more Content Security Policy violations occurred on the page ' +884'before the first test was run: ' +885goog.testing.CspViolationObserver.formatReports(reports);886// This CSP violation takes precedence over any pre-existing exception.887this.exceptionBeforeTest = msg;888}889890return ret;891};892893894/**895* Executes the next test method synchronously or with promises, depending on896* the test method's return value.897*898* If the test method returns a promise, the next test method will run once899* the promise is resolved or rejected. If the test method does not900* return a promise, it is assumed to be synchronous, and execution proceeds901* immediately to the next test method. This means that test cases can run902* partially synchronously and partially asynchronously, depending on903* the return values of their test methods. In particular, a test case904* executes synchronously until the first promise is returned from a905* test method (or until a resource limit is reached; see906* {@link finishTestInvocation_}).907* @return {?goog.testing.TestCase.Continuation_}908* @private909*/910goog.testing.TestCase.prototype.runNextTest_ = function() {911'use strict';912this.curTest_ = this.next();913if (!this.curTest_ || !this.running) {914this.finalize();915return new goog.testing.TestCase.Continuation_(916goog.bind(this.runNextTestCallback_, this, this.result_));917}918919var shouldRunTest = true;920try {921shouldRunTest = this.shouldRunTestsHelper_();922} catch (error) {923this.curTest_.name = 'shouldRunTests for ' + this.curTest_.name;924return new goog.testing.TestCase.Continuation_(925goog.bind(this.finishTestInvocation_, this, error));926}927928if (!shouldRunTest) {929return new goog.testing.TestCase.Continuation_(930goog.bind(this.finishTestInvocation_, this));931}932933this.cspViolationObserver_.setEnabled(true);934this.curTest_.started();935this.result_.runCount++;936this.log('Running test: ' + this.curTest_.name);937if (this.maybeFailTestEarly(this.curTest_)) {938return new goog.testing.TestCase.Continuation_(939goog.bind(this.finishTestInvocation_, this));940}941goog.testing.TestCase.currentTestName = this.curTest_.name;942return this.safeSetUp_();943};944945946/**947* @return {boolean}948* @private949*/950goog.testing.TestCase.prototype.shouldRunTestsHelper_ = function() {951'use strict';952var objChain =953this.curTest_.objChain.length ? this.curTest_.objChain : [this];954955for (var i = 0; i < objChain.length; i++) {956var obj = objChain[i];957958if (typeof obj.shouldRunTests !== 'function') {959continue;960}961962if (typeof obj.shouldRunTests['$cachedResult'] === 'function') {963if (!obj.shouldRunTests['$cachedResult']()) {964this.result_.suppressedTests.push(this.curTest_.name);965return false;966} else {967continue;968}969}970971var result;972(function() {973'use strict';974// Cache the result by storing a function. This way we only call975// shouldRunTests once per object in the chain. This enforces that people976// do not attempt to suppress some tests and not others with the same977// shouldRunTests function.978try {979var cached = result = obj.shouldRunTests.call(obj);980obj.shouldRunTests['$cachedResult'] = function() {981'use strict';982return cached;983};984} catch (error) {985obj.shouldRunTests['$cachedResult'] = function() {986'use strict';987throw error;988};989throw error;990}991})();992993if (!result) {994this.result_.suppressedTests.push(this.curTest_.name);995return false;996}997}998999return true;1000};10011002/**1003* Runs all the setups associated with a test.1004* @return {?goog.testing.TestCase.Continuation_}1005* @private1006*/1007goog.testing.TestCase.prototype.safeSetUp_ = function() {1008'use strict';1009var setUps =1010this.curTest_.setUps.length ? this.curTest_.setUps.slice() : [this.setUp];1011return this.safeSetUpHelper_(setUps).call(this);1012};10131014/**1015* Recursively invokes setUp functions.1016* @param {!Array<function()>} setUps1017* @return {function(): ?goog.testing.TestCase.Continuation_}1018* @private1019*/1020goog.testing.TestCase.prototype.safeSetUpHelper_ = function(setUps) {1021'use strict';1022if (!setUps.length) {1023return this.safeRunTest_;1024}1025return goog.bind(1026this.invokeFunction_, this, setUps.shift(), this.safeSetUpHelper_(setUps),1027this.safeTearDown_, 'setUp');1028};10291030/**1031* Calls the given test function, handling errors appropriately.1032* @return {?goog.testing.TestCase.Continuation_}1033* @private1034*/1035goog.testing.TestCase.prototype.safeRunTest_ = function() {1036'use strict';1037return this.invokeFunction_(1038goog.bind(this.curTest_.ref, this.curTest_.scope), this.safeTearDown_,1039this.safeTearDown_, this.curTest_.name);1040};104110421043/**1044* Calls {@link tearDown}, handling errors appropriately.1045* @param {*=} opt_error Error associated with the test, if any.1046* @return {?goog.testing.TestCase.Continuation_}1047* @private1048*/1049goog.testing.TestCase.prototype.safeTearDown_ = function(opt_error) {1050'use strict';1051// If the test itself failed, report that before running any tearDown()s.1052if (arguments.length == 1) {1053this.recordError(this.curTest_.name, opt_error);1054}1055var tearDowns = this.curTest_.tearDowns.length ?1056this.curTest_.tearDowns.slice() :1057[this.tearDown];1058return this.safeTearDownHelper_(tearDowns).call(this);1059};10601061/**1062* Recursively invokes tearDown functions.1063* @param {!Array<function()>} tearDowns1064* @return {function(): ?goog.testing.TestCase.Continuation_}1065* @private1066*/1067goog.testing.TestCase.prototype.safeTearDownHelper_ = function(tearDowns) {1068'use strict';1069if (!tearDowns.length) {1070return this.finishTestInvocation_;1071}1072return goog.bind(1073this.invokeFunction_, this, tearDowns.shift(),1074this.safeTearDownHelper_(tearDowns), this.finishTestInvocation_,1075'tearDown');1076};107710781079/**1080* Calls the given `fn`, then calls either `onSuccess` or1081* `onFailure`, either synchronously or using promises, depending on1082* `fn`'s return value.1083*1084* If `fn` throws an exception, `onFailure` is called immediately1085* with the exception.1086*1087* If `fn` returns a promise, and the promise is eventually resolved,1088* `onSuccess` is called with no arguments. If the promise is eventually1089* rejected, `onFailure` is called with the rejection reason.1090*1091* Otherwise, if `fn` neither returns a promise nor throws an exception,1092* `onSuccess` is called immediately with no arguments.1093*1094* `fn`, `onSuccess`, and `onFailure` are all called with1095* the TestCase instance as the method receiver.1096*1097* @param {function()} fn The function to call.1098* @param {function(this:goog.testing.TestCase):1099* (?goog.testing.TestCase.Continuation_|undefined)} onSuccess1100* @param {function(this:goog.testing.TestCase, *):1101* (?goog.testing.TestCase.Continuation_|undefined)} onFailure1102* @param {string} fnName Name of the function being invoked e.g. 'setUp'.1103* @return {?goog.testing.TestCase.Continuation_}1104* @private1105*/1106goog.testing.TestCase.prototype.invokeFunction_ = function(1107fn, onSuccess, onFailure, fnName) {1108'use strict';1109var self = this;1110this.thrownAssertionExceptions_ = [];1111try {1112this.cspViolationObserver_.start();1113var retval = fn.call(this);1114if (goog.Thenable.isImplementedBy(retval) ||1115(retval && typeof retval['then'] === 'function')) {1116// Resolve Thenable into a proper Promise to avoid hard to debug1117// problems.1118let promise;1119if (this.useNativePromise_) {1120promise = Promise.resolve(retval);1121} else {1122promise = goog.Promise.resolve(retval);1123}1124promise = this.rejectIfPromiseTimesOut_(1125promise, self.promiseTimeout,1126'Timed out while waiting for a promise returned from ' + fnName +1127' to resolve. Set goog.testing.TestCase.getActiveTestCase()' +1128'.promiseTimeout to adjust the timeout.');1129promise.then(1130function() {1131'use strict';1132self.resetBatchTimeAfterPromise_();1133self.checkCspViolations_(fnName);1134if (self.thrownAssertionExceptions_.length == 0) {1135goog.testing.TestCase.Continuation_.run(onSuccess.call(self));1136} else {1137goog.testing.TestCase.Continuation_.run(onFailure.call(1138self, self.reportUnpropagatedAssertionExceptions_(fnName)));1139}1140},1141function(e) {1142'use strict';1143self.reportUnpropagatedAssertionExceptions_(fnName, e);1144self.resetBatchTimeAfterPromise_();1145self.checkCspViolations_(fnName);1146goog.testing.TestCase.Continuation_.run(onFailure.call(self, e));1147});1148return null;1149} else {1150this.checkCspViolations_(fnName);1151if (this.thrownAssertionExceptions_.length == 0) {1152return new goog.testing.TestCase.Continuation_(1153goog.bind(onSuccess, this));1154} else {1155return new goog.testing.TestCase.Continuation_(goog.bind(1156onFailure, this,1157this.reportUnpropagatedAssertionExceptions_(fnName)));1158}1159}1160} catch (e) {1161this.checkCspViolations_(fnName);1162this.reportUnpropagatedAssertionExceptions_(fnName, e);1163return new goog.testing.TestCase.Continuation_(1164goog.bind(onFailure, this, e));1165}1166};116711681169/**1170* Logs all of the exceptions generated from failing assertions, and returns a1171* generic exception informing the user that one or more exceptions were not1172* propagated, causing the test to erroneously pass.1173*1174* This is also called when a test fails so that the user sees swallowed errors.1175* (This can make it much easier to debug failures in callbacks in catch blocks)1176* If the actually-thrown error (that made the test fail) is also a JSUnit error1177* (which will therefore be in this array), it will be silently deduped when the1178* regular failure handler tries to record it again.1179* @param {string} testName The test function's name.1180* @param {*=} actualError The thrown error the made the test fail, if any1181* @return {!goog.testing.JsUnitException}1182* @private1183*/1184goog.testing.TestCase.prototype.reportUnpropagatedAssertionExceptions_ =1185function(testName, actualError) {1186'use strict';1187var extraExceptions = this.thrownAssertionExceptions_.slice();1188// If the actual error isn't a JSUnit exception, it won't be in this array.1189goog.array.remove(extraExceptions, actualError);1190var numExceptions = extraExceptions.length;1191if (numExceptions && actualError) {1192// Don't log this message if the only exception is the actual failure.1193var message =1194numExceptions + ' additional exceptions were swallowed by the test:';1195this.log(message);1196this.saveMessage(message);1197}119811991200for (var i = 0; i < numExceptions; i++) {1201this.recordError(testName, extraExceptions[i]);1202}12031204// Mark the test as failed.1205return new goog.testing.JsUnitException(1206'One or more assertions were raised but not caught by the testing ' +1207'framework. These assertions may have been unintentionally captured ' +1208'by a catch block or a thenCatch resolution of a Promise.');1209};121012111212/**1213* Resets the batch run timer. This should only be called after resolving a1214* promise since Promise.then() has an implicit yield.1215* @private1216*/1217goog.testing.TestCase.prototype.resetBatchTimeAfterPromise_ = function() {1218'use strict';1219this.batchTime_ = this.now();1220};122112221223/**1224* Finishes up bookkeeping for the current test function, and schedules1225* the next test function to run, either immediately or asychronously.1226* @param {*=} opt_error Optional error resulting from the test invocation.1227* @return {?goog.testing.TestCase.Continuation_}1228* @private1229*/1230goog.testing.TestCase.prototype.finishTestInvocation_ = function(opt_error) {1231'use strict';1232if (arguments.length == 1) {1233this.recordError(this.curTest_.name, opt_error);1234}12351236// If no errors have been recorded for the test, it is a success.1237if (!(this.curTest_.name in this.result_.resultsByName) ||1238!this.result_.resultsByName[this.curTest_.name].length) {1239if (this.result_.suppressedTests.indexOf(this.curTest_.name) >= 0) {1240this.doSkipped(this.curTest_);1241} else {1242this.doSuccess(this.curTest_);1243}1244} else {1245this.doError(this.curTest_);1246}12471248goog.testing.TestCase.currentTestName = null;12491250// If the test case has consumed too much time or stack space,1251// yield to avoid blocking the browser. Otherwise, proceed to the next test.1252if (this.now() - this.batchTime_ > goog.testing.TestCase.maxRunTime) {1253this.saveMessage('Breaking async');1254this.timeout(goog.bind(this.startNextBatch_, this), 0);1255return null;1256} else {1257return new goog.testing.TestCase.Continuation_(1258goog.bind(this.runNextTest_, this));1259}1260};126112621263/**1264* Checks if any CSP violations have been logged since1265* this.cspViolationObserver_.start() was called and reports them as errors.1266*1267* @param {string} name1268* @private1269*/1270goog.testing.TestCase.prototype.checkCspViolations_ = function(name) {1271const reports = this.cspViolationObserver_.stop();1272if (reports.length == 0) {1273return;1274}12751276const formattedReports =1277goog.testing.CspViolationObserver.formatReports(reports);1278const msg =1279'One or more Content Security Policy violations occurred during ' +1280'execution of this test: ' + formattedReports;1281if (this.started) {1282this.recordError(name, msg);1283} else {1284this.exceptionBeforeTest = msg;1285}1286};128712881289/**1290* Start a new batch to tests after yielding, resetting batchTime and depth.1291* @private1292*/1293goog.testing.TestCase.prototype.startNextBatch_ = function() {1294'use strict';1295this.batchTime_ = this.now();1296goog.testing.TestCase.Continuation_.run(this.runNextTest_());1297};129812991300/**1301* Reorders the tests depending on the `order` field.1302* @private1303*/1304goog.testing.TestCase.prototype.orderTests_ = function() {1305'use strict';1306switch (this.order) {1307case goog.testing.TestCase.Order.RANDOM:1308// Fisher-Yates shuffle1309var i = this.tests_.length;1310while (i > 1) {1311// goog.math.randomInt is inlined to reduce dependencies.1312var j = Math.floor(Math.random() * i); // exclusive1313i--;1314var tmp = this.tests_[i];1315this.tests_[i] = this.tests_[j];1316this.tests_[j] = tmp;1317}1318break;13191320case goog.testing.TestCase.Order.SORTED:1321this.tests_.sort(function(t1, t2) {1322'use strict';1323if (t1.name == t2.name) {1324return 0;1325}1326return t1.name < t2.name ? -1 : 1;1327});1328break;13291330// Do nothing for NATURAL.1331}1332};133313341335/**1336* Gets list of objects that potentially contain test cases. For IE 8 and1337* below, this is the global "this" (for properties set directly on the global1338* this or window) and the RuntimeObject (for global variables and functions).1339* For all other browsers, the array simply contains the global this.1340*1341* @param {string=} opt_prefix An optional prefix. If specified, only get things1342* under this prefix. Note that the prefix is only honored in IE, since it1343* supports the RuntimeObject:1344* http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx1345* TODO: Remove this option.1346* @return {!Array<!Object>} A list of objects that should be inspected.1347*/1348goog.testing.TestCase.prototype.getGlobals = function(opt_prefix) {1349'use strict';1350return goog.testing.TestCase.getGlobals(opt_prefix);1351};135213531354/**1355* Gets list of objects that potentially contain test cases. For IE 8 and1356* below, this is the global "this" (for properties set directly on the global1357* this or window) and the RuntimeObject (for global variables and functions).1358* For all other browsers, the array simply contains the global this.1359*1360* @param {string=} opt_prefix An optional prefix. If specified, only get things1361* under this prefix. Note that the prefix is only honored in IE, since it1362* supports the RuntimeObject:1363* http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx1364* TODO: Remove this option.1365* @return {!Array<!Object>} A list of objects that should be inspected.1366*/1367goog.testing.TestCase.getGlobals = function(opt_prefix) {1368'use strict';1369// Look in the global scope for most browsers, on IE we use the little known1370// RuntimeObject which holds references to all globals. We reference this1371// via goog.global so that there isn't an aliasing that throws an exception1372// in Firefox.1373return typeof goog.global['RuntimeObject'] != 'undefined' ?1374[goog.global['RuntimeObject']((opt_prefix || '') + '*'), goog.global] :1375[goog.global];1376};137713781379/**1380* @private {?goog.testing.TestCase}1381*/1382goog.testing.TestCase.activeTestCase_ = null;138313841385/**1386* @return {?goog.testing.TestCase} currently active test case or null if not1387* test is currently running. Tries the G_testRunner first then the stored1388* value (when run outside of G_testRunner.1389*/1390goog.testing.TestCase.getActiveTestCase = function() {1391'use strict';1392var gTestRunner = goog.global['G_testRunner'];1393if (gTestRunner && gTestRunner.testCase) {1394return gTestRunner.testCase;1395} else {1396return goog.testing.TestCase.activeTestCase_;1397}1398};139914001401/**1402* Calls {@link goog.testing.TestCase.prototype.invalidateAssertionException}1403* on the active test case if it is installed, and logs an error otherwise.1404* @param {!goog.testing.JsUnitException} e The exception object to invalidate.1405* @package1406*/1407goog.testing.TestCase.invalidateAssertionException = function(e) {1408'use strict';1409var testCase = goog.testing.TestCase.getActiveTestCase();1410if (testCase) {1411testCase.invalidateAssertionException(e);1412} else {1413goog.global.console.error(1414'Failed to remove expected exception: no test case is installed.');1415}1416};141714181419/**1420* Gets called before any tests are executed. Can be overridden to set up the1421* environment for the whole test case.1422* @return {!Thenable|undefined}1423*/1424goog.testing.TestCase.prototype.setUpPage = function() {};142514261427/**1428* Gets called after all tests have been executed. Can be overridden to tear1429* down the entire test case.1430*/1431goog.testing.TestCase.prototype.tearDownPage = function() {};143214331434/**1435* Gets called before every goog.testing.TestCase.Test is been executed. Can1436* be overridden to add set up functionality to each test.1437* @return {!Thenable|undefined}1438*/1439goog.testing.TestCase.prototype.setUp = function() {};144014411442/**1443* Gets called after every goog.testing.TestCase.Test has been executed. Can1444* be overridden to add tear down functionality to each test.1445* @return {!Thenable|undefined}1446*/1447goog.testing.TestCase.prototype.tearDown = function() {};144814491450/**1451* @return {string} The function name prefix used to auto-discover tests.1452*/1453goog.testing.TestCase.prototype.getAutoDiscoveryPrefix = function() {1454'use strict';1455return 'test';1456};145714581459/**1460* @return {number} Time since the last batch of tests was started.1461* @protected1462*/1463goog.testing.TestCase.prototype.getBatchTime = function() {1464'use strict';1465return this.batchTime_;1466};146714681469/**1470* @param {number} batchTime Time since the last batch of tests was started.1471* @protected1472*/1473goog.testing.TestCase.prototype.setBatchTime = function(batchTime) {1474'use strict';1475this.batchTime_ = batchTime;1476};147714781479/**1480* Creates a `goog.testing.TestCase.Test` from an auto-discovered1481* function.1482* @param {string} name The name of the function.1483* @param {function()} ref The auto-discovered function.1484* @param {!Object=} scope The scope to attach to the test.1485* @param {!Array<!Object>=} objChain1486* @return {!goog.testing.TestCase.Test} The newly created test.1487* @protected1488*/1489goog.testing.TestCase.prototype.createTest = function(1490name, ref, scope, objChain) {1491'use strict';1492return new goog.testing.TestCase.Test(name, ref, scope, objChain);1493};149414951496/**1497* Adds any functions defined on the global object1498* that correspond to lifecycle events for the test case. Overrides1499* setUp, tearDown, setUpPage, tearDownPage, runTests, and shouldRunTests1500* if they are defined on global object.1501*/1502goog.testing.TestCase.prototype.autoDiscoverLifecycle = function() {1503'use strict';1504this.setLifecycleObj(goog.global);1505};150615071508// TODO(johnlenz): make this package private1509/**1510* Extracts any functions defined on 'obj' that correspond to page lifecycle1511* events (setUpPage, tearDownPage, runTests, shouldRunTests) and add them to1512* on this test case.1513* @param {!Object} obj1514*/1515goog.testing.TestCase.prototype.setLifecycleObj = function(obj) {1516'use strict';1517if (obj['setUp']) {1518this.setUp = goog.bind(obj['setUp'], obj);1519}1520if (obj['tearDown']) {1521this.tearDown = goog.bind(obj['tearDown'], obj);1522}1523if (obj['setUpPage']) {1524this.setUpPage = goog.bind(obj['setUpPage'], obj);1525}1526if (obj['tearDownPage']) {1527this.tearDownPage = goog.bind(obj['tearDownPage'], obj);1528}1529if (obj['runTests']) {1530this.runTests = goog.bind(obj['runTests'], obj);1531}1532if (obj['shouldRunTests']) {1533this.shouldRunTests = goog.bind(obj['shouldRunTests'], obj);1534}1535};153615371538// TODO(johnlenz): make this package private1539/**1540* @param {!Object} obj An object from which to extract test and lifecycle1541* methods.1542*/1543goog.testing.TestCase.prototype.setTestObj = function(obj) {1544'use strict';1545// Check any previously added (likely auto-discovered) tests, only one source1546// of discovered test and life-cycle methods is allowed.1547if (this.tests_.length > 0) {1548throw new Error(1549'Test methods have already been configured.\n' +1550'Tests previously found:\n' +1551this.tests_1552.map(function(test) {1553'use strict';1554return test.name;1555})1556.join('\n') +1557'\nNew tests found:\n' +1558Object.keys(obj)1559.filter(function(name) {1560'use strict';1561return name.startsWith('test');1562})1563.join('\n'));1564}1565this.shouldAutoDiscoverTests_ = false;1566if (obj['getTestName']) {1567this.name_ = obj['getTestName']();1568}1569this.setLifecycleObj(obj);1570this.addTestObj_(obj, '', [this]);1571};15721573/**1574* @param {!Object} obj An object from which to extract test and lifecycle1575* methods.1576* @param {string} name1577* @param {!Array<!Object>} objChain List of objects that have methods used1578* to create tests such as setUp, tearDown.1579* @private1580*/1581goog.testing.TestCase.prototype.addTestObj_ = function(obj, name, objChain) {1582'use strict';1583var regex = new RegExp('^' + this.getAutoDiscoveryPrefix());1584var properties = goog.object.getAllPropertyNames(obj);1585for (var i = 0; i < properties.length; i++) {1586var testName = properties[i];1587if (regex.test(testName)) {1588var testProperty;1589try {1590testProperty = obj[testName];1591} catch (ex) {1592// NOTE(brenneman): When running tests from a file:// URL on Firefox1593// 3.5 for Windows, any reference to goog.global.sessionStorage raises1594// an "Operation is not supported" exception. Ignore any exceptions1595// raised by simply accessing global properties.1596testProperty = null;1597}1598if (name) {1599testName = testName.slice(this.getAutoDiscoveryPrefix().length);1600}1601var fullTestName = name + (testName && name ? '_' : '') + testName;1602if (typeof testProperty === 'function') {1603this.addNewTest(fullTestName, testProperty, obj, objChain);1604} else if (goog.isObject(testProperty) && !Array.isArray(testProperty)) {1605// To prevent infinite loops.1606if (!goog.array.contains(objChain, testProperty)) {1607goog.asserts.assertObject(testProperty);1608var newObjChain = objChain.slice();1609newObjChain.push(testProperty);1610this.addTestObj_(testProperty, fullTestName, newObjChain);1611}1612}1613}1614}1615};161616171618/**1619* Adds any functions defined in the global scope that are prefixed with1620* "test" to the test case.1621*/1622goog.testing.TestCase.prototype.autoDiscoverTests = function() {1623'use strict';1624this.autoDiscoverLifecycle();1625var prefix = this.getAutoDiscoveryPrefix();1626var testSources = this.getGlobals(prefix);16271628for (var i = 0; i < testSources.length; i++) {1629var testSource = testSources[i];1630this.addTestObj_(testSource, '', [this]);1631}16321633this.orderTests_();1634};163516361637/**1638* Checks to see if the test should be marked as failed before it is run.1639*1640* If there was an error in setUpPage, we treat that as a failure for all1641* tests and mark them all as having failed.1642*1643* @param {goog.testing.TestCase.Test} testCase The current test case.1644* @return {boolean} Whether the test was marked as failed.1645* @protected1646*/1647goog.testing.TestCase.prototype.maybeFailTestEarly = function(testCase) {1648'use strict';1649if (this.exceptionBeforeTest) {1650// We just use the first error to report an error on a failed test.1651testCase.name = 'setUpPage for ' + testCase.name;1652this.recordError(testCase.name, this.exceptionBeforeTest);1653return true;1654}1655return false;1656};165716581659/**1660* Cycles through the tests, yielding asynchronously if the execution time1661* exceeds {@link #maxRunTime}. In particular, there is no guarantee that1662* the test case has finished execution once this method has returned.1663* To be notified when the test case has finished execution, use1664* {@link #addCompletedCallback} or {@link #runTestsReturningPromise}.1665*/1666goog.testing.TestCase.prototype.cycleTests = function() {1667'use strict';1668this.saveMessage('Start');1669this.batchTime_ = this.now();1670if (this.running) {1671this.runNextTestCallback_ = () => {};1672// Kick off the tests. runNextTest_ will schedule all of the tests,1673// using a mixture of synchronous and asynchronous strategies.1674goog.testing.TestCase.Continuation_.run(this.runNextTest_());1675}1676};167716781679/**1680* Counts the number of files that were loaded for dependencies that are1681* required to run the test.1682* @return {number} The number of files loaded.1683* @private1684*/1685goog.testing.TestCase.prototype.countNumFilesLoaded_ = function() {1686'use strict';1687var scripts = goog.dom.getElementsByTagName(goog.dom.TagName.SCRIPT);1688var count = 0;1689for (var i = 0, n = scripts.length; i < n; i++) {1690if (scripts[i].src) {1691count++;1692}1693}1694return count;1695};169616971698/**1699* Calls a function after a delay, using the protected timeout.1700* @param {Function} fn The function to call.1701* @param {number} time Delay in milliseconds.1702* @return {number} The timeout id.1703* @protected1704*/1705goog.testing.TestCase.prototype.timeout = function(fn, time) {1706'use strict';1707// NOTE: invoking protectedSetTimeout_ as a member of goog.testing.TestCase1708// would result in an Illegal Invocation error. The method must be executed1709// with the global context.1710var protectedSetTimeout = goog.testing.TestCase.protectedSetTimeout_;1711return protectedSetTimeout(fn, time);1712};171317141715/**1716* Clears a timeout created by `this.timeout()`.1717* @param {number} id A timeout id.1718* @protected1719*/1720goog.testing.TestCase.prototype.clearTimeout = function(id) {1721'use strict';1722// NOTE: see execution note for protectedSetTimeout above.1723var protectedClearTimeout = goog.testing.TestCase.protectedClearTimeout_;1724protectedClearTimeout(id);1725};172617271728/**1729* @return {number} The current time in milliseconds.1730* @protected1731*/1732goog.testing.TestCase.prototype.now = function() {1733'use strict';1734return goog.testing.TestCase.now();1735};173617371738/**1739* @return {number} The current time in milliseconds.1740* @protected1741*/1742goog.testing.TestCase.now = function() {1743'use strict';1744// don't use goog.now as some tests override it.1745if (goog.testing.TestCase.protectedPerformance_) {1746return goog.testing.TestCase.protectedPerformance_.now();1747}1748// Fallback for IE81749// Cannot use "new goog.testing.TestCase.protectedDate_()" due to b/8323223.1750var protectedDate = goog.testing.TestCase.protectedDate_;1751return new protectedDate().getTime();1752};175317541755/**1756* Returns the current time.1757* @return {string} HH:MM:SS.1758* @private1759*/1760goog.testing.TestCase.prototype.getTimeStamp_ = function() {1761'use strict';1762// Cannot use "new goog.testing.TestCase.protectedDate_()" due to b/8323223.1763var protectedDate = goog.testing.TestCase.protectedDate_;1764var d = new protectedDate();17651766// Ensure millis are always 3-digits1767var millis = '00' + d.getMilliseconds();1768millis = millis.slice(-3);17691770return this.pad_(d.getHours()) + ':' + this.pad_(d.getMinutes()) + ':' +1771this.pad_(d.getSeconds()) + '.' + millis;1772};177317741775/**1776* Pads a number to make it have a leading zero if it's less than 10.1777* @param {number} number The number to pad.1778* @return {string} The resulting string.1779* @private1780*/1781goog.testing.TestCase.prototype.pad_ = function(number) {1782'use strict';1783return number < 10 ? '0' + number : String(number);1784};178517861787/**1788* Trims a path to be only that after google3.1789* @param {string} path The path to trim.1790* @return {string} The resulting string.1791* @private1792*/1793goog.testing.TestCase.prototype.trimPath_ = function(path) {1794'use strict';1795return path.substring(path.indexOf('google3') + 8);1796};179717981799/**1800* Handles a test that passed.1801* @param {goog.testing.TestCase.Test} test The test that passed.1802* @protected1803*/1804goog.testing.TestCase.prototype.doSuccess = function(test) {1805'use strict';1806this.result_.successCount++;1807// An empty list of error messages indicates that the test passed.1808// If we already have a failure for this test, do not set to empty list.1809if (!(test.name in this.result_.resultsByName)) {1810this.result_.resultsByName[test.name] = [];1811}1812var message = test.name + ' : PASSED';1813this.saveMessage(message);1814this.log(message);1815if (this.testDone_) {1816this.doTestDone_(test, []);1817}1818};181918201821/**1822* Handles a test that was skipped.1823* @param {!goog.testing.TestCase.Test} test The test that was skipped.1824* @protected1825*/1826goog.testing.TestCase.prototype.doSkipped = function(test) {1827'use strict';1828this.result_.skipCount++;1829// An empty list of error messages indicates that the test passed.1830// If we already have a failure for this test, do not set to empty list.1831if (!(test.name in this.result_.resultsByName)) {1832this.result_.resultsByName[test.name] = [];1833}1834var message = test.name + ' : SKIPPED';1835this.saveMessage(message);1836this.log(message);1837if (this.testDone_) {1838this.doTestDone_(test, []);1839}1840};184118421843/**1844* Records an error that fails the current test, without throwing it.1845*1846* Use this function to implement expect()-style assertion libraries that fail a1847* test without breaking execution (so you can see further failures). Do not use1848* this from normal test code.1849*1850* Please contact js-core-libraries-team@ before using this method. If it grows1851* popular, we may add an expect() API to Closure.1852*1853* NOTE: If there is no active TestCase, you must throw an error.1854* @param {!Error} error The error to log. If it is a JsUnitException which has1855* already been logged, nothing will happen.1856*/1857goog.testing.TestCase.prototype.recordTestError = function(error) {1858'use strict';1859this.recordError(1860this.curTest_ ? this.curTest_.name : '<No active test>', error);1861};1862186318641865/**1866* Records and logs an error from or related to a test.1867* @param {string} testName The name of the test that failed.1868* @param {*} error The exception object associated with the1869* failure or a string.1870* @protected1871*/1872goog.testing.TestCase.prototype.recordError = function(testName, error) {1873'use strict';1874if (error && error['isJsUnitException'] && error['loggedJsUnitException']) {1875// We already logged this error; don't record it again. This is particularly1876// important for errors from mocks, which are rethrown by $verify, called by1877// tearDown().1878return;1879}18801881var err = this.logError(testName, error);1882this.result_.errors.push(err);1883if (testName in this.result_.resultsByName) {1884this.result_.resultsByName[testName].push(err);1885} else {1886this.result_.resultsByName[testName] = [err];1887}18881889if (error && error['isJsUnitException']) {1890error['loggedJsUnitException'] = true;1891}1892};189318941895/**1896* Handles a test that failed.1897* @param {goog.testing.TestCase.Test} test The test that failed.1898* @protected1899*/1900goog.testing.TestCase.prototype.doError = function(test) {1901'use strict';1902var message = test.name + ' : FAILED';1903this.log(message);1904this.saveMessage(message);19051906if (this.testDone_) {1907var results = this.result_.resultsByName[test.name];1908var errMsgs = [];1909for (var i = 0; i < results.length; i++) {1910errMsgs.push(results[i].toString());1911}1912this.doTestDone_(test, errMsgs);1913}1914};191519161917/**1918* Makes note of an exception arising from an assertion, and then throws it.1919* If the test otherwise passes (i.e., because something else caught the1920* exception on its way to the test framework), it will be forced to fail.1921* @param {!goog.testing.JsUnitException} e The exception object being thrown.1922* @throws {goog.testing.JsUnitException}1923* @package1924*/1925goog.testing.TestCase.prototype.raiseAssertionException = function(e) {1926'use strict';1927this.thrownAssertionExceptions_.push(e);1928throw e;1929};193019311932/**1933* Removes the specified exception from being tracked. This only needs to be1934* called for internal functions that intentionally catch an exception, such1935* as1936* `#assertThrowsJsUnitException`.1937* @param {!goog.testing.JsUnitException} e The exception object to invalidate.1938* @package1939*/1940goog.testing.TestCase.prototype.invalidateAssertionException = function(e) {1941'use strict';1942goog.array.remove(this.thrownAssertionExceptions_, e);1943};194419451946/**1947* @param {string} name Failed test name.1948* @param {*} error The exception object associated with the1949* failure or a string.1950* @return {!goog.testing.TestCase.Error} Error object.1951* @suppress {missingProperties} message and stack properties1952*/1953goog.testing.TestCase.prototype.logError = function(name, error) {1954'use strict';1955if (error) {1956this.log(error);1957}19581959var normalizedError = goog.debug.normalizeErrorObject(error);1960var stack =1961this.cleanStackTrace_(normalizedError.stack, normalizedError.message);1962var err =1963new goog.testing.TestCase.Error(name, normalizedError.message, stack);19641965this.saveMessage(err.toString());19661967return err;1968};19691970/**1971* @param {?string} stack1972* @param {string} errMsg1973* @return {string|undefined}1974* @private1975*/1976goog.testing.TestCase.prototype.cleanStackTrace_ = function(stack, errMsg) {1977'use strict';1978if (!stack) {1979return;1980}19811982// The Error class includes the message in the stack. Don't duplicate it.1983stack = stack.replace('Error: ' + errMsg + '\n', 'Error\n');19841985// Remove extra goog.testing.TestCase frames from all stacks (main error +1986// causes if they exists)1987var index = 0;1988while (index < stack.length) {1989var extraFrameIndex = stack.search(1990/\s*(\bat\b)?\s*(goog\.labs\.testing\.EnvironmentTestCase_\.)?goog\.testing\.(Continuation_\.(prototype\.)?run|TestCase\.(prototype\.)?(execute|cycleTests|startNextBatch_|safeRunTest_|invokeFunction_?))/);1991if (extraFrameIndex < 0) {1992break;1993}19941995var causedByIndex = stack.indexOf('Caused by:', extraFrameIndex);1996index = causedByIndex < 0 ? stack.length : causedByIndex;199719981999stack = stack.substring(0, extraFrameIndex + 1) + stack.substring(index);2000}20012002return stack;2003};200420052006/**2007* A class representing a single test function.2008* @param {string} name The test name.2009* @param {?function()} ref Reference to the test function or test object.2010* @param {?Object=} scope Optional scope that the test function should be2011* called in.2012* @param {!Array<?>=} objChain A chain of objects used to populate setUps2013* and tearDowns.2014* @constructor2015*/2016goog.testing.TestCase.Test = function(name, ref, scope, objChain) {2017'use strict';2018/**2019* The name of the test.2020* @type {string}2021*/2022this.name = name;20232024/**2025* TODO(user): Rename this to something more clear.2026* Reference to the test function.2027* @type {function()}2028*/2029this.ref = ref || function() {};20302031/**2032* Scope that the test function should be called in.2033* @type {?Object}2034*/2035this.scope = scope || null;20362037/**2038* @type {!Array<function()>}2039*/2040this.setUps = [];20412042/**2043* @type {!Array<function()>}2044*/2045this.tearDowns = [];20462047/**2048* @type {!Array<?>}2049*/2050this.objChain = objChain || [];20512052if (objChain) {2053for (var i = 0; i < objChain.length; i++) {2054if (typeof objChain[i].setUp === 'function') {2055this.setUps.push(goog.bind(objChain[i].setUp, objChain[i]));2056}2057if (typeof objChain[i].tearDown === 'function') {2058this.tearDowns.push(goog.bind(objChain[i].tearDown, objChain[i]));2059}2060}2061this.tearDowns.reverse();2062}20632064/**2065* Timestamp just before the test begins execution.2066* @type {number}2067* @private2068*/2069this.startTime_;20702071/**2072* Timestamp just after the test ends execution.2073* @type {number}2074* @private2075*/2076this.stoppedTime_;20772078/** @package {boolean|undefined} */2079this.waiting;2080};20812082/**2083* Executes the test function.2084* @package2085*/2086goog.testing.TestCase.Test.prototype.execute = function() {2087'use strict';2088this.ref.call(this.scope);2089};20902091/**2092* Sets the start time2093*/2094goog.testing.TestCase.Test.prototype.started = function() {2095'use strict';2096this.startTime_ = goog.testing.TestCase.now();2097};20982099/**2100* Sets the stop time2101*/2102goog.testing.TestCase.Test.prototype.stopped = function() {2103'use strict';2104this.stoppedTime_ = goog.testing.TestCase.now();2105};21062107/**2108* Returns the runtime for this test function in milliseconds.2109* @return {number}2110*/2111goog.testing.TestCase.Test.prototype.getElapsedTime = function() {2112'use strict';2113// Round the elapsed time to the closest multiple of 0.1ms (the resolution of2114// performance.now()) to avoid noise due to floating point rounding errors2115// when it's printed.2116return Math.round((this.stoppedTime_ - this.startTime_) * 10) / 10;2117};21182119/**2120* A class for representing test results. A bag of public properties.2121* @param {goog.testing.TestCase} testCase The test case that owns this result.2122* @constructor2123* @final2124*/2125goog.testing.TestCase.Result = function(testCase) {2126'use strict';2127/**2128* The test case that owns this result.2129* @type {goog.testing.TestCase}2130* @private2131*/2132this.testCase_ = testCase;21332134/**2135* Total number of tests that should have been run.2136* @type {number}2137*/2138this.totalCount = 0;21392140/**2141* Total number of tests that were actually run.2142* @type {number}2143*/2144this.runCount = 0;21452146/**2147* Number of successful tests.2148* @type {number}2149*/2150this.successCount = 0;21512152/**2153* Number of tests skipped due to nested shouldRunTests.2154* @type {number}2155*/2156this.skipCount = 0;21572158/**2159* The amount of time the tests took to run.2160* @type {number}2161*/2162this.runTime = 0;21632164/**2165* The number of files loaded to run this test.2166* @type {number}2167*/2168this.numFilesLoaded = 0;21692170/**2171* Whether all tests were suppressed from a top-level shouldRunTests().2172* @type {boolean}2173*/2174this.testSuppressed = false;21752176/**2177* Which tests were suppressed by shouldRunTests() returning false.2178* @type {!Array<string>}2179*/2180this.suppressedTests = [];21812182/**2183* Test results for each test that was run. The test name is always added2184* as the key in the map, and the array of strings is an optional list2185* of failure messages. If the array is empty, the test passed. Otherwise,2186* the test failed.2187* @type {!Object<string, !Array<goog.testing.TestCase.Error>>}2188*/2189this.resultsByName = {};21902191/**2192* Errors encountered while running the test.2193* @type {!Array<goog.testing.TestCase.Error>}2194*/2195this.errors = [];21962197/**2198* Messages to show the user after running the test.2199* @type {!Array<string>}2200*/2201this.messages = [];22022203/**2204* Whether the tests have completed.2205* @type {boolean}2206*/2207this.complete = false;2208};220922102211/**2212* @return {boolean} Whether the test was successful.2213*/2214goog.testing.TestCase.Result.prototype.isSuccess = function() {2215'use strict';2216return this.complete && this.errors.length == 0;2217};221822192220/**2221* @return {string} A summary of the tests, including total number of tests that2222* passed, failed, and the time taken.2223*/2224goog.testing.TestCase.Result.prototype.getSummary = function() {2225'use strict';2226var summary = this.runCount + ' of ' + this.totalCount + ' tests run in ' +2227Math.round(this.runTime) + ' ms.\n';2228if (this.testSuppressed) {2229summary += 'Tests not run because shouldRunTests() returned false.';2230} else {2231var failures = this.totalCount - this.successCount - this.skipCount;2232var suppressionMessage = '';22332234if (this.skipCount) {2235suppressionMessage +=2236', ' + this.skipCount + ' skipped by shouldRunTests()';2237}22382239var countOfRunTests = this.testCase_.getActuallyRunCount();2240if (countOfRunTests) {2241failures = countOfRunTests - this.successCount - this.skipCount;2242suppressionMessage += ', ' + (this.totalCount - countOfRunTests) +2243' suppressed by querystring';2244}2245summary += this.successCount + ' passed, ' + failures + ' failed' +2246suppressionMessage + '.\n' + Math.round(this.runTime / this.runCount) +2247' ms/test. ' + this.numFilesLoaded + ' files loaded.';2248}22492250return summary;2251};225222532254/**2255* @param {function(goog.testing.TestCase.Test, !Array<string>)} testDone2256*/2257goog.testing.TestCase.prototype.setTestDoneCallback = function(testDone) {2258'use strict';2259this.testDone_ = testDone;2260};226122622263/**2264* @param {goog.testing.TestCase.Test} test2265* @param {!Array<string>} errMsgs2266* @private2267*/2268goog.testing.TestCase.prototype.doTestDone_ = function(test, errMsgs) {2269'use strict';2270test.stopped();2271this.testDone_(test, errMsgs);2272};22732274/**2275* Initializes the TestCase.2276* @param {goog.testing.TestCase} testCase The test case to install.2277* @param {function(goog.testing.TestCase.Test, Array<string>)=} opt_testDone2278* Called when each test completes.2279*/2280goog.testing.TestCase.initializeTestCase = function(testCase, opt_testDone) {2281'use strict';2282if (opt_testDone) {2283testCase.setTestDoneCallback(opt_testDone);2284}22852286if (testCase.shouldAutoDiscoverTests_) {2287testCase.autoDiscoverTests();2288} else {2289// Make sure the tests are still ordered based on provided order.2290testCase.orderTests_();2291}22922293if (goog.global.location) {2294var href = goog.global.location.href;2295testCase.setTestsToRun(goog.testing.TestCase.parseRunTests_(href));2296}2297goog.testing.TestCase.activeTestCase_ = testCase;2298};229923002301/**2302* Initializes the given test case with the global test runner 'G_testRunner'.2303* @param {goog.testing.TestCase} testCase The test case to install.2304* @param {function(goog.testing.TestCase.Test, Array<string>)=} opt_testDone2305* Called when each test completes.2306*/2307goog.testing.TestCase.initializeTestRunner = function(testCase, opt_testDone) {2308'use strict';2309goog.testing.TestCase.initializeTestCase(testCase, opt_testDone);23102311var gTestRunner = goog.global['G_testRunner'];2312if (gTestRunner) {2313gTestRunner['initialize'](testCase);2314} else {2315throw new Error(2316'G_testRunner is undefined. Please ensure goog.testing.jsunit' +2317' is included.');2318}2319};232023212322/**2323* Parses URL query parameters for the 'runTests' parameter.2324* @param {string} href The current URL.2325* @return {Object<string, boolean>} A set of test names or test indices to be2326* run by the test runner.2327* @private2328*/2329goog.testing.TestCase.parseRunTests_ = function(href) {2330'use strict';2331const queryParamIndex = href.indexOf('?');2332if (queryParamIndex < 0) {2333return null;2334}23352336const nonOriginParts = href.slice(queryParamIndex);23372338// Use a "fake" origin because tests may load using protocols that goog.url2339// doesn't support2340const searchParams = goog.url.getSearchParams(2341goog.url.resolveUrl('https://google.com' + nonOriginParts));23422343let runTestsString = null;2344for (const [key, value] of searchParams) {2345if (key.toLowerCase() === 'runtests') {2346runTestsString = value;2347}2348}23492350if (!runTestsString) {2351return null;2352}23532354const testsToRun = {};2355const arr = runTestsString.split(',');2356for (let i = 0, len = arr.length; i < len; i++) {2357try {2358// `TestRunner` double encodes commas in test names so we decode back here2359testsToRun[arr[i].replace(/%2C/g, ',')] = true;2360} catch (e) {2361return null;2362}2363}23642365return testsToRun;2366};236723682369/**2370* Wraps provided promise and returns a new promise which will be rejected2371* if the original promise does not settle within the given timeout.2372* @param {!IThenable<T>} promise2373* @param {number} timeoutInMs Number of milliseconds to wait for the promise to2374* settle before failing it with a timeout error.2375* @param {string} errorMsg Error message to use if the promise times out.2376* @return {!IThenable<T>} A promise that will settle with the original2377promise unless the timeout is exceeded.2378* error.2379* @template T2380* @private2381*/2382goog.testing.TestCase.prototype.rejectIfPromiseTimesOut_ = function(2383promise, timeoutInMs, errorMsg) {2384'use strict';2385const start = this.now();2386/**2387* @param {function(?)} resolve2388* @param {function(*)} reject2389*/2390const resolver = (resolve, reject) => {2391'use strict';2392const timeoutId = this.timeout(() => {2393'use strict';2394const elapsed = this.now() - start;2395reject(new Error(`${errorMsg}\nElapsed time: ${elapsed} ms.`));2396}, timeoutInMs);2397const clearTimeout = () => {2398this.clearTimeout(timeoutId);2399};2400promise.then(clearTimeout, clearTimeout);2401promise.then(resolve, reject);2402};2403if (this.useNativePromise_) {2404return new Promise(resolver);2405}2406return new goog.Promise(resolver);2407};2408240924102411/**2412* A class representing an error thrown by the test2413* @param {string} source The name of the test which threw the error.2414* @param {string} message The error message.2415* @param {string=} opt_stack A string showing the execution stack.2416* @constructor2417* @final2418*/2419goog.testing.TestCase.Error = function(source, message, opt_stack) {2420'use strict';2421/**2422* The name of the test which threw the error.2423* @type {string}2424*/2425this.source = source;24262427/**2428* Reference to the test function.2429* @type {string}2430*/2431this.message = message;24322433/**2434* The stack.2435* @type {?string}2436*/2437this.stack = null;24382439if (opt_stack) {2440this.stack = opt_stack;2441} else {2442// Attempt to capture a stack trace.2443if (Error.captureStackTrace) {2444// See https://code.google.com/p/v8-wiki/wiki/JavaScriptStackTraceApi2445Error.captureStackTrace(this, goog.testing.TestCase.Error);2446} else {2447var stack = new Error().stack;2448if (stack) {2449this.stack = stack;2450}2451}2452}2453};245424552456/**2457* Call this from setUpPage() to prevent any Content Security Policy violations2458* that may have occurred during page load from being reported as errors .2459*/2460goog.testing.TestCase.prototype.ignoreStartupCspViolations = function() {2461this.ignoreStartupCspViolations_ = true;2462};246324642465/**2466* Toggles recording of Content Security Policy violations. Call this with false2467* during tests, setUpPage, setUp, and tearDown functions to prevent CSP2468* violations occurring while the function is executing from being reported as2469* errors. Reporting will be reset upon execution of the next test function.2470*2471* @param {boolean} enable2472*/2473goog.testing.TestCase.prototype.observeCspViolations = function(enable) {2474this.cspViolationObserver_.setEnabled(enable);2475};247624772478/**2479* Returns a string representing the error object.2480* @return {string} A string representation of the error.2481* @override2482*/2483goog.testing.TestCase.Error.prototype.toString = function() {2484'use strict';2485return 'ERROR in ' + this.source + '\n' + this.message +2486(this.stack && this.stack !== 'Not available' ? '\n' + this.stack : '');2487};24882489/**2490* Returns an object representing the error suitable for JSON serialization.2491* @return {!goog.testing.TestCase.IResult} An object2492* representation of the error.2493* @private2494*/2495goog.testing.TestCase.Error.prototype.toObject_ = function() {2496'use strict';2497return {2498'source': this.source,2499'message': this.message,2500'stacktrace': this.stack || ''2501};2502};2503250425052506/**2507* @constructor2508* @param {function(): (?goog.testing.TestCase.Continuation_|undefined)} fn2509* @private2510*/2511goog.testing.TestCase.Continuation_ = function(fn) {2512'use strict';2513/** @private @const */2514this.fn_ = fn;2515};251625172518/** @param {?goog.testing.TestCase.Continuation_|undefined} continuation */2519goog.testing.TestCase.Continuation_.run = function(continuation) {2520'use strict';2521var fn = continuation && continuation.fn_;2522while (fn) {2523continuation = fn();2524fn = continuation && continuation.fn_;2525}2526};252725282529