# 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 at6//7//      http://www.apache.org/licenses/LICENSE-2.08//9// Unless required by applicable law or agreed to in writing, software10// 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 and13// limitations under the License.1415/**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 to19 * reduce the chance of masking missing dependencies.20 *21 * This file does not compile correctly with --collapse_properties. Use22 * --property_renaming=ALL_UNQUOTED instead.23 *24 */2526goog.provide('goog.testing.TestCase');27goog.provide('goog.testing.TestCase.Error');28goog.provide('goog.testing.TestCase.Order');29goog.provide('goog.testing.TestCase.Result');30goog.provide('goog.testing.TestCase.Test');3132goog.require('goog.object');33goog.require('goog.testing.asserts');34goog.require('goog.testing.stacktrace');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 *51 * Use {@link #autoDiscoverLifecycle} and {@link #autoDiscoverTests}52 *53 * @param {string=} opt_name The name of the test case, defaults to54 *     'Untitled Test Case'.55 * @constructor56 */57goog.testing.TestCase = function(opt_name) {58  /**59   * A name for the test case.60   * @type {string}61   * @private62   */63  this.name_ = opt_name || 'Untitled Test Case';6465  /**66   * Array of test functions that can be executed.67   * @type {!Array.<!goog.testing.TestCase.Test>}68   * @private69   */70  this.tests_ = [];7172  /**73   * Set of test names and/or indices to execute, or null if all tests should74   * be executed.75   *76   * Indices are included to allow automation tools to run a subset of the77   * tests without knowing the exact contents of the test file.78   *79   * Indices should only be used with SORTED ordering.80   *81   * Example valid values:82   * <ul>83   * <li>[testName]84   * <li>[testName1, testName2]85   * <li>[2] - will run the 3rd test in the order specified86   * <li>[1,3,5]87   * <li>[testName1, testName2, 3, 5] - will work88   * <ul>89   * @type {Object}90   * @private91   */92  this.testsToRun_ = null;9394  var search = '';95  if (goog.global.location) {96    search = goog.global.location.search;97  }9899  // Parse the 'runTests' query parameter into a set of test names and/or100  // test indices.101  var runTestsMatch = search.match(/(?:\?|&)runTests=([^?&]+)/i);102  if (runTestsMatch) {103    this.testsToRun_ = {};104    var arr = runTestsMatch[1].split(',');105    for (var i = 0, len = arr.length; i < len; i++) {106      this.testsToRun_[arr[i]] = 1;107    }108  }109110  // Checks the URL for a valid order param.111  var orderMatch = search.match(/(?:\?|&)order=(natural|random|sorted)/i);112  if (orderMatch) {113    this.order = orderMatch[1];114  }115116  /**117   * Object used to encapsulate the test results.118   * @type {goog.testing.TestCase.Result}119   * @protected120   * @suppress {underscore|visibility}121   */122  this.result_ = new goog.testing.TestCase.Result(this);123124  // This silences a compiler warning from the legacy property check, which125  // is deprecated. It idly writes to testRunner properties that are used126  // in this file.127  var testRunnerMethods = {isFinished: true, hasErrors: true};128};129130131/**132 * The order to run the auto-discovered tests.133 * @enum {string}134 */135goog.testing.TestCase.Order = {136  /**137   * This is browser dependent and known to be different in FF and Safari138   * compared to others.139   */140  NATURAL: 'natural',141142  /** Random order. */143  RANDOM: 'random',144145  /** Sorted based on the name. */146  SORTED: 'sorted'147};148149150/**151 * @return {string} The name of the test.152 */153goog.testing.TestCase.prototype.getName = function() {154  return this.name_;155};156157158/**159 * The maximum amount of time that the test can run before we force it to be160 * async.  This prevents the test runner from blocking the browser and161 * potentially hurting the Selenium test harness.162 * @type {number}163 */164goog.testing.TestCase.maxRunTime = 200;165166167/**168 * The order to run the auto-discovered tests in.169 * @type {string}170 */171goog.testing.TestCase.prototype.order = goog.testing.TestCase.Order.SORTED;172173174/**175 * Save a reference to {@code window.setTimeout}, so any code that overrides the176 * default behavior (the MockClock, for example) doesn't affect our runner.177 * @type {function((Function|string), number, *=): number}178 * @private179 */180goog.testing.TestCase.protectedSetTimeout_ = goog.global.setTimeout;181182183/**184 * Save a reference to {@code window.clearTimeout}, so any code that overrides185 * the default behavior (e.g. MockClock) doesn't affect our runner.186 * @type {function((null|number|undefined)): void}187 * @private188 */189goog.testing.TestCase.protectedClearTimeout_ = goog.global.clearTimeout;190191192/**193 * Save a reference to {@code window.Date}, so any code that overrides194 * the default behavior doesn't affect our runner.195 * @type {function(new: Date)}196 * @private197 */198goog.testing.TestCase.protectedDate_ = Date;199200201/**202 * Saved string referencing goog.global.setTimeout's string serialization.  IE203 * sometimes fails to uphold equality for setTimeout, but the string version204 * stays the same.205 * @type {string}206 * @private207 */208goog.testing.TestCase.setTimeoutAsString_ = String(goog.global.setTimeout);209210211/**212 * TODO(user) replace this with prototype.currentTest.213 * Name of the current test that is running, or null if none is running.214 * @type {?string}215 */216goog.testing.TestCase.currentTestName = null;217218219/**220 * Avoid a dependency on goog.userAgent and keep our own reference of whether221 * the browser is IE.222 * @type {boolean}223 */224goog.testing.TestCase.IS_IE = typeof opera == 'undefined' &&225    !!goog.global.navigator &&226    goog.global.navigator.userAgent.indexOf('MSIE') != -1;227228229/**230 * Exception object that was detected before a test runs.231 * @type {*}232 * @protected233 */234goog.testing.TestCase.prototype.exceptionBeforeTest;235236237/**238 * Whether the test case has ever tried to execute.239 * @type {boolean}240 */241goog.testing.TestCase.prototype.started = false;242243244/**245 * Whether the test case is running.246 * @type {boolean}247 */248goog.testing.TestCase.prototype.running = false;249250251/**252 * Timestamp for when the test was started.253 * @type {number}254 * @private255 */256goog.testing.TestCase.prototype.startTime_ = 0;257258259/**260 * Time since the last batch of tests was started, if batchTime exceeds261 * {@link #maxRunTime} a timeout will be used to stop the tests blocking the262 * browser and a new batch will be started.263 * @type {number}264 * @private265 */266goog.testing.TestCase.prototype.batchTime_ = 0;267268269/**270 * Pointer to the current test.271 * @type {number}272 * @private273 */274goog.testing.TestCase.prototype.currentTestPointer_ = 0;275276277/**278 * Optional callback that will be executed when the test has finalized.279 * @type {Function}280 * @private281 */282goog.testing.TestCase.prototype.onCompleteCallback_ = null;283284285/**286 * Adds a new test to the test case.287 * @param {goog.testing.TestCase.Test} test The test to add.288 */289goog.testing.TestCase.prototype.add = function(test) {290  if (this.started) {291    throw Error('Tests cannot be added after execute() has been called. ' +292                'Test: ' + test.name);293  }294295  this.tests_.push(test);296};297298299/**300 * Creates and adds a new test.301 *302 * Convenience function to make syntax less awkward when not using automatic303 * test discovery.304 *305 * @param {string} name The test name.306 * @param {!Function} ref Reference to the test function.307 * @param {!Object=} opt_scope Optional scope that the test function should be308 *     called in.309 */310goog.testing.TestCase.prototype.addNewTest = function(name, ref, opt_scope) {311  var test = new goog.testing.TestCase.Test(name, ref, opt_scope || this);312  this.add(test);313};314315316/**317 * Sets the tests.318 * @param {!Array.<goog.testing.TestCase.Test>} tests A new test array.319 * @protected320 */321goog.testing.TestCase.prototype.setTests = function(tests) {322  this.tests_ = tests;323};324325326/**327 * Gets the tests.328 * @return {!Array.<goog.testing.TestCase.Test>} The test array.329 */330goog.testing.TestCase.prototype.getTests = function() {331  return this.tests_;332};333334335/**336 * Returns the number of tests contained in the test case.337 * @return {number} The number of tests.338 */339goog.testing.TestCase.prototype.getCount = function() {340  return this.tests_.length;341};342343344/**345 * Returns the number of tests actually run in the test case, i.e. subtracting346 * any which are skipped.347 * @return {number} The number of un-ignored tests.348 */349goog.testing.TestCase.prototype.getActuallyRunCount = function() {350  return this.testsToRun_ ? goog.object.getCount(this.testsToRun_) : 0;351};352353354/**355 * Returns the current test and increments the pointer.356 * @return {goog.testing.TestCase.Test} The current test case.357 */358goog.testing.TestCase.prototype.next = function() {359  var test;360  while ((test = this.tests_[this.currentTestPointer_++])) {361    if (!this.testsToRun_ || this.testsToRun_[test.name] ||362        this.testsToRun_[this.currentTestPointer_ - 1]) {363      return test;364    }365  }366  return null;367};368369370/**371 * Resets the test case pointer, so that next returns the first test.372 */373goog.testing.TestCase.prototype.reset = function() {374  this.currentTestPointer_ = 0;375  this.result_ = new goog.testing.TestCase.Result(this);376};377378379/**380 * Sets the callback function that should be executed when the tests have381 * completed.382 * @param {Function} fn The callback function.383 */384goog.testing.TestCase.prototype.setCompletedCallback = function(fn) {385  this.onCompleteCallback_ = fn;386};387388389/**390 * Can be overridden in test classes to indicate whether the tests in a case391 * should be run in that particular situation.  For example, this could be used392 * to stop tests running in a particular browser, where browser support for393 * the class under test was absent.394 * @return {boolean} Whether any of the tests in the case should be run.395 */396goog.testing.TestCase.prototype.shouldRunTests = function() {397  return true;398};399400401/**402 * Executes each of the tests.403 */404goog.testing.TestCase.prototype.execute = function() {405  this.started = true;406  this.reset();407  this.startTime_ = this.now();408  this.running = true;409  this.result_.totalCount = this.getCount();410411  if (!this.shouldRunTests()) {412    this.log('shouldRunTests() returned false, skipping these tests.');413    this.result_.testSuppressed = true;414    this.finalize();415    return;416  }417418  this.log('Starting tests: ' + this.name_);419  this.cycleTests();420};421422423/**424 * Finalizes the test case, called when the tests have finished executing.425 */426goog.testing.TestCase.prototype.finalize = function() {427  this.saveMessage('Done');428429  this.tearDownPage();430431  var restoredSetTimeout =432      goog.testing.TestCase.protectedSetTimeout_ == goog.global.setTimeout &&433      goog.testing.TestCase.protectedClearTimeout_ == goog.global.clearTimeout;434  if (!restoredSetTimeout && goog.testing.TestCase.IS_IE &&435      String(goog.global.setTimeout) ==436          goog.testing.TestCase.setTimeoutAsString_) {437    // In strange cases, IE's value of setTimeout *appears* to change, but438    // the string representation stays stable.439    restoredSetTimeout = true;440  }441442  if (!restoredSetTimeout) {443    var message = 'ERROR: Test did not restore setTimeout and clearTimeout';444    this.saveMessage(message);445    var err = new goog.testing.TestCase.Error(this.name_, message);446    this.result_.errors.push(err);447  }448  goog.global.clearTimeout = goog.testing.TestCase.protectedClearTimeout_;449  goog.global.setTimeout = goog.testing.TestCase.protectedSetTimeout_;450  this.endTime_ = this.now();451  this.running = false;452  this.result_.runTime = this.endTime_ - this.startTime_;453  this.result_.numFilesLoaded = this.countNumFilesLoaded_();454  this.result_.complete = true;455456  this.log(this.result_.getSummary());457  if (this.result_.isSuccess()) {458    this.log('Tests complete');459  } else {460    this.log('Tests Failed');461  }462  if (this.onCompleteCallback_) {463    var fn = this.onCompleteCallback_;464    // Execute's the completed callback in the context of the global object.465    fn();466    this.onCompleteCallback_ = null;467  }468};469470471/**472 * Saves a message to the result set.473 * @param {string} message The message to save.474 */475goog.testing.TestCase.prototype.saveMessage = function(message) {476  this.result_.messages.push(this.getTimeStamp_() + '  ' + message);477};478479480/**481 * @return {boolean} Whether the test case is running inside the multi test482 *     runner.483 */484goog.testing.TestCase.prototype.isInsideMultiTestRunner = function() {485  var top = goog.global['top'];486  return top && typeof top['_allTests'] != 'undefined';487};488489490/**491 * Logs an object to the console, if available.492 * @param {*} val The value to log. Will be ToString'd.493 */494goog.testing.TestCase.prototype.log = function(val) {495  if (!this.isInsideMultiTestRunner() && goog.global.console) {496    if (typeof val == 'string') {497      val = this.getTimeStamp_() + ' : ' + val;498    }499    if (val instanceof Error && val.stack) {500      // Chrome does console.log asynchronously in a different process501      // (http://code.google.com/p/chromium/issues/detail?id=50316).502      // This is an acute problem for Errors, which almost never survive.503      // Grab references to the immutable strings so they survive.504      goog.global.console.log(val, val.message, val.stack);505      // TODO(gboyer): Consider for Chrome cloning any object if we can ensure506      // there are no circular references.507    } else {508      goog.global.console.log(val);509    }510  }511};512513514/**515 * @return {boolean} Whether the test was a success.516 */517goog.testing.TestCase.prototype.isSuccess = function() {518  return !!this.result_ && this.result_.isSuccess();519};520521522/**523 * Returns a string detailing the results from the test.524 * @param {boolean=} opt_verbose If true results will include data about all525 *     tests, not just what failed.526 * @return {string} The results from the test.527 */528goog.testing.TestCase.prototype.getReport = function(opt_verbose) {529  var rv = [];530531  if (this.running) {532    rv.push(this.name_ + ' [RUNNING]');533  } else {534    var label = this.result_.isSuccess() ? 'PASSED' : 'FAILED';535    rv.push(this.name_ + ' [' + label + ']');536  }537538  if (goog.global.location) {539    rv.push(this.trimPath_(goog.global.location.href));540  }541542  rv.push(this.result_.getSummary());543544  if (opt_verbose) {545    rv.push('.', this.result_.messages.join('\n'));546  } else if (!this.result_.isSuccess()) {547    rv.push(this.result_.errors.join('\n'));548  }549550  rv.push(' ');551552  return rv.join('\n');553};554555556/**557 * Returns the amount of time it took for the test to run.558 * @return {number} The run time, in milliseconds.559 */560goog.testing.TestCase.prototype.getRunTime = function() {561  return this.result_.runTime;562};563564565/**566 * Returns the number of script files that were loaded in order to run the test.567 * @return {number} The number of script files.568 */569goog.testing.TestCase.prototype.getNumFilesLoaded = function() {570  return this.result_.numFilesLoaded;571};572573574/**575 * Returns the test results object: a map from test names to a list of test576 * failures (if any exist).577 * @return {!Object.<string, !Array.<string>>} Tests results object.578 */579goog.testing.TestCase.prototype.getTestResults = function() {580  return this.result_.resultsByName;581};582583584/**585 * Executes each of the tests.586 * Overridable by the individual test case.  This allows test cases to defer587 * when the test is actually started.  If overridden, finalize must be called588 * by the test to indicate it has finished.589 */590goog.testing.TestCase.prototype.runTests = function() {591  try {592    this.setUpPage();593  } catch (e) {594    this.exceptionBeforeTest = e;595  }596  this.execute();597};598599600/**601 * Reorders the tests depending on the {@code order} field.602 * @param {Array.<goog.testing.TestCase.Test>} tests An array of tests to603 *     reorder.604 * @private605 */606goog.testing.TestCase.prototype.orderTests_ = function(tests) {607  switch (this.order) {608    case goog.testing.TestCase.Order.RANDOM:609      // Fisher-Yates shuffle610      var i = tests.length;611      while (i > 1) {612        // goog.math.randomInt is inlined to reduce dependencies.613        var j = Math.floor(Math.random() * i); // exclusive614        i--;615        var tmp = tests[i];616        tests[i] = tests[j];617        tests[j] = tmp;618      }619      break;620621    case goog.testing.TestCase.Order.SORTED:622      tests.sort(function(t1, t2) {623        if (t1.name == t2.name) {624          return 0;625        }626        return t1.name < t2.name ? -1 : 1;627      });628      break;629630      // Do nothing for NATURAL.631  }632};633634635/**636 * Gets list of objects that potentially contain test cases. For IE 8 and below,637 * this is the global "this" (for properties set directly on the global this or638 * window) and the RuntimeObject (for global variables and functions). For all639 * other browsers, the array simply contains the global this.640 *641 * @param {string=} opt_prefix An optional prefix. If specified, only get things642 *     under this prefix. Note that the prefix is only honored in IE, since it643 *     supports the RuntimeObject:644 *     http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx645 *     TODO: Remove this option.646 * @return {!Array.<!Object>} A list of objects that should be inspected.647 */648goog.testing.TestCase.prototype.getGlobals = function(opt_prefix) {649  return goog.testing.TestCase.getGlobals(opt_prefix);650};651652653/**654 * Gets list of objects that potentially contain test cases. For IE 8 and below,655 * this is the global "this" (for properties set directly on the global this or656 * window) and the RuntimeObject (for global variables and functions). For all657 * other browsers, the array simply contains the global this.658 *659 * @param {string=} opt_prefix An optional prefix. If specified, only get things660 *     under this prefix. Note that the prefix is only honored in IE, since it661 *     supports the RuntimeObject:662 *     http://msdn.microsoft.com/en-us/library/ff521039%28VS.85%29.aspx663 *     TODO: Remove this option.664 * @return {!Array.<!Object>} A list of objects that should be inspected.665 */666goog.testing.TestCase.getGlobals = function(opt_prefix) {667  // Look in the global scope for most browsers, on IE we use the little known668  // RuntimeObject which holds references to all globals. We reference this669  // via goog.global so that there isn't an aliasing that throws an exception670  // in Firefox.671  return typeof goog.global['RuntimeObject'] != 'undefined' ?672      [goog.global['RuntimeObject']((opt_prefix || '') + '*'), goog.global] :673      [goog.global];674};675676677/**678 * Gets called before any tests are executed.  Can be overridden to set up the679 * environment for the whole test case.680 */681goog.testing.TestCase.prototype.setUpPage = function() {};682683684/**685 * Gets called after all tests have been executed.  Can be overridden to tear686 * down the entire test case.687 */688goog.testing.TestCase.prototype.tearDownPage = function() {};689690691/**692 * Gets called before every goog.testing.TestCase.Test is been executed. Can be693 * overridden to add set up functionality to each test.694 */695goog.testing.TestCase.prototype.setUp = function() {};696697698/**699 * Gets called after every goog.testing.TestCase.Test has been executed. Can be700 * overriden to add tear down functionality to each test.701 */702goog.testing.TestCase.prototype.tearDown = function() {};703704705/**706 * @return {string} The function name prefix used to auto-discover tests.707 * @protected708 */709goog.testing.TestCase.prototype.getAutoDiscoveryPrefix = function() {710  return 'test';711};712713714/**715 * @return {number} Time since the last batch of tests was started.716 * @protected717 */718goog.testing.TestCase.prototype.getBatchTime = function() {719  return this.batchTime_;720};721722723/**724 * @param {number} batchTime Time since the last batch of tests was started.725 * @protected726 */727goog.testing.TestCase.prototype.setBatchTime = function(batchTime) {728  this.batchTime_ = batchTime;729};730731732/**733 * Creates a {@code goog.testing.TestCase.Test} from an auto-discovered734 *     function.735 * @param {string} name The name of the function.736 * @param {function() : void} ref The auto-discovered function.737 * @return {!goog.testing.TestCase.Test} The newly created test.738 * @protected739 */740goog.testing.TestCase.prototype.createTestFromAutoDiscoveredFunction =741    function(name, ref) {742  return new goog.testing.TestCase.Test(name, ref, goog.global);743};744745746/**747 * Adds any functions defined in the global scope that correspond to748 * lifecycle events for the test case. Overrides setUp, tearDown, setUpPage,749 * tearDownPage and runTests if they are defined.750 */751goog.testing.TestCase.prototype.autoDiscoverLifecycle = function() {752  if (goog.global['setUp']) {753    this.setUp = goog.bind(goog.global['setUp'], goog.global);754  }755  if (goog.global['tearDown']) {756    this.tearDown = goog.bind(goog.global['tearDown'], goog.global);757  }758  if (goog.global['setUpPage']) {759    this.setUpPage = goog.bind(goog.global['setUpPage'], goog.global);760  }761  if (goog.global['tearDownPage']) {762    this.tearDownPage = goog.bind(goog.global['tearDownPage'], goog.global);763  }764  if (goog.global['runTests']) {765    this.runTests = goog.bind(goog.global['runTests'], goog.global);766  }767  if (goog.global['shouldRunTests']) {768    this.shouldRunTests = goog.bind(goog.global['shouldRunTests'], goog.global);769  }770};771772773/**774 * Adds any functions defined in the global scope that are prefixed with "test"775 * to the test case.776 */777goog.testing.TestCase.prototype.autoDiscoverTests = function() {778  var prefix = this.getAutoDiscoveryPrefix();779  var testSources = this.getGlobals(prefix);780781  var foundTests = [];782783  for (var i = 0; i < testSources.length; i++) {784    var testSource = testSources[i];785    for (var name in testSource) {786      if ((new RegExp('^' + prefix)).test(name)) {787        var ref;788        try {789          ref = testSource[name];790        } catch (ex) {791          // NOTE(brenneman): When running tests from a file:// URL on Firefox792          // 3.5 for Windows, any reference to goog.global.sessionStorage raises793          // an "Operation is not supported" exception. Ignore any exceptions794          // raised by simply accessing global properties.795          ref = undefined;796        }797798        if (goog.isFunction(ref)) {799          foundTests.push(this.createTestFromAutoDiscoveredFunction(name, ref));800        }801      }802    }803  }804805  this.orderTests_(foundTests);806807  for (var i = 0; i < foundTests.length; i++) {808    this.add(foundTests[i]);809  }810811  this.log(this.getCount() + ' tests auto-discovered');812813  // TODO(user): Do this as a separate call. Unfortunately, a lot of projects814  // currently override autoDiscoverTests and expect lifecycle events to be815  // registered as a part of this call.816  this.autoDiscoverLifecycle();817};818819820/**821 * Checks to see if the test should be marked as failed before it is run.822 *823 * If there was an error in setUpPage, we treat that as a failure for all tests824 * and mark them all as having failed.825 *826 * @param {goog.testing.TestCase.Test} testCase The current test case.827 * @return {boolean} Whether the test was marked as failed.828 * @protected829 */830goog.testing.TestCase.prototype.maybeFailTestEarly = function(testCase) {831  if (this.exceptionBeforeTest) {832    // We just use the first error to report an error on a failed test.833    testCase.name = 'setUpPage for ' + testCase.name;834    this.doError(testCase, this.exceptionBeforeTest);835    return true;836  }837  return false;838};839840841/**842 * Cycles through the tests, breaking out using a setTimeout if the execution843 * time has execeeded {@link #maxRunTime}.844 */845goog.testing.TestCase.prototype.cycleTests = function() {846  this.saveMessage('Start');847  this.batchTime_ = this.now();848  var nextTest;849  while ((nextTest = this.next()) && this.running) {850    this.result_.runCount++;851    // Execute the test and handle the error, we execute all tests rather than852    // stopping after a single error.853    var cleanedUp = false;854    try {855      this.log('Running test: ' + nextTest.name);856857      if (this.maybeFailTestEarly(nextTest)) {858        cleanedUp = true;859      } else {860        goog.testing.TestCase.currentTestName = nextTest.name;861        this.setUp();862        nextTest.execute();863        this.tearDown();864        goog.testing.TestCase.currentTestName = null;865866        cleanedUp = true;867868        this.doSuccess(nextTest);869      }870    } catch (e) {871      this.doError(nextTest, e);872873      if (!cleanedUp) {874        try {875          this.tearDown();876        } catch (e2) {} // Fail silently if tearDown is throwing the errors.877      }878    }879880    // If the max run time is exceeded call this function again async so as not881    // to block the browser.882    if (this.currentTestPointer_ < this.tests_.length &&883        this.now() - this.batchTime_ > goog.testing.TestCase.maxRunTime) {884      this.saveMessage('Breaking async');885      this.timeout(goog.bind(this.cycleTests, this), 0);886      return;887    }888  }889  // Tests are done.890  this.finalize();891};892893894/**895 * Counts the number of files that were loaded for dependencies that are896 * required to run the test.897 * @return {number} The number of files loaded.898 * @private899 */900goog.testing.TestCase.prototype.countNumFilesLoaded_ = function() {901  var scripts = document.getElementsByTagName('script');902  var count = 0;903  for (var i = 0, n = scripts.length; i < n; i++) {904    if (scripts[i].src) {905      count++;906    }907  }908  return count;909};910911912/**913 * Calls a function after a delay, using the protected timeout.914 * @param {Function} fn The function to call.915 * @param {number} time Delay in milliseconds.916 * @return {number} The timeout id.917 * @protected918 */919goog.testing.TestCase.prototype.timeout = function(fn, time) {920  // NOTE: invoking protectedSetTimeout_ as a member of goog.testing.TestCase921  // would result in an Illegal Invocation error. The method must be executed922  // with the global context.923  var protectedSetTimeout = goog.testing.TestCase.protectedSetTimeout_;924  return protectedSetTimeout(fn, time);925};926927928/**929 * Clears a timeout created by {@code this.timeout()}.930 * @param {number} id A timeout id.931 * @protected932 */933goog.testing.TestCase.prototype.clearTimeout = function(id) {934  // NOTE: see execution note for protectedSetTimeout above.935  var protectedClearTimeout = goog.testing.TestCase.protectedClearTimeout_;936  protectedClearTimeout(id);937};938939940/**941 * @return {number} The current time in milliseconds, don't use goog.now as some942 *     tests override it.943 * @protected944 */945goog.testing.TestCase.prototype.now = function() {946  // Cannot use "new goog.testing.TestCase.protectedDate_()" due to b/8323223.947  var protectedDate = goog.testing.TestCase.protectedDate_;948  return new protectedDate().getTime();949};950951952/**953 * Returns the current time.954 * @return {string} HH:MM:SS.955 * @private956 */957goog.testing.TestCase.prototype.getTimeStamp_ = function() {958  // Cannot use "new goog.testing.TestCase.protectedDate_()" due to b/8323223.959  var protectedDate = goog.testing.TestCase.protectedDate_;960  var d = new protectedDate();961962  // Ensure millis are always 3-digits963  var millis = '00' + d.getMilliseconds();964  millis = millis.substr(millis.length - 3);965966  return this.pad_(d.getHours()) + ':' + this.pad_(d.getMinutes()) + ':' +967         this.pad_(d.getSeconds()) + '.' + millis;968};969970971/**972 * Pads a number to make it have a leading zero if it's less than 10.973 * @param {number} number The number to pad.974 * @return {string} The resulting string.975 * @private976 */977goog.testing.TestCase.prototype.pad_ = function(number) {978  return number < 10 ? '0' + number : String(number);979};980981982/**983 * Trims a path to be only that after google3.984 * @param {string} path The path to trim.985 * @return {string} The resulting string.986 * @private987 */988goog.testing.TestCase.prototype.trimPath_ = function(path) {989  return path.substring(path.indexOf('google3') + 8);990};991992993/**994 * Handles a test that passed.995 * @param {goog.testing.TestCase.Test} test The test that passed.996 * @protected997 */998goog.testing.TestCase.prototype.doSuccess = function(test) {999  this.result_.successCount++;1000  // An empty list of error messages indicates that the test passed.1001  // If we already have a failure for this test, do not set to empty list.1002  if (!(test.name in this.result_.resultsByName)) {1003    this.result_.resultsByName[test.name] = [];1004  }1005  var message = test.name + ' : PASSED';1006  this.saveMessage(message);1007  this.log(message);1008};100910101011/**1012 * Handles a test that failed.1013 * @param {goog.testing.TestCase.Test} test The test that failed.1014 * @param {*=} opt_e The exception object associated with the1015 *     failure or a string.1016 * @protected1017 */1018goog.testing.TestCase.prototype.doError = function(test, opt_e) {1019  var message = test.name + ' : FAILED';1020  this.log(message);1021  this.saveMessage(message);1022  var err = this.logError(test.name, opt_e);1023  this.result_.errors.push(err);1024  if (test.name in this.result_.resultsByName) {1025    this.result_.resultsByName[test.name].push(err.toString());1026  } else {1027    this.result_.resultsByName[test.name] = [err.toString()];1028  }1029};103010311032/**1033 * @param {string} name Failed test name.1034 * @param {*=} opt_e The exception object associated with the1035 *     failure or a string.1036 * @return {!goog.testing.TestCase.Error} Error object.1037 */1038goog.testing.TestCase.prototype.logError = function(name, opt_e) {1039  var errMsg = null;1040  var stack = null;1041  if (opt_e) {1042    this.log(opt_e);1043    if (goog.isString(opt_e)) {1044      errMsg = opt_e;1045    } else {1046      errMsg = opt_e.message || opt_e.description || opt_e.toString();1047      stack = opt_e.stack ? goog.testing.stacktrace.canonicalize(opt_e.stack) :1048          opt_e['stackTrace'];1049    }1050  } else {1051    errMsg = 'An unknown error occurred';1052  }1053  var err = new goog.testing.TestCase.Error(name, errMsg, stack);10541055  // Avoid double logging.1056  if (!opt_e || !opt_e['isJsUnitException'] ||1057      !opt_e['loggedJsUnitException']) {1058    this.saveMessage(err.toString());1059  }1060  if (opt_e && opt_e['isJsUnitException']) {1061    opt_e['loggedJsUnitException'] = true;1062  }10631064  return err;1065};1066106710681069/**1070 * A class representing a single test function.1071 * @param {string} name The test name.1072 * @param {Function} ref Reference to the test function.1073 * @param {Object=} opt_scope Optional scope that the test function should be1074 *     called in.1075 * @constructor1076 */1077goog.testing.TestCase.Test = function(name, ref, opt_scope) {1078  /**1079   * The name of the test.1080   * @type {string}1081   */1082  this.name = name;10831084  /**1085   * Reference to the test function.1086   * @type {Function}1087   */1088  this.ref = ref;10891090  /**1091   * Scope that the test function should be called in.1092   * @type {Object}1093   */1094  this.scope = opt_scope || null;1095};109610971098/**1099 * Executes the test function.1100 */1101goog.testing.TestCase.Test.prototype.execute = function() {1102  this.ref.call(this.scope);1103};1104110511061107/**1108 * A class for representing test results.  A bag of public properties.1109 * @param {goog.testing.TestCase} testCase The test case that owns this result.1110 * @constructor1111 * @final1112 */1113goog.testing.TestCase.Result = function(testCase) {1114  /**1115   * The test case that owns this result.1116   * @type {goog.testing.TestCase}1117   * @private1118   */1119  this.testCase_ = testCase;11201121  /**1122   * Total number of tests that should have been run.1123   * @type {number}1124   */1125  this.totalCount = 0;11261127  /**1128   * Total number of tests that were actually run.1129   * @type {number}1130   */1131  this.runCount = 0;11321133  /**1134   * Number of successful tests.1135   * @type {number}1136   */1137  this.successCount = 0;11381139  /**1140   * The amount of time the tests took to run.1141   * @type {number}1142   */1143  this.runTime = 0;11441145  /**1146   * The number of files loaded to run this test.1147   * @type {number}1148   */1149  this.numFilesLoaded = 0;11501151  /**1152   * Whether this test case was suppressed by shouldRunTests() returning false.1153   * @type {boolean}1154   */1155  this.testSuppressed = false;11561157  /**1158   * Test results for each test that was run. The test name is always added1159   * as the key in the map, and the array of strings is an optional list1160   * of failure messages. If the array is empty, the test passed. Otherwise,1161   * the test failed.1162   * @type {!Object.<string, !Array.<string>>}1163   */1164  this.resultsByName = {};11651166  /**1167   * Errors encountered while running the test.1168   * @type {!Array.<goog.testing.TestCase.Error>}1169   */1170  this.errors = [];11711172  /**1173   * Messages to show the user after running the test.1174   * @type {!Array.<string>}1175   */1176  this.messages = [];11771178  /**1179   * Whether the tests have completed.1180   * @type {boolean}1181   */1182  this.complete = false;1183};118411851186/**1187 * @return {boolean} Whether the test was successful.1188 */1189goog.testing.TestCase.Result.prototype.isSuccess = function() {1190  return this.complete && this.errors.length == 0;1191};119211931194/**1195 * @return {string} A summary of the tests, including total number of tests that1196 *     passed, failed, and the time taken.1197 */1198goog.testing.TestCase.Result.prototype.getSummary = function() {1199  var summary = this.runCount + ' of ' + this.totalCount + ' tests run in ' +1200      this.runTime + 'ms.\n';1201  if (this.testSuppressed) {1202    summary += 'Tests not run because shouldRunTests() returned false.';1203  } else {1204    var failures = this.totalCount - this.successCount;1205    var suppressionMessage = '';12061207    var countOfRunTests = this.testCase_.getActuallyRunCount();1208    if (countOfRunTests) {1209      failures = countOfRunTests - this.successCount;1210      suppressionMessage = ', ' +1211          (this.totalCount - countOfRunTests) + ' suppressed by querystring';1212    }1213    summary += this.successCount + ' passed, ' +1214        failures + ' failed' + suppressionMessage + '.\n' +1215        Math.round(this.runTime / this.runCount) + ' ms/test. ' +1216        this.numFilesLoaded + ' files loaded.';1217  }12181219  return summary;1220};122112221223/**1224 * Initializes the given test case with the global test runner 'G_testRunner'.1225 * @param {goog.testing.TestCase} testCase The test case to install.1226 */1227goog.testing.TestCase.initializeTestRunner = function(testCase) {1228  testCase.autoDiscoverTests();1229  var gTestRunner = goog.global['G_testRunner'];1230  if (gTestRunner) {1231    gTestRunner['initialize'](testCase);1232  } else {1233    throw Error('G_testRunner is undefined. Please ensure goog.testing.jsunit' +1234        ' is included.');1235  }1236};1237123812391240/**1241 * A class representing an error thrown by the test1242 * @param {string} source The name of the test which threw the error.1243 * @param {string} message The error message.1244 * @param {string=} opt_stack A string showing the execution stack.1245 * @constructor1246 * @final1247 */1248goog.testing.TestCase.Error = function(source, message, opt_stack) {1249  /**1250   * The name of the test which threw the error.1251   * @type {string}1252   */1253  this.source = source;12541255  /**1256   * Reference to the test function.1257   * @type {string}1258   */1259  this.message = message;12601261  /**1262   * Scope that the test function should be called in.1263   * @type {?string}1264   */1265  this.stack = opt_stack || null;1266};126712681269/**1270 * Returns a string representing the error object.1271 * @return {string} A string representation of the error.1272 * @override1273 */1274goog.testing.TestCase.Error.prototype.toString = function() {1275  return 'ERROR in ' + this.source + '\n' +1276      this.message + (this.stack ? '\n' + this.stack : '');1277};