multitestrunner.js 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548
  1. // Copyright 2008 The Closure Library Authors. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS-IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. /**
  15. * @fileoverview Utility for running multiple test files that utilize the same
  16. * interface as goog.testing.TestRunner. Each test is run in series and their
  17. * results aggregated. The main usecase for the MultiTestRunner is to allow
  18. * the testing of all tests in a project locally.
  19. *
  20. */
  21. goog.setTestOnly('goog.testing.MultiTestRunner');
  22. goog.provide('goog.testing.MultiTestRunner');
  23. goog.provide('goog.testing.MultiTestRunner.TestFrame');
  24. goog.require('goog.Timer');
  25. goog.require('goog.array');
  26. goog.require('goog.asserts');
  27. goog.require('goog.dom');
  28. goog.require('goog.dom.TagName');
  29. goog.require('goog.dom.classlist');
  30. goog.require('goog.events.EventHandler');
  31. goog.require('goog.functions');
  32. goog.require('goog.object');
  33. goog.require('goog.string');
  34. goog.require('goog.testing.TestCase');
  35. goog.require('goog.ui.Component');
  36. goog.require('goog.ui.ServerChart');
  37. goog.require('goog.ui.TableSorter');
  38. /**
  39. * A component for running multiple tests within the browser.
  40. * @param {goog.dom.DomHelper=} opt_domHelper A DOM helper.
  41. * @extends {goog.ui.Component}
  42. * @constructor
  43. * @final
  44. */
  45. goog.testing.MultiTestRunner = function(opt_domHelper) {
  46. goog.ui.Component.call(this, opt_domHelper);
  47. /**
  48. * Array of tests to execute, when combined with the base path this should be
  49. * a relative path to the test from the page containing the multi testrunner.
  50. * @type {Array<string>}
  51. * @private
  52. */
  53. this.allTests_ = [];
  54. /**
  55. * Tests that match the filter function.
  56. * @type {Array<string>}
  57. * @private
  58. */
  59. this.activeTests_ = [];
  60. /**
  61. * An event handler for handling events.
  62. * @type {goog.events.EventHandler<!goog.testing.MultiTestRunner>}
  63. * @private
  64. */
  65. this.eh_ = new goog.events.EventHandler(this);
  66. /**
  67. * A table sorter for the stats.
  68. * @type {goog.ui.TableSorter}
  69. * @private
  70. */
  71. this.tableSorter_ = new goog.ui.TableSorter(this.dom_);
  72. /**
  73. * Array to hold individual test reports for tests that failed.
  74. * @type {!Array<!string>}
  75. * @private
  76. */
  77. this.failureReports_ = [];
  78. /**
  79. * Array of test result objects returned from G_testRunner.getTestResults for
  80. * each individual test run.
  81. * @private {!Array<!Object<string,!Array<!goog.testing.TestCase.IResult>>>}
  82. */
  83. this.allTestResults_ = [];
  84. };
  85. goog.inherits(goog.testing.MultiTestRunner, goog.ui.Component);
  86. /**
  87. * Default maximimum amount of time to spend at each stage of the test.
  88. * @type {number}
  89. */
  90. goog.testing.MultiTestRunner.DEFAULT_TIMEOUT_MS = 45 * 1000;
  91. /**
  92. * Messages corresponding to the numeric states.
  93. * @type {Array<string>}
  94. */
  95. goog.testing.MultiTestRunner.STATES = [
  96. 'waiting for test runner', 'initializing tests', 'waiting for tests to finish'
  97. ];
  98. /**
  99. * Event type dispatched when tests are completed.
  100. * @const
  101. */
  102. goog.testing.MultiTestRunner.TESTS_FINISHED = 'testsFinished';
  103. /**
  104. * The test suite's name.
  105. * @type {string} name
  106. * @private
  107. */
  108. goog.testing.MultiTestRunner.prototype.name_ = '';
  109. /**
  110. * The base path used to resolve files within the allTests_ array.
  111. * @type {string}
  112. * @private
  113. */
  114. goog.testing.MultiTestRunner.prototype.basePath_ = '';
  115. /**
  116. * A set of tests that have finished. All extant keys map to true.
  117. * @type {Object<boolean>}
  118. * @private
  119. */
  120. goog.testing.MultiTestRunner.prototype.finished_ = null;
  121. /**
  122. * Whether the report should contain verbose information about the passes.
  123. * @type {boolean}
  124. * @private
  125. */
  126. goog.testing.MultiTestRunner.prototype.verbosePasses_ = false;
  127. /**
  128. * Whether to hide passing tests completely in the report, makes verbosePasses_
  129. * obsolete.
  130. * @type {boolean}
  131. * @private
  132. */
  133. goog.testing.MultiTestRunner.prototype.hidePasses_ = false;
  134. /**
  135. * Flag used to tell the test runner to stop after the current test.
  136. * @type {boolean}
  137. * @private
  138. */
  139. goog.testing.MultiTestRunner.prototype.stopped_ = false;
  140. /**
  141. * Flag indicating whether the test runner is active.
  142. * @type {boolean}
  143. * @private
  144. */
  145. goog.testing.MultiTestRunner.prototype.active_ = false;
  146. /**
  147. * Index of the next test to run.
  148. * @type {number}
  149. * @private
  150. */
  151. goog.testing.MultiTestRunner.prototype.startedCount_ = 0;
  152. /**
  153. * Count of the results received so far.
  154. * @type {number}
  155. * @private
  156. */
  157. goog.testing.MultiTestRunner.prototype.resultCount_ = 0;
  158. /**
  159. * Number of passes so far.
  160. * @type {number}
  161. * @private
  162. */
  163. goog.testing.MultiTestRunner.prototype.passes_ = 0;
  164. /**
  165. * Timestamp for the current start time.
  166. * @type {number}
  167. * @private
  168. */
  169. goog.testing.MultiTestRunner.prototype.startTime_ = 0;
  170. /**
  171. * Only tests whose paths patch this filter function will be
  172. * executed.
  173. * @type {function(string): boolean}
  174. * @private
  175. */
  176. goog.testing.MultiTestRunner.prototype.filterFn_ = goog.functions.TRUE;
  177. /**
  178. * Number of milliseconds to wait for loading and initialization steps.
  179. * @type {number}
  180. * @private
  181. */
  182. goog.testing.MultiTestRunner.prototype.timeoutMs_ =
  183. goog.testing.MultiTestRunner.DEFAULT_TIMEOUT_MS;
  184. /**
  185. * An array of objects containing stats about the tests.
  186. * @type {Array<Object>?}
  187. * @private
  188. */
  189. goog.testing.MultiTestRunner.prototype.stats_ = null;
  190. /**
  191. * Reference to the start button element.
  192. * @type {Element}
  193. * @private
  194. */
  195. goog.testing.MultiTestRunner.prototype.startButtonEl_ = null;
  196. /**
  197. * Reference to the stop button element.
  198. * @type {Element}
  199. * @private
  200. */
  201. goog.testing.MultiTestRunner.prototype.stopButtonEl_ = null;
  202. /**
  203. * Reference to the log element.
  204. * @type {Element}
  205. * @private
  206. */
  207. goog.testing.MultiTestRunner.prototype.logEl_ = null;
  208. /**
  209. * Reference to the report element.
  210. * @type {Element}
  211. * @private
  212. */
  213. goog.testing.MultiTestRunner.prototype.reportEl_ = null;
  214. /**
  215. * Reference to the stats element.
  216. * @type {Element}
  217. * @private
  218. */
  219. goog.testing.MultiTestRunner.prototype.statsEl_ = null;
  220. /**
  221. * Reference to the progress bar's element.
  222. * @type {Element}
  223. * @private
  224. */
  225. goog.testing.MultiTestRunner.prototype.progressEl_ = null;
  226. /**
  227. * Reference to the progress bar's inner row element.
  228. * @type {Element}
  229. * @private
  230. */
  231. goog.testing.MultiTestRunner.prototype.progressRow_ = null;
  232. /**
  233. * Reference to the log tab.
  234. * @type {Element}
  235. * @private
  236. */
  237. goog.testing.MultiTestRunner.prototype.logTabEl_ = null;
  238. /**
  239. * Reference to the report tab.
  240. * @type {Element}
  241. * @private
  242. */
  243. goog.testing.MultiTestRunner.prototype.reportTabEl_ = null;
  244. /**
  245. * Reference to the stats tab.
  246. * @type {Element}
  247. * @private
  248. */
  249. goog.testing.MultiTestRunner.prototype.statsTabEl_ = null;
  250. /**
  251. * The number of tests to run at a time.
  252. * @type {number}
  253. * @private
  254. */
  255. goog.testing.MultiTestRunner.prototype.poolSize_ = 1;
  256. /**
  257. * The size of the stats bucket for the number of files loaded histogram.
  258. * @type {number}
  259. * @private
  260. */
  261. goog.testing.MultiTestRunner.prototype.numFilesStatsBucketSize_ = 20;
  262. /**
  263. * The size of the stats bucket in ms for the run time histogram.
  264. * @type {number}
  265. * @private
  266. */
  267. goog.testing.MultiTestRunner.prototype.runTimeStatsBucketSize_ = 500;
  268. /**
  269. * Sets the name for the test suite.
  270. * @param {string} name The suite's name.
  271. * @return {!goog.testing.MultiTestRunner} Instance for chaining.
  272. */
  273. goog.testing.MultiTestRunner.prototype.setName = function(name) {
  274. this.name_ = name;
  275. return this;
  276. };
  277. /**
  278. * Returns the name for the test suite.
  279. * @return {string} The name for the test suite.
  280. */
  281. goog.testing.MultiTestRunner.prototype.getName = function() {
  282. return this.name_;
  283. };
  284. /**
  285. * Sets the basepath that tests added using addTests are resolved with.
  286. * @param {string} path The relative basepath.
  287. * @return {!goog.testing.MultiTestRunner} Instance for chaining.
  288. */
  289. goog.testing.MultiTestRunner.prototype.setBasePath = function(path) {
  290. this.basePath_ = path;
  291. return this;
  292. };
  293. /**
  294. * Returns the basepath that tests added using addTests are resolved with.
  295. * @return {string} The basepath that tests added using addTests are resolved
  296. * with.
  297. */
  298. goog.testing.MultiTestRunner.prototype.getBasePath = function() {
  299. return this.basePath_;
  300. };
  301. /**
  302. * Sets whether the report should contain verbose information for tests that
  303. * pass.
  304. * @param {boolean} verbose Whether report should be verbose.
  305. * @return {!goog.testing.MultiTestRunner} Instance for chaining.
  306. */
  307. goog.testing.MultiTestRunner.prototype.setVerbosePasses = function(verbose) {
  308. this.verbosePasses_ = verbose;
  309. return this;
  310. };
  311. /**
  312. * Returns whether the report should contain verbose information for tests that
  313. * pass.
  314. * @return {boolean} Whether the report should contain verbose information for
  315. * tests that pass.
  316. */
  317. goog.testing.MultiTestRunner.prototype.getVerbosePasses = function() {
  318. return this.verbosePasses_;
  319. };
  320. /**
  321. * Sets whether the report should contain passing tests at all, makes
  322. * setVerbosePasses obsolete.
  323. * @param {boolean} hide Whether report should not contain passing tests.
  324. * @return {!goog.testing.MultiTestRunner} Instance for chaining.
  325. */
  326. goog.testing.MultiTestRunner.prototype.setHidePasses = function(hide) {
  327. this.hidePasses_ = hide;
  328. return this;
  329. };
  330. /**
  331. * Returns whether the report should contain passing tests at all, makes
  332. * setVerbosePasses obsolete.
  333. * @return {boolean} Whether the report should contain passing tests at all,
  334. * makes setVerbosePasses obsolete.
  335. */
  336. goog.testing.MultiTestRunner.prototype.getHidePasses = function() {
  337. return this.hidePasses_;
  338. };
  339. /**
  340. * Sets the bucket sizes for the histograms.
  341. * @param {number} f Bucket size for num files loaded histogram.
  342. * @param {number} t Bucket size for run time histogram.
  343. * @return {!goog.testing.MultiTestRunner} Instance for chaining.
  344. */
  345. goog.testing.MultiTestRunner.prototype.setStatsBucketSizes = function(f, t) {
  346. this.numFilesStatsBucketSize_ = f;
  347. this.runTimeStatsBucketSize_ = t;
  348. return this;
  349. };
  350. /**
  351. * Sets the number of milliseconds to wait for the page to load, initialize and
  352. * run the tests.
  353. * @param {number} timeout Time in milliseconds.
  354. * @return {!goog.testing.MultiTestRunner} Instance for chaining.
  355. */
  356. goog.testing.MultiTestRunner.prototype.setTimeout = function(timeout) {
  357. this.timeoutMs_ = timeout;
  358. return this;
  359. };
  360. /**
  361. * Returns the number of milliseconds to wait for the page to load, initialize
  362. * and run the tests.
  363. * @return {number} The number of milliseconds to wait for the page to load,
  364. * initialize and run the tests.
  365. */
  366. goog.testing.MultiTestRunner.prototype.getTimeout = function() {
  367. return this.timeoutMs_;
  368. };
  369. /**
  370. * Sets the number of tests that can be run at the same time. This only improves
  371. * performance due to the amount of time spent loading the tests.
  372. * @param {number} size The number of tests to run at a time.
  373. * @return {!goog.testing.MultiTestRunner} Instance for chaining.
  374. */
  375. goog.testing.MultiTestRunner.prototype.setPoolSize = function(size) {
  376. this.poolSize_ = size;
  377. return this;
  378. };
  379. /**
  380. * Returns the number of tests that can be run at the same time. This only
  381. * improves performance due to the amount of time spent loading the tests.
  382. * @return {number} The number of tests that can be run at the same time. This
  383. * only improves performance due to the amount of time spent loading the
  384. * tests.
  385. */
  386. goog.testing.MultiTestRunner.prototype.getPoolSize = function() {
  387. return this.poolSize_;
  388. };
  389. /**
  390. * Sets a filter function. Only test paths that match the filter function
  391. * will be executed.
  392. * @param {function(string): boolean} filterFn Filters test paths.
  393. * @return {!goog.testing.MultiTestRunner} Instance for chaining.
  394. */
  395. goog.testing.MultiTestRunner.prototype.setFilterFunction = function(filterFn) {
  396. this.filterFn_ = filterFn;
  397. return this;
  398. };
  399. /**
  400. * Returns a filter function. Only test paths that match the filter function
  401. * will be executed.
  402. * @return {function(string): boolean} A filter function. Only test paths that
  403. * match the filter function will be executed.
  404. */
  405. goog.testing.MultiTestRunner.prototype.getFilterFunction = function() {
  406. return this.filterFn_;
  407. };
  408. /**
  409. * Adds an array of tests to the tests that the test runner should execute.
  410. * @param {Array<string>} tests Adds tests to the test runner.
  411. * @return {!goog.testing.MultiTestRunner} Instance for chaining.
  412. */
  413. goog.testing.MultiTestRunner.prototype.addTests = function(tests) {
  414. goog.array.extend(this.allTests_, tests);
  415. return this;
  416. };
  417. /**
  418. * Returns the list of all tests added to the runner.
  419. * @return {Array<string>} The list of all tests added to the runner.
  420. */
  421. goog.testing.MultiTestRunner.prototype.getAllTests = function() {
  422. return this.allTests_;
  423. };
  424. /**
  425. * Returns the list of tests that will be run when start() is called.
  426. * @return {!Array<string>} The list of tests that will be run when start() is
  427. * called.
  428. */
  429. goog.testing.MultiTestRunner.prototype.getTestsToRun = function() {
  430. return goog.array.filter(this.allTests_, this.filterFn_);
  431. };
  432. /**
  433. * Returns a list of tests from runner that have been marked as failed.
  434. * @return {!Array<string>} A list of tests from runner that have been marked
  435. * as failed.
  436. */
  437. goog.testing.MultiTestRunner.prototype.getTestsThatFailed = function() {
  438. var stats = this.stats_;
  439. var failedTests = [];
  440. if (stats) {
  441. for (var i = 0, stat; stat = stats[i]; i++) {
  442. if (!stat.success) {
  443. failedTests.push(stat.testFile);
  444. }
  445. }
  446. }
  447. return failedTests;
  448. };
  449. /**
  450. * Returns a list of reports for tests that have finished since last "start".
  451. * @return {!Array<string>} A list of tests reports.
  452. */
  453. goog.testing.MultiTestRunner.prototype.getFailureReports = function() {
  454. return this.failureReports_;
  455. };
  456. /**
  457. * Returns list of each frame's test results.
  458. * @return {!Array<!Object<string,!Array<!goog.testing.TestCase.IResult>>>}
  459. */
  460. goog.testing.MultiTestRunner.prototype.getAllTestResults = function() {
  461. return this.allTestResults_;
  462. };
  463. /**
  464. * Deletes and re-creates the progress table inside the progess element.
  465. * @private
  466. */
  467. goog.testing.MultiTestRunner.prototype.resetProgressDom_ = function() {
  468. goog.dom.removeChildren(this.progressEl_);
  469. var progressTable = this.dom_.createDom(goog.dom.TagName.TABLE);
  470. var progressTBody = this.dom_.createDom(goog.dom.TagName.TBODY);
  471. this.progressRow_ = this.dom_.createDom(goog.dom.TagName.TR);
  472. for (var i = 0; i < this.activeTests_.length; i++) {
  473. var progressCell = this.dom_.createDom(goog.dom.TagName.TD);
  474. this.progressRow_.appendChild(progressCell);
  475. }
  476. progressTBody.appendChild(this.progressRow_);
  477. progressTable.appendChild(progressTBody);
  478. this.progressEl_.appendChild(progressTable);
  479. };
  480. /** @override */
  481. goog.testing.MultiTestRunner.prototype.createDom = function() {
  482. goog.testing.MultiTestRunner.superClass_.createDom.call(this);
  483. var el = this.getElement();
  484. el.className = goog.getCssName('goog-testrunner');
  485. this.progressEl_ = this.dom_.createDom(goog.dom.TagName.DIV);
  486. this.progressEl_.className = goog.getCssName('goog-testrunner-progress');
  487. el.appendChild(this.progressEl_);
  488. var buttons = this.dom_.createDom(goog.dom.TagName.DIV);
  489. buttons.className = goog.getCssName('goog-testrunner-buttons');
  490. this.startButtonEl_ =
  491. this.dom_.createDom(goog.dom.TagName.BUTTON, null, 'Start');
  492. this.stopButtonEl_ =
  493. this.dom_.createDom(goog.dom.TagName.BUTTON, {'disabled': true}, 'Stop');
  494. buttons.appendChild(this.startButtonEl_);
  495. buttons.appendChild(this.stopButtonEl_);
  496. el.appendChild(buttons);
  497. this.eh_.listen(this.startButtonEl_, 'click', this.onStartClicked_);
  498. this.eh_.listen(this.stopButtonEl_, 'click', this.onStopClicked_);
  499. this.logEl_ = this.dom_.createElement(goog.dom.TagName.DIV);
  500. this.logEl_.className = goog.getCssName('goog-testrunner-log');
  501. el.appendChild(this.logEl_);
  502. this.reportEl_ = this.dom_.createElement(goog.dom.TagName.DIV);
  503. this.reportEl_.className = goog.getCssName('goog-testrunner-report');
  504. this.reportEl_.style.display = 'none';
  505. el.appendChild(this.reportEl_);
  506. this.statsEl_ = this.dom_.createElement(goog.dom.TagName.DIV);
  507. this.statsEl_.className = goog.getCssName('goog-testrunner-stats');
  508. this.statsEl_.style.display = 'none';
  509. el.appendChild(this.statsEl_);
  510. this.logTabEl_ = this.dom_.createDom(goog.dom.TagName.DIV, null, 'Log');
  511. this.logTabEl_.className = goog.getCssName('goog-testrunner-logtab') + ' ' +
  512. goog.getCssName('goog-testrunner-activetab');
  513. el.appendChild(this.logTabEl_);
  514. this.reportTabEl_ = this.dom_.createDom(goog.dom.TagName.DIV, null, 'Report');
  515. this.reportTabEl_.className = goog.getCssName('goog-testrunner-reporttab');
  516. el.appendChild(this.reportTabEl_);
  517. this.statsTabEl_ = this.dom_.createDom(goog.dom.TagName.DIV, null, 'Stats');
  518. this.statsTabEl_.className = goog.getCssName('goog-testrunner-statstab');
  519. el.appendChild(this.statsTabEl_);
  520. this.eh_.listen(this.logTabEl_, 'click', this.onLogTabClicked_);
  521. this.eh_.listen(this.reportTabEl_, 'click', this.onReportTabClicked_);
  522. this.eh_.listen(this.statsTabEl_, 'click', this.onStatsTabClicked_);
  523. };
  524. /** @override */
  525. goog.testing.MultiTestRunner.prototype.disposeInternal = function() {
  526. goog.testing.MultiTestRunner.superClass_.disposeInternal.call(this);
  527. this.tableSorter_.dispose();
  528. this.eh_.dispose();
  529. this.startButtonEl_ = null;
  530. this.stopButtonEl_ = null;
  531. this.logEl_ = null;
  532. this.reportEl_ = null;
  533. this.progressEl_ = null;
  534. this.logTabEl_ = null;
  535. this.reportTabEl_ = null;
  536. this.statsTabEl_ = null;
  537. this.statsEl_ = null;
  538. };
  539. /**
  540. * Starts executing the tests.
  541. */
  542. goog.testing.MultiTestRunner.prototype.start = function() {
  543. this.startButtonEl_.disabled = true;
  544. this.stopButtonEl_.disabled = false;
  545. this.stopped_ = false;
  546. this.active_ = true;
  547. this.finished_ = {};
  548. this.activeTests_ = this.getTestsToRun();
  549. this.startedCount_ = 0;
  550. this.resultCount_ = 0;
  551. this.passes_ = 0;
  552. this.stats_ = [];
  553. this.startTime_ = goog.now();
  554. this.failureReports_ = [];
  555. this.resetProgressDom_();
  556. goog.dom.removeChildren(this.logEl_);
  557. this.resetReport_();
  558. this.clearStats_();
  559. this.showTab_(0);
  560. // No tests to run, finish early and return.
  561. if (this.activeTests_.length == 0) {
  562. this.finish_();
  563. return;
  564. }
  565. // Ensure the pool isn't too big.
  566. while (this.getChildCount() > this.poolSize_) {
  567. this.removeChildAt(0, true).dispose();
  568. }
  569. // Start a test in each runner.
  570. for (var i = 0; i < this.poolSize_; i++) {
  571. if (i >= this.getChildCount()) {
  572. var testFrame = new goog.testing.MultiTestRunner.TestFrame(
  573. this.basePath_, this.timeoutMs_, this.verbosePasses_, this.dom_);
  574. this.addChild(testFrame, true);
  575. }
  576. this.runNextTest_(
  577. /** @type {goog.testing.MultiTestRunner.TestFrame} */
  578. (this.getChildAt(i)));
  579. }
  580. };
  581. /**
  582. * Logs a message to the log window.
  583. * @param {string} msg A message to log.
  584. */
  585. goog.testing.MultiTestRunner.prototype.log = function(msg) {
  586. if (msg != '.') {
  587. msg = this.getTimeStamp_() + ' : ' + msg;
  588. }
  589. this.logEl_.appendChild(this.dom_.createDom(goog.dom.TagName.DIV, null, msg));
  590. // Autoscroll if we're near the bottom.
  591. var top = this.logEl_.scrollTop;
  592. var height = /** @type {!HTMLElement} */ (this.logEl_).scrollHeight -
  593. /** @type {!HTMLElement} */ (this.logEl_).offsetHeight;
  594. if (top == 0 || top > height - 50) {
  595. this.logEl_.scrollTop = height;
  596. }
  597. };
  598. /**
  599. * Processes a result returned from a TestFrame. If there are tests remaining
  600. * it will trigger the next one to be run, otherwise if there are no tests and
  601. * all results have been received then it will call finish.
  602. * @param {goog.testing.MultiTestRunner.TestFrame} frame The frame that just
  603. * finished.
  604. */
  605. goog.testing.MultiTestRunner.prototype.processResult = function(frame) {
  606. var success = frame.isSuccess();
  607. var report = frame.getReport();
  608. var test = frame.getTestFile();
  609. var stats = frame.getStats();
  610. if (!stats.success) {
  611. this.failureReports_.push(report);
  612. }
  613. this.allTestResults_.push(frame.getTestResults());
  614. this.stats_.push(stats);
  615. this.finished_[test] = true;
  616. var prefix = success ? '' : '*** FAILURE *** ';
  617. this.log(
  618. prefix + this.trimFileName_(test) + ' : ' +
  619. (success ? 'Passed' : 'Failed'));
  620. this.resultCount_++;
  621. if (success) {
  622. this.passes_++;
  623. }
  624. this.drawProgressSegment_(test, success);
  625. this.writeCurrentSummary_();
  626. if (!(success && this.hidePasses_)) {
  627. this.drawTestResult_(test, success, report);
  628. }
  629. if (!this.stopped_ && this.startedCount_ < this.activeTests_.length) {
  630. this.runNextTest_(frame);
  631. } else if (this.resultCount_ == this.activeTests_.length) {
  632. this.finish_();
  633. }
  634. };
  635. /**
  636. * Runs the next available test, if there are any left.
  637. * @param {goog.testing.MultiTestRunner.TestFrame} frame Where to run the test.
  638. * @private
  639. */
  640. goog.testing.MultiTestRunner.prototype.runNextTest_ = function(frame) {
  641. if (this.startedCount_ < this.activeTests_.length) {
  642. var nextTest = this.activeTests_[this.startedCount_++];
  643. this.log(this.trimFileName_(nextTest) + ' : Loading');
  644. frame.runTest(nextTest);
  645. }
  646. };
  647. /**
  648. * Handles the test finishing, processing the results and rendering the report.
  649. * @private
  650. */
  651. goog.testing.MultiTestRunner.prototype.finish_ = function() {
  652. if (this.stopped_) {
  653. this.log('Stopped');
  654. } else {
  655. this.log('Finished');
  656. }
  657. this.startButtonEl_.disabled = false;
  658. this.stopButtonEl_.disabled = true;
  659. this.active_ = false;
  660. this.showTab_(1);
  661. this.drawStats_();
  662. // Remove all the test frames
  663. while (this.getChildCount() > 0) {
  664. this.removeChildAt(0, true).dispose();
  665. }
  666. // Compute tests that did not finish before the stop button was hit.
  667. var unfinished = [];
  668. for (var i = 0; i < this.activeTests_.length; i++) {
  669. var test = this.activeTests_[i];
  670. if (!this.finished_[test]) {
  671. unfinished.push(test);
  672. }
  673. }
  674. if (unfinished.length) {
  675. this.reportEl_.appendChild(
  676. goog.dom.createDom(
  677. goog.dom.TagName.PRE, undefined,
  678. 'These tests did not finish:\n' + unfinished.join('\n')));
  679. }
  680. this.dispatchEvent({
  681. 'type': goog.testing.MultiTestRunner.TESTS_FINISHED,
  682. 'allTestResults': this.getAllTestResults()
  683. });
  684. };
  685. /**
  686. * Resets the report, clearing out all children and drawing the initial summary.
  687. * @private
  688. */
  689. goog.testing.MultiTestRunner.prototype.resetReport_ = function() {
  690. goog.dom.removeChildren(this.reportEl_);
  691. var summary = this.dom_.createDom(goog.dom.TagName.DIV);
  692. summary.className = goog.getCssName('goog-testrunner-progress-summary');
  693. this.reportEl_.appendChild(summary);
  694. this.writeCurrentSummary_();
  695. };
  696. /**
  697. * Draws the stats for the test run.
  698. * @private
  699. */
  700. goog.testing.MultiTestRunner.prototype.drawStats_ = function() {
  701. this.drawFilesHistogram_();
  702. // Only show time stats if pool size is 1, otherwise times are wrong.
  703. if (this.poolSize_ == 1) {
  704. this.drawRunTimePie_();
  705. this.drawTimeHistogram_();
  706. }
  707. this.drawWorstTestsTable_();
  708. };
  709. /**
  710. * Draws the histogram showing number of files loaded.
  711. * @private
  712. */
  713. goog.testing.MultiTestRunner.prototype.drawFilesHistogram_ = function() {
  714. this.drawStatsHistogram_(
  715. 'numFilesLoaded', this.numFilesStatsBucketSize_, goog.functions.identity,
  716. 500,
  717. 'Histogram showing distribution of\nnumber of files loaded per test');
  718. };
  719. /**
  720. * Draws the histogram showing how long each test took to complete.
  721. * @private
  722. */
  723. goog.testing.MultiTestRunner.prototype.drawTimeHistogram_ = function() {
  724. this.drawStatsHistogram_(
  725. 'totalTime', this.runTimeStatsBucketSize_,
  726. function(x) { return x / 1000; }, 500,
  727. 'Histogram showing distribution of\ntime spent running tests in s');
  728. };
  729. /**
  730. * Draws a stats histogram.
  731. * @param {string} statsField Field of the stats object to graph.
  732. * @param {number} bucketSize The size for the histogram's buckets.
  733. * @param {function(number, ...*): *} valueTransformFn Function for
  734. * transforming the x-labels value for display.
  735. * @param {number} width The width in pixels of the graph.
  736. * @param {string} title The graph's title.
  737. * @private
  738. */
  739. goog.testing.MultiTestRunner.prototype.drawStatsHistogram_ = function(
  740. statsField, bucketSize, valueTransformFn, width, title) {
  741. var hist = {}, data = [], xlabels = [], ylabels = [];
  742. var max = 0;
  743. for (var i = 0; i < this.stats_.length; i++) {
  744. var num = this.stats_[i][statsField];
  745. var bucket = Math.floor(num / bucketSize) * bucketSize;
  746. if (bucket > max) {
  747. max = bucket;
  748. }
  749. if (!hist[bucket]) {
  750. hist[bucket] = 1;
  751. } else {
  752. hist[bucket]++;
  753. }
  754. }
  755. var maxBucketSize = 0;
  756. for (var i = 0; i <= max; i += bucketSize) {
  757. xlabels.push(valueTransformFn(i));
  758. var count = hist[i] || 0;
  759. if (count > maxBucketSize) {
  760. maxBucketSize = count;
  761. }
  762. data.push(count);
  763. }
  764. var diff = Math.max(1, Math.ceil(maxBucketSize / 10));
  765. for (var i = 0; i <= maxBucketSize; i += diff) {
  766. ylabels.push(i);
  767. }
  768. var chart = new goog.ui.ServerChart(
  769. goog.ui.ServerChart.ChartType.VERTICAL_STACKED_BAR, width, 250, null,
  770. goog.ui.ServerChart.CHART_SERVER_HTTPS_URI);
  771. chart.setTitle(title);
  772. chart.addDataSet(data, 'ff9900');
  773. chart.setLeftLabels(ylabels);
  774. chart.setGridY(ylabels.length - 1);
  775. chart.setXLabels(xlabels);
  776. chart.render(this.statsEl_);
  777. };
  778. /**
  779. * Draws a pie chart showing the percentage of time spent running the tests
  780. * compared to loading them etc.
  781. * @private
  782. */
  783. goog.testing.MultiTestRunner.prototype.drawRunTimePie_ = function() {
  784. var totalTime = 0, runTime = 0;
  785. for (var i = 0; i < this.stats_.length; i++) {
  786. var stat = this.stats_[i];
  787. totalTime += stat.totalTime;
  788. runTime += stat.runTime;
  789. }
  790. var loadTime = totalTime - runTime;
  791. var pie = new goog.ui.ServerChart(
  792. goog.ui.ServerChart.ChartType.PIE, 500, 250, null,
  793. goog.ui.ServerChart.CHART_SERVER_HTTPS_URI);
  794. pie.setMinValue(0);
  795. pie.setMaxValue(totalTime);
  796. pie.addDataSet([runTime, loadTime], 'ff9900');
  797. pie.setXLabels(
  798. ['Test execution (' + runTime + 'ms)', 'Loading (' + loadTime + 'ms)']);
  799. pie.render(this.statsEl_);
  800. };
  801. /**
  802. * Draws a pie chart showing the percentage of time spent running the tests
  803. * compared to loading them etc.
  804. * @private
  805. */
  806. goog.testing.MultiTestRunner.prototype.drawWorstTestsTable_ = function() {
  807. this.stats_.sort(function(a, b) {
  808. return b['numFilesLoaded'] - a['numFilesLoaded'];
  809. });
  810. var tbody = goog.bind(this.dom_.createDom, this.dom_, 'tbody');
  811. var thead = goog.bind(this.dom_.createDom, this.dom_, 'thead');
  812. var tr = goog.bind(this.dom_.createDom, this.dom_, 'tr');
  813. var th = goog.bind(this.dom_.createDom, this.dom_, 'th');
  814. var td = goog.bind(this.dom_.createDom, this.dom_, 'td');
  815. var a = goog.bind(this.dom_.createDom, this.dom_, 'a');
  816. var head = thead(
  817. {'style': 'cursor: pointer'},
  818. tr(null, th(null, ' '), th(null, 'Test file'),
  819. th('center', 'Num files loaded'), th('center', 'Run time (ms)'),
  820. th('center', 'Total time (ms)')));
  821. var body = tbody();
  822. var table = this.dom_.createDom(goog.dom.TagName.TABLE, null, head, body);
  823. for (var i = 0; i < this.stats_.length; i++) {
  824. var stat = this.stats_[i];
  825. body.appendChild(
  826. tr(null, td('center', String(i + 1)),
  827. td(null,
  828. a({'href': this.basePath_ + stat['testFile'], 'target': '_blank'},
  829. stat['testFile'])),
  830. td('center', String(stat['numFilesLoaded'])),
  831. td('center', String(stat['runTime'])),
  832. td('center', String(stat['totalTime']))));
  833. }
  834. this.statsEl_.appendChild(table);
  835. this.tableSorter_.setDefaultSortFunction(goog.ui.TableSorter.numericSort);
  836. this.tableSorter_.setSortFunction(
  837. 1 /* test file name */, goog.ui.TableSorter.alphaSort);
  838. this.tableSorter_.decorate(table);
  839. };
  840. /**
  841. * Clears the stats page.
  842. * @private
  843. */
  844. goog.testing.MultiTestRunner.prototype.clearStats_ = function() {
  845. goog.dom.removeChildren(this.statsEl_);
  846. this.tableSorter_.exitDocument();
  847. };
  848. /**
  849. * Updates the report's summary.
  850. * @private
  851. */
  852. goog.testing.MultiTestRunner.prototype.writeCurrentSummary_ = function() {
  853. var total = this.activeTests_.length;
  854. var executed = this.resultCount_;
  855. var passes = this.passes_;
  856. var duration = Math.round((goog.now() - this.startTime_) / 1000);
  857. var text = executed + ' of ' + total + ' tests executed.<br>' + passes +
  858. ' passed, ' + (executed - passes) + ' failed.<br>' +
  859. 'Duration: ' + duration + 's.';
  860. this.reportEl_.firstChild.innerHTML = text;
  861. };
  862. /**
  863. * Adds a segment to the progress bar.
  864. * @param {string} title Title for the segment.
  865. * @param {*} success Whether the segment should indicate a success.
  866. * @private
  867. */
  868. goog.testing.MultiTestRunner.prototype.drawProgressSegment_ = function(
  869. title, success) {
  870. var part = this.progressRow_.cells[this.resultCount_ - 1];
  871. part.title = title + ' : ' + (success ? 'SUCCESS' : 'FAILURE');
  872. part.style.backgroundColor = success ? '#090' : '#900';
  873. };
  874. /**
  875. * Draws a test result in the report pane.
  876. * @param {string} test Test name.
  877. * @param {*} success Whether the test succeeded.
  878. * @param {string} report The report.
  879. * @private
  880. */
  881. goog.testing.MultiTestRunner.prototype.drawTestResult_ = function(
  882. test, success, report) {
  883. var text = goog.string.isEmptyOrWhitespace(report) ?
  884. 'No report for ' + test + '\n' :
  885. report;
  886. var el = this.dom_.createDom(goog.dom.TagName.DIV);
  887. text = goog.string.htmlEscape(text).replace(/\n/g, '<br>');
  888. if (success) {
  889. el.className = goog.getCssName('goog-testrunner-report-success');
  890. } else {
  891. text += '<a href="' + this.basePath_ + test +
  892. '">Run individually &raquo;</a><br>&nbsp;';
  893. el.className = goog.getCssName('goog-testrunner-report-failure');
  894. }
  895. el.innerHTML = text;
  896. this.reportEl_.appendChild(el);
  897. };
  898. /**
  899. * Returns the current timestamp.
  900. * @return {string} HH:MM:SS.
  901. * @private
  902. */
  903. goog.testing.MultiTestRunner.prototype.getTimeStamp_ = function() {
  904. var d = new Date;
  905. return goog.string.padNumber(d.getHours(), 2) + ':' +
  906. goog.string.padNumber(d.getMinutes(), 2) + ':' +
  907. goog.string.padNumber(d.getSeconds(), 2);
  908. };
  909. /**
  910. * Trims a filename to be less than 35-characters, ensuring that we do not break
  911. * a path part.
  912. * @param {string} name The file name.
  913. * @return {string} The shortened name.
  914. * @private
  915. */
  916. goog.testing.MultiTestRunner.prototype.trimFileName_ = function(name) {
  917. if (name.length < 35) {
  918. return name;
  919. }
  920. var parts = name.split('/');
  921. var result = '';
  922. while (result.length < 35 && parts.length > 0) {
  923. result = '/' + parts.pop() + result;
  924. }
  925. return '...' + result;
  926. };
  927. /**
  928. * Shows the report and hides the log if the argument is true.
  929. * @param {number} tab Which tab to show.
  930. * @private
  931. */
  932. goog.testing.MultiTestRunner.prototype.showTab_ = function(tab) {
  933. var activeTabCssClass = goog.getCssName('goog-testrunner-activetab');
  934. var logTabElement = goog.asserts.assert(this.logTabEl_);
  935. var reportTabElement = goog.asserts.assert(this.reportTabEl_);
  936. var statsTabElement = goog.asserts.assert(this.statsTabEl_);
  937. if (tab == 0) {
  938. this.logEl_.style.display = '';
  939. goog.dom.classlist.add(logTabElement, activeTabCssClass);
  940. } else {
  941. this.logEl_.style.display = 'none';
  942. goog.dom.classlist.remove(logTabElement, activeTabCssClass);
  943. }
  944. if (tab == 1) {
  945. this.reportEl_.style.display = '';
  946. goog.dom.classlist.add(reportTabElement, activeTabCssClass);
  947. } else {
  948. this.reportEl_.style.display = 'none';
  949. goog.dom.classlist.remove(reportTabElement, activeTabCssClass);
  950. }
  951. if (tab == 2) {
  952. this.statsEl_.style.display = '';
  953. goog.dom.classlist.add(statsTabElement, activeTabCssClass);
  954. } else {
  955. this.statsEl_.style.display = 'none';
  956. goog.dom.classlist.remove(statsTabElement, activeTabCssClass);
  957. }
  958. };
  959. /**
  960. * Handles the start button being clicked.
  961. * @param {goog.events.BrowserEvent} e The click event.
  962. * @private
  963. */
  964. goog.testing.MultiTestRunner.prototype.onStartClicked_ = function(e) {
  965. this.start();
  966. };
  967. /**
  968. * Handles the stop button being clicked.
  969. * @param {goog.events.BrowserEvent} e The click event.
  970. * @private
  971. */
  972. goog.testing.MultiTestRunner.prototype.onStopClicked_ = function(e) {
  973. this.stopped_ = true;
  974. this.finish_();
  975. };
  976. /**
  977. * Handles the log tab being clicked.
  978. * @param {goog.events.BrowserEvent} e The click event.
  979. * @private
  980. */
  981. goog.testing.MultiTestRunner.prototype.onLogTabClicked_ = function(e) {
  982. this.showTab_(0);
  983. };
  984. /**
  985. * Handles the log tab being clicked.
  986. * @param {goog.events.BrowserEvent} e The click event.
  987. * @private
  988. */
  989. goog.testing.MultiTestRunner.prototype.onReportTabClicked_ = function(e) {
  990. this.showTab_(1);
  991. };
  992. /**
  993. * Handles the stats tab being clicked.
  994. * @param {goog.events.BrowserEvent} e The click event.
  995. * @private
  996. */
  997. goog.testing.MultiTestRunner.prototype.onStatsTabClicked_ = function(e) {
  998. this.showTab_(2);
  999. };
  1000. /**
  1001. * Class used to manage the interaction with a single iframe.
  1002. * @param {string} basePath The base path for tests.
  1003. * @param {number} timeoutMs The time to wait for the test to load and run.
  1004. * @param {boolean} verbosePasses Whether to show results for passes.
  1005. * @param {goog.dom.DomHelper=} opt_domHelper Optional dom helper.
  1006. * @constructor
  1007. * @extends {goog.ui.Component}
  1008. * @final
  1009. */
  1010. goog.testing.MultiTestRunner.TestFrame = function(
  1011. basePath, timeoutMs, verbosePasses, opt_domHelper) {
  1012. goog.ui.Component.call(this, opt_domHelper);
  1013. /**
  1014. * Base path where tests should be resolved from.
  1015. * @type {string}
  1016. * @private
  1017. */
  1018. this.basePath_ = basePath;
  1019. /**
  1020. * The timeout for the test.
  1021. * @type {number}
  1022. * @private
  1023. */
  1024. this.timeoutMs_ = timeoutMs;
  1025. /**
  1026. * Whether to show a summary for passing tests.
  1027. * @type {boolean}
  1028. * @private
  1029. */
  1030. this.verbosePasses_ = verbosePasses;
  1031. /**
  1032. * An event handler for handling events.
  1033. * @type {goog.events.EventHandler<!goog.testing.MultiTestRunner.TestFrame>}
  1034. * @private
  1035. */
  1036. this.eh_ = new goog.events.EventHandler(this);
  1037. /**
  1038. * Object to hold test results. Key is test method or file name (depending on
  1039. * failure mode) and the value is an array of failure messages.
  1040. * @private {!Object<string,!Array<!goog.testing.TestCase.IResult>>}
  1041. */
  1042. this.testResults_ = {};
  1043. };
  1044. goog.inherits(goog.testing.MultiTestRunner.TestFrame, goog.ui.Component);
  1045. /**
  1046. * Reference to the iframe.
  1047. * @type {HTMLIFrameElement}
  1048. * @private
  1049. */
  1050. goog.testing.MultiTestRunner.TestFrame.prototype.iframeEl_ = null;
  1051. /**
  1052. * Whether the iframe for the current test has loaded.
  1053. * @type {boolean}
  1054. * @private
  1055. */
  1056. goog.testing.MultiTestRunner.TestFrame.prototype.iframeLoaded_ = false;
  1057. /**
  1058. * The test file being run.
  1059. * @type {string}
  1060. * @private
  1061. */
  1062. goog.testing.MultiTestRunner.TestFrame.prototype.testFile_ = '';
  1063. /**
  1064. * The report returned from the test.
  1065. * @type {string}
  1066. * @private
  1067. */
  1068. goog.testing.MultiTestRunner.TestFrame.prototype.report_ = '';
  1069. /**
  1070. * The total time loading and running the test in milliseconds.
  1071. * @type {number}
  1072. * @private
  1073. */
  1074. goog.testing.MultiTestRunner.TestFrame.prototype.totalTime_ = 0;
  1075. /**
  1076. * The actual runtime of the test in milliseconds.
  1077. * @type {number}
  1078. * @private
  1079. */
  1080. goog.testing.MultiTestRunner.TestFrame.prototype.runTime_ = 0;
  1081. /**
  1082. * The number of files loaded by the test.
  1083. * @type {number}
  1084. * @private
  1085. */
  1086. goog.testing.MultiTestRunner.TestFrame.prototype.numFilesLoaded_ = 0;
  1087. /**
  1088. * Whether the test was successful, null if no result has been returned yet.
  1089. * @type {?boolean}
  1090. * @private
  1091. */
  1092. goog.testing.MultiTestRunner.TestFrame.prototype.isSuccess_ = null;
  1093. /**
  1094. * Timestamp for the when the test was started.
  1095. * @type {number}
  1096. * @private
  1097. */
  1098. goog.testing.MultiTestRunner.TestFrame.prototype.startTime_ = 0;
  1099. /**
  1100. * Timestamp for the last state, used to determine timeouts.
  1101. * @type {number}
  1102. * @private
  1103. */
  1104. goog.testing.MultiTestRunner.TestFrame.prototype.lastStateTime_ = 0;
  1105. /**
  1106. * The state of the active test.
  1107. * @type {number}
  1108. * @private
  1109. */
  1110. goog.testing.MultiTestRunner.TestFrame.prototype.currentState_ = 0;
  1111. /** @override */
  1112. goog.testing.MultiTestRunner.TestFrame.prototype.disposeInternal = function() {
  1113. goog.testing.MultiTestRunner.TestFrame.superClass_.disposeInternal.call(this);
  1114. this.dom_.removeNode(this.iframeEl_);
  1115. this.eh_.dispose();
  1116. this.iframeEl_ = null;
  1117. };
  1118. /**
  1119. * Runs a test file in this test frame.
  1120. * @param {string} testFile The test to run.
  1121. */
  1122. goog.testing.MultiTestRunner.TestFrame.prototype.runTest = function(testFile) {
  1123. this.lastStateTime_ = this.startTime_ = goog.now();
  1124. if (!this.iframeEl_) {
  1125. this.createIframe_();
  1126. }
  1127. this.iframeLoaded_ = false;
  1128. this.currentState_ = 0;
  1129. this.isSuccess_ = null;
  1130. this.report_ = '';
  1131. this.testResults_ = {};
  1132. this.testFile_ = testFile;
  1133. try {
  1134. this.iframeEl_.src = this.basePath_ + testFile;
  1135. } catch (e) {
  1136. // Failures will trigger a JS exception on the local file system.
  1137. this.report_ = this.testFile_ + ' failed to load : ' + e.message;
  1138. this.isSuccess_ = false;
  1139. this.finish_();
  1140. return;
  1141. }
  1142. this.checkForCompletion_();
  1143. };
  1144. /**
  1145. * @return {string} The test file the TestFrame is running.
  1146. */
  1147. goog.testing.MultiTestRunner.TestFrame.prototype.getTestFile = function() {
  1148. return this.testFile_;
  1149. };
  1150. /**
  1151. * @return {!Object} Stats about the test run.
  1152. */
  1153. goog.testing.MultiTestRunner.TestFrame.prototype.getStats = function() {
  1154. return {
  1155. 'testFile': this.testFile_,
  1156. 'success': this.isSuccess_,
  1157. 'runTime': this.runTime_,
  1158. 'totalTime': this.totalTime_,
  1159. 'numFilesLoaded': this.numFilesLoaded_
  1160. };
  1161. };
  1162. /**
  1163. * @return {string} The report for the test run.
  1164. */
  1165. goog.testing.MultiTestRunner.TestFrame.prototype.getReport = function() {
  1166. return this.report_;
  1167. };
  1168. /**
  1169. * @return {!Object<string,!Array<!goog.testing.TestCase.IResult>>} The results
  1170. * per individual test in the file. Key is the test filename concatenated
  1171. * with the test name, and the array holds failures.
  1172. */
  1173. goog.testing.MultiTestRunner.TestFrame.prototype.getTestResults = function() {
  1174. var results = {};
  1175. for (var testName in this.testResults_) {
  1176. var testKey = this.testFile_.replace(/\.html$/, '');
  1177. // Concatenate with ":<testName>" unless the testName is equivalent to
  1178. // testFile_, which means the test timed out or had no test methods and
  1179. // there's no way to get the test method name.
  1180. if (testName != this.testFile_) {
  1181. testKey += ':' + testName;
  1182. }
  1183. results[testKey] = this.testResults_[testName];
  1184. }
  1185. return results;
  1186. };
  1187. /**
  1188. * @return {?boolean} Whether the test frame had a success.
  1189. */
  1190. goog.testing.MultiTestRunner.TestFrame.prototype.isSuccess = function() {
  1191. return this.isSuccess_;
  1192. };
  1193. /**
  1194. * Handles the TestFrame finishing a single test.
  1195. * @private
  1196. */
  1197. goog.testing.MultiTestRunner.TestFrame.prototype.finish_ = function() {
  1198. this.totalTime_ = goog.now() - this.startTime_;
  1199. // TODO(user): Fire an event instead?
  1200. if (this.getParent() && this.getParent().processResult) {
  1201. this.getParent().processResult(this);
  1202. }
  1203. };
  1204. /**
  1205. * Creates an iframe to run the tests in. For overriding in unit tests.
  1206. * @private
  1207. */
  1208. goog.testing.MultiTestRunner.TestFrame.prototype.createIframe_ = function() {
  1209. this.iframeEl_ = this.dom_.createDom(goog.dom.TagName.IFRAME);
  1210. this.getElement().appendChild(this.iframeEl_);
  1211. this.eh_.listen(this.iframeEl_, 'load', this.onIframeLoaded_);
  1212. };
  1213. /**
  1214. * Handles the iframe loading.
  1215. * @param {goog.events.BrowserEvent} e The load event.
  1216. * @private
  1217. */
  1218. goog.testing.MultiTestRunner.TestFrame.prototype.onIframeLoaded_ = function(e) {
  1219. this.iframeLoaded_ = true;
  1220. };
  1221. /**
  1222. * Checks the active test for completion, keeping track of the tests' various
  1223. * execution stages.
  1224. * @private
  1225. */
  1226. goog.testing.MultiTestRunner.TestFrame.prototype.checkForCompletion_ =
  1227. function() {
  1228. var js = goog.dom.getFrameContentWindow(this.iframeEl_);
  1229. switch (this.currentState_) {
  1230. case 0:
  1231. if (this.iframeLoaded_ && js['G_testRunner']) {
  1232. this.lastStateTime_ = goog.now();
  1233. this.currentState_++;
  1234. }
  1235. break;
  1236. case 1:
  1237. if (js['G_testRunner']['isInitialized']()) {
  1238. this.lastStateTime_ = goog.now();
  1239. this.currentState_++;
  1240. }
  1241. break;
  1242. case 2:
  1243. if (js['G_testRunner']['isFinished']()) {
  1244. var tr = js['G_testRunner'];
  1245. this.isSuccess_ = tr['isSuccess']();
  1246. this.report_ = tr['getReport'](this.verbosePasses_);
  1247. this.testResults_ = tr['getTestResults']();
  1248. // If there is a syntax error, or no tests, it's not possible to get the
  1249. // individual test method results from TestCase. So just create one here
  1250. // based on the test report and filename.
  1251. if (goog.object.isEmpty(this.testResults_)) {
  1252. // Existence of a report is a signal of a test failure by the test
  1253. // runner.
  1254. this.testResults_[this.testFile_] = this.isSuccess_ ? [] : [{
  1255. 'message': this.report_,
  1256. 'source': this.testFile_,
  1257. 'stacktrace': ''
  1258. }];
  1259. }
  1260. this.runTime_ = tr['getRunTime']();
  1261. this.numFilesLoaded_ = tr['getNumFilesLoaded']();
  1262. this.finish_();
  1263. return;
  1264. }
  1265. }
  1266. // Check to see if the test has timed out.
  1267. if (goog.now() - this.lastStateTime_ > this.timeoutMs_) {
  1268. this.report_ = this.testFile_ + ' timed out ' +
  1269. goog.testing.MultiTestRunner.STATES[this.currentState_];
  1270. this.testResults_[this.testFile_] =
  1271. [{'message': this.report_, 'source': this.testFile_, 'stacktrace': ''}];
  1272. this.isSuccess_ = false;
  1273. this.finish_();
  1274. return;
  1275. }
  1276. // Check again in 100ms.
  1277. goog.Timer.callOnce(this.checkForCompletion_, 100, this);
  1278. };