123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548 |
- // Copyright 2008 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview Utility for running multiple test files that utilize the same
- * interface as goog.testing.TestRunner. Each test is run in series and their
- * results aggregated. The main usecase for the MultiTestRunner is to allow
- * the testing of all tests in a project locally.
- *
- */
- goog.setTestOnly('goog.testing.MultiTestRunner');
- goog.provide('goog.testing.MultiTestRunner');
- goog.provide('goog.testing.MultiTestRunner.TestFrame');
- goog.require('goog.Timer');
- goog.require('goog.array');
- goog.require('goog.asserts');
- goog.require('goog.dom');
- goog.require('goog.dom.TagName');
- goog.require('goog.dom.classlist');
- goog.require('goog.events.EventHandler');
- goog.require('goog.functions');
- goog.require('goog.object');
- goog.require('goog.string');
- goog.require('goog.testing.TestCase');
- goog.require('goog.ui.Component');
- goog.require('goog.ui.ServerChart');
- goog.require('goog.ui.TableSorter');
- /**
- * A component for running multiple tests within the browser.
- * @param {goog.dom.DomHelper=} opt_domHelper A DOM helper.
- * @extends {goog.ui.Component}
- * @constructor
- * @final
- */
- goog.testing.MultiTestRunner = function(opt_domHelper) {
- goog.ui.Component.call(this, opt_domHelper);
- /**
- * Array of tests to execute, when combined with the base path this should be
- * a relative path to the test from the page containing the multi testrunner.
- * @type {Array<string>}
- * @private
- */
- this.allTests_ = [];
- /**
- * Tests that match the filter function.
- * @type {Array<string>}
- * @private
- */
- this.activeTests_ = [];
- /**
- * An event handler for handling events.
- * @type {goog.events.EventHandler<!goog.testing.MultiTestRunner>}
- * @private
- */
- this.eh_ = new goog.events.EventHandler(this);
- /**
- * A table sorter for the stats.
- * @type {goog.ui.TableSorter}
- * @private
- */
- this.tableSorter_ = new goog.ui.TableSorter(this.dom_);
- /**
- * Array to hold individual test reports for tests that failed.
- * @type {!Array<!string>}
- * @private
- */
- this.failureReports_ = [];
- /**
- * Array of test result objects returned from G_testRunner.getTestResults for
- * each individual test run.
- * @private {!Array<!Object<string,!Array<!goog.testing.TestCase.IResult>>>}
- */
- this.allTestResults_ = [];
- };
- goog.inherits(goog.testing.MultiTestRunner, goog.ui.Component);
- /**
- * Default maximimum amount of time to spend at each stage of the test.
- * @type {number}
- */
- goog.testing.MultiTestRunner.DEFAULT_TIMEOUT_MS = 45 * 1000;
- /**
- * Messages corresponding to the numeric states.
- * @type {Array<string>}
- */
- goog.testing.MultiTestRunner.STATES = [
- 'waiting for test runner', 'initializing tests', 'waiting for tests to finish'
- ];
- /**
- * Event type dispatched when tests are completed.
- * @const
- */
- goog.testing.MultiTestRunner.TESTS_FINISHED = 'testsFinished';
- /**
- * The test suite's name.
- * @type {string} name
- * @private
- */
- goog.testing.MultiTestRunner.prototype.name_ = '';
- /**
- * The base path used to resolve files within the allTests_ array.
- * @type {string}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.basePath_ = '';
- /**
- * A set of tests that have finished. All extant keys map to true.
- * @type {Object<boolean>}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.finished_ = null;
- /**
- * Whether the report should contain verbose information about the passes.
- * @type {boolean}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.verbosePasses_ = false;
- /**
- * Whether to hide passing tests completely in the report, makes verbosePasses_
- * obsolete.
- * @type {boolean}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.hidePasses_ = false;
- /**
- * Flag used to tell the test runner to stop after the current test.
- * @type {boolean}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.stopped_ = false;
- /**
- * Flag indicating whether the test runner is active.
- * @type {boolean}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.active_ = false;
- /**
- * Index of the next test to run.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.startedCount_ = 0;
- /**
- * Count of the results received so far.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.resultCount_ = 0;
- /**
- * Number of passes so far.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.passes_ = 0;
- /**
- * Timestamp for the current start time.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.startTime_ = 0;
- /**
- * Only tests whose paths patch this filter function will be
- * executed.
- * @type {function(string): boolean}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.filterFn_ = goog.functions.TRUE;
- /**
- * Number of milliseconds to wait for loading and initialization steps.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.timeoutMs_ =
- goog.testing.MultiTestRunner.DEFAULT_TIMEOUT_MS;
- /**
- * An array of objects containing stats about the tests.
- * @type {Array<Object>?}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.stats_ = null;
- /**
- * Reference to the start button element.
- * @type {Element}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.startButtonEl_ = null;
- /**
- * Reference to the stop button element.
- * @type {Element}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.stopButtonEl_ = null;
- /**
- * Reference to the log element.
- * @type {Element}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.logEl_ = null;
- /**
- * Reference to the report element.
- * @type {Element}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.reportEl_ = null;
- /**
- * Reference to the stats element.
- * @type {Element}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.statsEl_ = null;
- /**
- * Reference to the progress bar's element.
- * @type {Element}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.progressEl_ = null;
- /**
- * Reference to the progress bar's inner row element.
- * @type {Element}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.progressRow_ = null;
- /**
- * Reference to the log tab.
- * @type {Element}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.logTabEl_ = null;
- /**
- * Reference to the report tab.
- * @type {Element}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.reportTabEl_ = null;
- /**
- * Reference to the stats tab.
- * @type {Element}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.statsTabEl_ = null;
- /**
- * The number of tests to run at a time.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.poolSize_ = 1;
- /**
- * The size of the stats bucket for the number of files loaded histogram.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.numFilesStatsBucketSize_ = 20;
- /**
- * The size of the stats bucket in ms for the run time histogram.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.prototype.runTimeStatsBucketSize_ = 500;
- /**
- * Sets the name for the test suite.
- * @param {string} name The suite's name.
- * @return {!goog.testing.MultiTestRunner} Instance for chaining.
- */
- goog.testing.MultiTestRunner.prototype.setName = function(name) {
- this.name_ = name;
- return this;
- };
- /**
- * Returns the name for the test suite.
- * @return {string} The name for the test suite.
- */
- goog.testing.MultiTestRunner.prototype.getName = function() {
- return this.name_;
- };
- /**
- * Sets the basepath that tests added using addTests are resolved with.
- * @param {string} path The relative basepath.
- * @return {!goog.testing.MultiTestRunner} Instance for chaining.
- */
- goog.testing.MultiTestRunner.prototype.setBasePath = function(path) {
- this.basePath_ = path;
- return this;
- };
- /**
- * Returns the basepath that tests added using addTests are resolved with.
- * @return {string} The basepath that tests added using addTests are resolved
- * with.
- */
- goog.testing.MultiTestRunner.prototype.getBasePath = function() {
- return this.basePath_;
- };
- /**
- * Sets whether the report should contain verbose information for tests that
- * pass.
- * @param {boolean} verbose Whether report should be verbose.
- * @return {!goog.testing.MultiTestRunner} Instance for chaining.
- */
- goog.testing.MultiTestRunner.prototype.setVerbosePasses = function(verbose) {
- this.verbosePasses_ = verbose;
- return this;
- };
- /**
- * Returns whether the report should contain verbose information for tests that
- * pass.
- * @return {boolean} Whether the report should contain verbose information for
- * tests that pass.
- */
- goog.testing.MultiTestRunner.prototype.getVerbosePasses = function() {
- return this.verbosePasses_;
- };
- /**
- * Sets whether the report should contain passing tests at all, makes
- * setVerbosePasses obsolete.
- * @param {boolean} hide Whether report should not contain passing tests.
- * @return {!goog.testing.MultiTestRunner} Instance for chaining.
- */
- goog.testing.MultiTestRunner.prototype.setHidePasses = function(hide) {
- this.hidePasses_ = hide;
- return this;
- };
- /**
- * Returns whether the report should contain passing tests at all, makes
- * setVerbosePasses obsolete.
- * @return {boolean} Whether the report should contain passing tests at all,
- * makes setVerbosePasses obsolete.
- */
- goog.testing.MultiTestRunner.prototype.getHidePasses = function() {
- return this.hidePasses_;
- };
- /**
- * Sets the bucket sizes for the histograms.
- * @param {number} f Bucket size for num files loaded histogram.
- * @param {number} t Bucket size for run time histogram.
- * @return {!goog.testing.MultiTestRunner} Instance for chaining.
- */
- goog.testing.MultiTestRunner.prototype.setStatsBucketSizes = function(f, t) {
- this.numFilesStatsBucketSize_ = f;
- this.runTimeStatsBucketSize_ = t;
- return this;
- };
- /**
- * Sets the number of milliseconds to wait for the page to load, initialize and
- * run the tests.
- * @param {number} timeout Time in milliseconds.
- * @return {!goog.testing.MultiTestRunner} Instance for chaining.
- */
- goog.testing.MultiTestRunner.prototype.setTimeout = function(timeout) {
- this.timeoutMs_ = timeout;
- return this;
- };
- /**
- * Returns the number of milliseconds to wait for the page to load, initialize
- * and run the tests.
- * @return {number} The number of milliseconds to wait for the page to load,
- * initialize and run the tests.
- */
- goog.testing.MultiTestRunner.prototype.getTimeout = function() {
- return this.timeoutMs_;
- };
- /**
- * Sets the number of tests that can be run at the same time. This only improves
- * performance due to the amount of time spent loading the tests.
- * @param {number} size The number of tests to run at a time.
- * @return {!goog.testing.MultiTestRunner} Instance for chaining.
- */
- goog.testing.MultiTestRunner.prototype.setPoolSize = function(size) {
- this.poolSize_ = size;
- return this;
- };
- /**
- * Returns the number of tests that can be run at the same time. This only
- * improves performance due to the amount of time spent loading the tests.
- * @return {number} The number of tests that can be run at the same time. This
- * only improves performance due to the amount of time spent loading the
- * tests.
- */
- goog.testing.MultiTestRunner.prototype.getPoolSize = function() {
- return this.poolSize_;
- };
- /**
- * Sets a filter function. Only test paths that match the filter function
- * will be executed.
- * @param {function(string): boolean} filterFn Filters test paths.
- * @return {!goog.testing.MultiTestRunner} Instance for chaining.
- */
- goog.testing.MultiTestRunner.prototype.setFilterFunction = function(filterFn) {
- this.filterFn_ = filterFn;
- return this;
- };
- /**
- * Returns a filter function. Only test paths that match the filter function
- * will be executed.
- * @return {function(string): boolean} A filter function. Only test paths that
- * match the filter function will be executed.
- */
- goog.testing.MultiTestRunner.prototype.getFilterFunction = function() {
- return this.filterFn_;
- };
- /**
- * Adds an array of tests to the tests that the test runner should execute.
- * @param {Array<string>} tests Adds tests to the test runner.
- * @return {!goog.testing.MultiTestRunner} Instance for chaining.
- */
- goog.testing.MultiTestRunner.prototype.addTests = function(tests) {
- goog.array.extend(this.allTests_, tests);
- return this;
- };
- /**
- * Returns the list of all tests added to the runner.
- * @return {Array<string>} The list of all tests added to the runner.
- */
- goog.testing.MultiTestRunner.prototype.getAllTests = function() {
- return this.allTests_;
- };
- /**
- * Returns the list of tests that will be run when start() is called.
- * @return {!Array<string>} The list of tests that will be run when start() is
- * called.
- */
- goog.testing.MultiTestRunner.prototype.getTestsToRun = function() {
- return goog.array.filter(this.allTests_, this.filterFn_);
- };
- /**
- * Returns a list of tests from runner that have been marked as failed.
- * @return {!Array<string>} A list of tests from runner that have been marked
- * as failed.
- */
- goog.testing.MultiTestRunner.prototype.getTestsThatFailed = function() {
- var stats = this.stats_;
- var failedTests = [];
- if (stats) {
- for (var i = 0, stat; stat = stats[i]; i++) {
- if (!stat.success) {
- failedTests.push(stat.testFile);
- }
- }
- }
- return failedTests;
- };
- /**
- * Returns a list of reports for tests that have finished since last "start".
- * @return {!Array<string>} A list of tests reports.
- */
- goog.testing.MultiTestRunner.prototype.getFailureReports = function() {
- return this.failureReports_;
- };
- /**
- * Returns list of each frame's test results.
- * @return {!Array<!Object<string,!Array<!goog.testing.TestCase.IResult>>>}
- */
- goog.testing.MultiTestRunner.prototype.getAllTestResults = function() {
- return this.allTestResults_;
- };
- /**
- * Deletes and re-creates the progress table inside the progess element.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.resetProgressDom_ = function() {
- goog.dom.removeChildren(this.progressEl_);
- var progressTable = this.dom_.createDom(goog.dom.TagName.TABLE);
- var progressTBody = this.dom_.createDom(goog.dom.TagName.TBODY);
- this.progressRow_ = this.dom_.createDom(goog.dom.TagName.TR);
- for (var i = 0; i < this.activeTests_.length; i++) {
- var progressCell = this.dom_.createDom(goog.dom.TagName.TD);
- this.progressRow_.appendChild(progressCell);
- }
- progressTBody.appendChild(this.progressRow_);
- progressTable.appendChild(progressTBody);
- this.progressEl_.appendChild(progressTable);
- };
- /** @override */
- goog.testing.MultiTestRunner.prototype.createDom = function() {
- goog.testing.MultiTestRunner.superClass_.createDom.call(this);
- var el = this.getElement();
- el.className = goog.getCssName('goog-testrunner');
- this.progressEl_ = this.dom_.createDom(goog.dom.TagName.DIV);
- this.progressEl_.className = goog.getCssName('goog-testrunner-progress');
- el.appendChild(this.progressEl_);
- var buttons = this.dom_.createDom(goog.dom.TagName.DIV);
- buttons.className = goog.getCssName('goog-testrunner-buttons');
- this.startButtonEl_ =
- this.dom_.createDom(goog.dom.TagName.BUTTON, null, 'Start');
- this.stopButtonEl_ =
- this.dom_.createDom(goog.dom.TagName.BUTTON, {'disabled': true}, 'Stop');
- buttons.appendChild(this.startButtonEl_);
- buttons.appendChild(this.stopButtonEl_);
- el.appendChild(buttons);
- this.eh_.listen(this.startButtonEl_, 'click', this.onStartClicked_);
- this.eh_.listen(this.stopButtonEl_, 'click', this.onStopClicked_);
- this.logEl_ = this.dom_.createElement(goog.dom.TagName.DIV);
- this.logEl_.className = goog.getCssName('goog-testrunner-log');
- el.appendChild(this.logEl_);
- this.reportEl_ = this.dom_.createElement(goog.dom.TagName.DIV);
- this.reportEl_.className = goog.getCssName('goog-testrunner-report');
- this.reportEl_.style.display = 'none';
- el.appendChild(this.reportEl_);
- this.statsEl_ = this.dom_.createElement(goog.dom.TagName.DIV);
- this.statsEl_.className = goog.getCssName('goog-testrunner-stats');
- this.statsEl_.style.display = 'none';
- el.appendChild(this.statsEl_);
- this.logTabEl_ = this.dom_.createDom(goog.dom.TagName.DIV, null, 'Log');
- this.logTabEl_.className = goog.getCssName('goog-testrunner-logtab') + ' ' +
- goog.getCssName('goog-testrunner-activetab');
- el.appendChild(this.logTabEl_);
- this.reportTabEl_ = this.dom_.createDom(goog.dom.TagName.DIV, null, 'Report');
- this.reportTabEl_.className = goog.getCssName('goog-testrunner-reporttab');
- el.appendChild(this.reportTabEl_);
- this.statsTabEl_ = this.dom_.createDom(goog.dom.TagName.DIV, null, 'Stats');
- this.statsTabEl_.className = goog.getCssName('goog-testrunner-statstab');
- el.appendChild(this.statsTabEl_);
- this.eh_.listen(this.logTabEl_, 'click', this.onLogTabClicked_);
- this.eh_.listen(this.reportTabEl_, 'click', this.onReportTabClicked_);
- this.eh_.listen(this.statsTabEl_, 'click', this.onStatsTabClicked_);
- };
- /** @override */
- goog.testing.MultiTestRunner.prototype.disposeInternal = function() {
- goog.testing.MultiTestRunner.superClass_.disposeInternal.call(this);
- this.tableSorter_.dispose();
- this.eh_.dispose();
- this.startButtonEl_ = null;
- this.stopButtonEl_ = null;
- this.logEl_ = null;
- this.reportEl_ = null;
- this.progressEl_ = null;
- this.logTabEl_ = null;
- this.reportTabEl_ = null;
- this.statsTabEl_ = null;
- this.statsEl_ = null;
- };
- /**
- * Starts executing the tests.
- */
- goog.testing.MultiTestRunner.prototype.start = function() {
- this.startButtonEl_.disabled = true;
- this.stopButtonEl_.disabled = false;
- this.stopped_ = false;
- this.active_ = true;
- this.finished_ = {};
- this.activeTests_ = this.getTestsToRun();
- this.startedCount_ = 0;
- this.resultCount_ = 0;
- this.passes_ = 0;
- this.stats_ = [];
- this.startTime_ = goog.now();
- this.failureReports_ = [];
- this.resetProgressDom_();
- goog.dom.removeChildren(this.logEl_);
- this.resetReport_();
- this.clearStats_();
- this.showTab_(0);
- // No tests to run, finish early and return.
- if (this.activeTests_.length == 0) {
- this.finish_();
- return;
- }
- // Ensure the pool isn't too big.
- while (this.getChildCount() > this.poolSize_) {
- this.removeChildAt(0, true).dispose();
- }
- // Start a test in each runner.
- for (var i = 0; i < this.poolSize_; i++) {
- if (i >= this.getChildCount()) {
- var testFrame = new goog.testing.MultiTestRunner.TestFrame(
- this.basePath_, this.timeoutMs_, this.verbosePasses_, this.dom_);
- this.addChild(testFrame, true);
- }
- this.runNextTest_(
- /** @type {goog.testing.MultiTestRunner.TestFrame} */
- (this.getChildAt(i)));
- }
- };
- /**
- * Logs a message to the log window.
- * @param {string} msg A message to log.
- */
- goog.testing.MultiTestRunner.prototype.log = function(msg) {
- if (msg != '.') {
- msg = this.getTimeStamp_() + ' : ' + msg;
- }
- this.logEl_.appendChild(this.dom_.createDom(goog.dom.TagName.DIV, null, msg));
- // Autoscroll if we're near the bottom.
- var top = this.logEl_.scrollTop;
- var height = /** @type {!HTMLElement} */ (this.logEl_).scrollHeight -
- /** @type {!HTMLElement} */ (this.logEl_).offsetHeight;
- if (top == 0 || top > height - 50) {
- this.logEl_.scrollTop = height;
- }
- };
- /**
- * Processes a result returned from a TestFrame. If there are tests remaining
- * it will trigger the next one to be run, otherwise if there are no tests and
- * all results have been received then it will call finish.
- * @param {goog.testing.MultiTestRunner.TestFrame} frame The frame that just
- * finished.
- */
- goog.testing.MultiTestRunner.prototype.processResult = function(frame) {
- var success = frame.isSuccess();
- var report = frame.getReport();
- var test = frame.getTestFile();
- var stats = frame.getStats();
- if (!stats.success) {
- this.failureReports_.push(report);
- }
- this.allTestResults_.push(frame.getTestResults());
- this.stats_.push(stats);
- this.finished_[test] = true;
- var prefix = success ? '' : '*** FAILURE *** ';
- this.log(
- prefix + this.trimFileName_(test) + ' : ' +
- (success ? 'Passed' : 'Failed'));
- this.resultCount_++;
- if (success) {
- this.passes_++;
- }
- this.drawProgressSegment_(test, success);
- this.writeCurrentSummary_();
- if (!(success && this.hidePasses_)) {
- this.drawTestResult_(test, success, report);
- }
- if (!this.stopped_ && this.startedCount_ < this.activeTests_.length) {
- this.runNextTest_(frame);
- } else if (this.resultCount_ == this.activeTests_.length) {
- this.finish_();
- }
- };
- /**
- * Runs the next available test, if there are any left.
- * @param {goog.testing.MultiTestRunner.TestFrame} frame Where to run the test.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.runNextTest_ = function(frame) {
- if (this.startedCount_ < this.activeTests_.length) {
- var nextTest = this.activeTests_[this.startedCount_++];
- this.log(this.trimFileName_(nextTest) + ' : Loading');
- frame.runTest(nextTest);
- }
- };
- /**
- * Handles the test finishing, processing the results and rendering the report.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.finish_ = function() {
- if (this.stopped_) {
- this.log('Stopped');
- } else {
- this.log('Finished');
- }
- this.startButtonEl_.disabled = false;
- this.stopButtonEl_.disabled = true;
- this.active_ = false;
- this.showTab_(1);
- this.drawStats_();
- // Remove all the test frames
- while (this.getChildCount() > 0) {
- this.removeChildAt(0, true).dispose();
- }
- // Compute tests that did not finish before the stop button was hit.
- var unfinished = [];
- for (var i = 0; i < this.activeTests_.length; i++) {
- var test = this.activeTests_[i];
- if (!this.finished_[test]) {
- unfinished.push(test);
- }
- }
- if (unfinished.length) {
- this.reportEl_.appendChild(
- goog.dom.createDom(
- goog.dom.TagName.PRE, undefined,
- 'These tests did not finish:\n' + unfinished.join('\n')));
- }
- this.dispatchEvent({
- 'type': goog.testing.MultiTestRunner.TESTS_FINISHED,
- 'allTestResults': this.getAllTestResults()
- });
- };
- /**
- * Resets the report, clearing out all children and drawing the initial summary.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.resetReport_ = function() {
- goog.dom.removeChildren(this.reportEl_);
- var summary = this.dom_.createDom(goog.dom.TagName.DIV);
- summary.className = goog.getCssName('goog-testrunner-progress-summary');
- this.reportEl_.appendChild(summary);
- this.writeCurrentSummary_();
- };
- /**
- * Draws the stats for the test run.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.drawStats_ = function() {
- this.drawFilesHistogram_();
- // Only show time stats if pool size is 1, otherwise times are wrong.
- if (this.poolSize_ == 1) {
- this.drawRunTimePie_();
- this.drawTimeHistogram_();
- }
- this.drawWorstTestsTable_();
- };
- /**
- * Draws the histogram showing number of files loaded.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.drawFilesHistogram_ = function() {
- this.drawStatsHistogram_(
- 'numFilesLoaded', this.numFilesStatsBucketSize_, goog.functions.identity,
- 500,
- 'Histogram showing distribution of\nnumber of files loaded per test');
- };
- /**
- * Draws the histogram showing how long each test took to complete.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.drawTimeHistogram_ = function() {
- this.drawStatsHistogram_(
- 'totalTime', this.runTimeStatsBucketSize_,
- function(x) { return x / 1000; }, 500,
- 'Histogram showing distribution of\ntime spent running tests in s');
- };
- /**
- * Draws a stats histogram.
- * @param {string} statsField Field of the stats object to graph.
- * @param {number} bucketSize The size for the histogram's buckets.
- * @param {function(number, ...*): *} valueTransformFn Function for
- * transforming the x-labels value for display.
- * @param {number} width The width in pixels of the graph.
- * @param {string} title The graph's title.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.drawStatsHistogram_ = function(
- statsField, bucketSize, valueTransformFn, width, title) {
- var hist = {}, data = [], xlabels = [], ylabels = [];
- var max = 0;
- for (var i = 0; i < this.stats_.length; i++) {
- var num = this.stats_[i][statsField];
- var bucket = Math.floor(num / bucketSize) * bucketSize;
- if (bucket > max) {
- max = bucket;
- }
- if (!hist[bucket]) {
- hist[bucket] = 1;
- } else {
- hist[bucket]++;
- }
- }
- var maxBucketSize = 0;
- for (var i = 0; i <= max; i += bucketSize) {
- xlabels.push(valueTransformFn(i));
- var count = hist[i] || 0;
- if (count > maxBucketSize) {
- maxBucketSize = count;
- }
- data.push(count);
- }
- var diff = Math.max(1, Math.ceil(maxBucketSize / 10));
- for (var i = 0; i <= maxBucketSize; i += diff) {
- ylabels.push(i);
- }
- var chart = new goog.ui.ServerChart(
- goog.ui.ServerChart.ChartType.VERTICAL_STACKED_BAR, width, 250, null,
- goog.ui.ServerChart.CHART_SERVER_HTTPS_URI);
- chart.setTitle(title);
- chart.addDataSet(data, 'ff9900');
- chart.setLeftLabels(ylabels);
- chart.setGridY(ylabels.length - 1);
- chart.setXLabels(xlabels);
- chart.render(this.statsEl_);
- };
- /**
- * Draws a pie chart showing the percentage of time spent running the tests
- * compared to loading them etc.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.drawRunTimePie_ = function() {
- var totalTime = 0, runTime = 0;
- for (var i = 0; i < this.stats_.length; i++) {
- var stat = this.stats_[i];
- totalTime += stat.totalTime;
- runTime += stat.runTime;
- }
- var loadTime = totalTime - runTime;
- var pie = new goog.ui.ServerChart(
- goog.ui.ServerChart.ChartType.PIE, 500, 250, null,
- goog.ui.ServerChart.CHART_SERVER_HTTPS_URI);
- pie.setMinValue(0);
- pie.setMaxValue(totalTime);
- pie.addDataSet([runTime, loadTime], 'ff9900');
- pie.setXLabels(
- ['Test execution (' + runTime + 'ms)', 'Loading (' + loadTime + 'ms)']);
- pie.render(this.statsEl_);
- };
- /**
- * Draws a pie chart showing the percentage of time spent running the tests
- * compared to loading them etc.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.drawWorstTestsTable_ = function() {
- this.stats_.sort(function(a, b) {
- return b['numFilesLoaded'] - a['numFilesLoaded'];
- });
- var tbody = goog.bind(this.dom_.createDom, this.dom_, 'tbody');
- var thead = goog.bind(this.dom_.createDom, this.dom_, 'thead');
- var tr = goog.bind(this.dom_.createDom, this.dom_, 'tr');
- var th = goog.bind(this.dom_.createDom, this.dom_, 'th');
- var td = goog.bind(this.dom_.createDom, this.dom_, 'td');
- var a = goog.bind(this.dom_.createDom, this.dom_, 'a');
- var head = thead(
- {'style': 'cursor: pointer'},
- tr(null, th(null, ' '), th(null, 'Test file'),
- th('center', 'Num files loaded'), th('center', 'Run time (ms)'),
- th('center', 'Total time (ms)')));
- var body = tbody();
- var table = this.dom_.createDom(goog.dom.TagName.TABLE, null, head, body);
- for (var i = 0; i < this.stats_.length; i++) {
- var stat = this.stats_[i];
- body.appendChild(
- tr(null, td('center', String(i + 1)),
- td(null,
- a({'href': this.basePath_ + stat['testFile'], 'target': '_blank'},
- stat['testFile'])),
- td('center', String(stat['numFilesLoaded'])),
- td('center', String(stat['runTime'])),
- td('center', String(stat['totalTime']))));
- }
- this.statsEl_.appendChild(table);
- this.tableSorter_.setDefaultSortFunction(goog.ui.TableSorter.numericSort);
- this.tableSorter_.setSortFunction(
- 1 /* test file name */, goog.ui.TableSorter.alphaSort);
- this.tableSorter_.decorate(table);
- };
- /**
- * Clears the stats page.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.clearStats_ = function() {
- goog.dom.removeChildren(this.statsEl_);
- this.tableSorter_.exitDocument();
- };
- /**
- * Updates the report's summary.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.writeCurrentSummary_ = function() {
- var total = this.activeTests_.length;
- var executed = this.resultCount_;
- var passes = this.passes_;
- var duration = Math.round((goog.now() - this.startTime_) / 1000);
- var text = executed + ' of ' + total + ' tests executed.<br>' + passes +
- ' passed, ' + (executed - passes) + ' failed.<br>' +
- 'Duration: ' + duration + 's.';
- this.reportEl_.firstChild.innerHTML = text;
- };
- /**
- * Adds a segment to the progress bar.
- * @param {string} title Title for the segment.
- * @param {*} success Whether the segment should indicate a success.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.drawProgressSegment_ = function(
- title, success) {
- var part = this.progressRow_.cells[this.resultCount_ - 1];
- part.title = title + ' : ' + (success ? 'SUCCESS' : 'FAILURE');
- part.style.backgroundColor = success ? '#090' : '#900';
- };
- /**
- * Draws a test result in the report pane.
- * @param {string} test Test name.
- * @param {*} success Whether the test succeeded.
- * @param {string} report The report.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.drawTestResult_ = function(
- test, success, report) {
- var text = goog.string.isEmptyOrWhitespace(report) ?
- 'No report for ' + test + '\n' :
- report;
- var el = this.dom_.createDom(goog.dom.TagName.DIV);
- text = goog.string.htmlEscape(text).replace(/\n/g, '<br>');
- if (success) {
- el.className = goog.getCssName('goog-testrunner-report-success');
- } else {
- text += '<a href="' + this.basePath_ + test +
- '">Run individually »</a><br> ';
- el.className = goog.getCssName('goog-testrunner-report-failure');
- }
- el.innerHTML = text;
- this.reportEl_.appendChild(el);
- };
- /**
- * Returns the current timestamp.
- * @return {string} HH:MM:SS.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.getTimeStamp_ = function() {
- var d = new Date;
- return goog.string.padNumber(d.getHours(), 2) + ':' +
- goog.string.padNumber(d.getMinutes(), 2) + ':' +
- goog.string.padNumber(d.getSeconds(), 2);
- };
- /**
- * Trims a filename to be less than 35-characters, ensuring that we do not break
- * a path part.
- * @param {string} name The file name.
- * @return {string} The shortened name.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.trimFileName_ = function(name) {
- if (name.length < 35) {
- return name;
- }
- var parts = name.split('/');
- var result = '';
- while (result.length < 35 && parts.length > 0) {
- result = '/' + parts.pop() + result;
- }
- return '...' + result;
- };
- /**
- * Shows the report and hides the log if the argument is true.
- * @param {number} tab Which tab to show.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.showTab_ = function(tab) {
- var activeTabCssClass = goog.getCssName('goog-testrunner-activetab');
- var logTabElement = goog.asserts.assert(this.logTabEl_);
- var reportTabElement = goog.asserts.assert(this.reportTabEl_);
- var statsTabElement = goog.asserts.assert(this.statsTabEl_);
- if (tab == 0) {
- this.logEl_.style.display = '';
- goog.dom.classlist.add(logTabElement, activeTabCssClass);
- } else {
- this.logEl_.style.display = 'none';
- goog.dom.classlist.remove(logTabElement, activeTabCssClass);
- }
- if (tab == 1) {
- this.reportEl_.style.display = '';
- goog.dom.classlist.add(reportTabElement, activeTabCssClass);
- } else {
- this.reportEl_.style.display = 'none';
- goog.dom.classlist.remove(reportTabElement, activeTabCssClass);
- }
- if (tab == 2) {
- this.statsEl_.style.display = '';
- goog.dom.classlist.add(statsTabElement, activeTabCssClass);
- } else {
- this.statsEl_.style.display = 'none';
- goog.dom.classlist.remove(statsTabElement, activeTabCssClass);
- }
- };
- /**
- * Handles the start button being clicked.
- * @param {goog.events.BrowserEvent} e The click event.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.onStartClicked_ = function(e) {
- this.start();
- };
- /**
- * Handles the stop button being clicked.
- * @param {goog.events.BrowserEvent} e The click event.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.onStopClicked_ = function(e) {
- this.stopped_ = true;
- this.finish_();
- };
- /**
- * Handles the log tab being clicked.
- * @param {goog.events.BrowserEvent} e The click event.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.onLogTabClicked_ = function(e) {
- this.showTab_(0);
- };
- /**
- * Handles the log tab being clicked.
- * @param {goog.events.BrowserEvent} e The click event.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.onReportTabClicked_ = function(e) {
- this.showTab_(1);
- };
- /**
- * Handles the stats tab being clicked.
- * @param {goog.events.BrowserEvent} e The click event.
- * @private
- */
- goog.testing.MultiTestRunner.prototype.onStatsTabClicked_ = function(e) {
- this.showTab_(2);
- };
- /**
- * Class used to manage the interaction with a single iframe.
- * @param {string} basePath The base path for tests.
- * @param {number} timeoutMs The time to wait for the test to load and run.
- * @param {boolean} verbosePasses Whether to show results for passes.
- * @param {goog.dom.DomHelper=} opt_domHelper Optional dom helper.
- * @constructor
- * @extends {goog.ui.Component}
- * @final
- */
- goog.testing.MultiTestRunner.TestFrame = function(
- basePath, timeoutMs, verbosePasses, opt_domHelper) {
- goog.ui.Component.call(this, opt_domHelper);
- /**
- * Base path where tests should be resolved from.
- * @type {string}
- * @private
- */
- this.basePath_ = basePath;
- /**
- * The timeout for the test.
- * @type {number}
- * @private
- */
- this.timeoutMs_ = timeoutMs;
- /**
- * Whether to show a summary for passing tests.
- * @type {boolean}
- * @private
- */
- this.verbosePasses_ = verbosePasses;
- /**
- * An event handler for handling events.
- * @type {goog.events.EventHandler<!goog.testing.MultiTestRunner.TestFrame>}
- * @private
- */
- this.eh_ = new goog.events.EventHandler(this);
- /**
- * Object to hold test results. Key is test method or file name (depending on
- * failure mode) and the value is an array of failure messages.
- * @private {!Object<string,!Array<!goog.testing.TestCase.IResult>>}
- */
- this.testResults_ = {};
- };
- goog.inherits(goog.testing.MultiTestRunner.TestFrame, goog.ui.Component);
- /**
- * Reference to the iframe.
- * @type {HTMLIFrameElement}
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.iframeEl_ = null;
- /**
- * Whether the iframe for the current test has loaded.
- * @type {boolean}
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.iframeLoaded_ = false;
- /**
- * The test file being run.
- * @type {string}
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.testFile_ = '';
- /**
- * The report returned from the test.
- * @type {string}
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.report_ = '';
- /**
- * The total time loading and running the test in milliseconds.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.totalTime_ = 0;
- /**
- * The actual runtime of the test in milliseconds.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.runTime_ = 0;
- /**
- * The number of files loaded by the test.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.numFilesLoaded_ = 0;
- /**
- * Whether the test was successful, null if no result has been returned yet.
- * @type {?boolean}
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.isSuccess_ = null;
- /**
- * Timestamp for the when the test was started.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.startTime_ = 0;
- /**
- * Timestamp for the last state, used to determine timeouts.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.lastStateTime_ = 0;
- /**
- * The state of the active test.
- * @type {number}
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.currentState_ = 0;
- /** @override */
- goog.testing.MultiTestRunner.TestFrame.prototype.disposeInternal = function() {
- goog.testing.MultiTestRunner.TestFrame.superClass_.disposeInternal.call(this);
- this.dom_.removeNode(this.iframeEl_);
- this.eh_.dispose();
- this.iframeEl_ = null;
- };
- /**
- * Runs a test file in this test frame.
- * @param {string} testFile The test to run.
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.runTest = function(testFile) {
- this.lastStateTime_ = this.startTime_ = goog.now();
- if (!this.iframeEl_) {
- this.createIframe_();
- }
- this.iframeLoaded_ = false;
- this.currentState_ = 0;
- this.isSuccess_ = null;
- this.report_ = '';
- this.testResults_ = {};
- this.testFile_ = testFile;
- try {
- this.iframeEl_.src = this.basePath_ + testFile;
- } catch (e) {
- // Failures will trigger a JS exception on the local file system.
- this.report_ = this.testFile_ + ' failed to load : ' + e.message;
- this.isSuccess_ = false;
- this.finish_();
- return;
- }
- this.checkForCompletion_();
- };
- /**
- * @return {string} The test file the TestFrame is running.
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.getTestFile = function() {
- return this.testFile_;
- };
- /**
- * @return {!Object} Stats about the test run.
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.getStats = function() {
- return {
- 'testFile': this.testFile_,
- 'success': this.isSuccess_,
- 'runTime': this.runTime_,
- 'totalTime': this.totalTime_,
- 'numFilesLoaded': this.numFilesLoaded_
- };
- };
- /**
- * @return {string} The report for the test run.
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.getReport = function() {
- return this.report_;
- };
- /**
- * @return {!Object<string,!Array<!goog.testing.TestCase.IResult>>} The results
- * per individual test in the file. Key is the test filename concatenated
- * with the test name, and the array holds failures.
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.getTestResults = function() {
- var results = {};
- for (var testName in this.testResults_) {
- var testKey = this.testFile_.replace(/\.html$/, '');
- // Concatenate with ":<testName>" unless the testName is equivalent to
- // testFile_, which means the test timed out or had no test methods and
- // there's no way to get the test method name.
- if (testName != this.testFile_) {
- testKey += ':' + testName;
- }
- results[testKey] = this.testResults_[testName];
- }
- return results;
- };
- /**
- * @return {?boolean} Whether the test frame had a success.
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.isSuccess = function() {
- return this.isSuccess_;
- };
- /**
- * Handles the TestFrame finishing a single test.
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.finish_ = function() {
- this.totalTime_ = goog.now() - this.startTime_;
- // TODO(user): Fire an event instead?
- if (this.getParent() && this.getParent().processResult) {
- this.getParent().processResult(this);
- }
- };
- /**
- * Creates an iframe to run the tests in. For overriding in unit tests.
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.createIframe_ = function() {
- this.iframeEl_ = this.dom_.createDom(goog.dom.TagName.IFRAME);
- this.getElement().appendChild(this.iframeEl_);
- this.eh_.listen(this.iframeEl_, 'load', this.onIframeLoaded_);
- };
- /**
- * Handles the iframe loading.
- * @param {goog.events.BrowserEvent} e The load event.
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.onIframeLoaded_ = function(e) {
- this.iframeLoaded_ = true;
- };
- /**
- * Checks the active test for completion, keeping track of the tests' various
- * execution stages.
- * @private
- */
- goog.testing.MultiTestRunner.TestFrame.prototype.checkForCompletion_ =
- function() {
- var js = goog.dom.getFrameContentWindow(this.iframeEl_);
- switch (this.currentState_) {
- case 0:
- if (this.iframeLoaded_ && js['G_testRunner']) {
- this.lastStateTime_ = goog.now();
- this.currentState_++;
- }
- break;
- case 1:
- if (js['G_testRunner']['isInitialized']()) {
- this.lastStateTime_ = goog.now();
- this.currentState_++;
- }
- break;
- case 2:
- if (js['G_testRunner']['isFinished']()) {
- var tr = js['G_testRunner'];
- this.isSuccess_ = tr['isSuccess']();
- this.report_ = tr['getReport'](this.verbosePasses_);
- this.testResults_ = tr['getTestResults']();
- // If there is a syntax error, or no tests, it's not possible to get the
- // individual test method results from TestCase. So just create one here
- // based on the test report and filename.
- if (goog.object.isEmpty(this.testResults_)) {
- // Existence of a report is a signal of a test failure by the test
- // runner.
- this.testResults_[this.testFile_] = this.isSuccess_ ? [] : [{
- 'message': this.report_,
- 'source': this.testFile_,
- 'stacktrace': ''
- }];
- }
- this.runTime_ = tr['getRunTime']();
- this.numFilesLoaded_ = tr['getNumFilesLoaded']();
- this.finish_();
- return;
- }
- }
- // Check to see if the test has timed out.
- if (goog.now() - this.lastStateTime_ > this.timeoutMs_) {
- this.report_ = this.testFile_ + ' timed out ' +
- goog.testing.MultiTestRunner.STATES[this.currentState_];
- this.testResults_[this.testFile_] =
- [{'message': this.report_, 'source': this.testFile_, 'stacktrace': ''}];
- this.isSuccess_ = false;
- this.finish_();
- return;
- }
- // Check again in 100ms.
- goog.Timer.callOnce(this.checkForCompletion_, 100, this);
- };
|