| <!DOCTYPE html> |
| <!-- |
| Copyright (c) 2014 The Chromium Authors. All rights reserved. |
| Use of this source code is governed by a BSD-style license that can be |
| found in the LICENSE file. |
| --> |
| |
| <link rel="import" href="/tracing/base/base.html"> |
| <link rel="import" href="/tracing/base/unittest.html"> |
| <link rel="import" href="/tracing/base/unittest/suite_loader.html"> |
| <link rel="import" href="/tracing/base/unittest/test_runner.html"> |
| <link rel="import" href="/tracing/base/unittest/html_test_results.html"> |
| <link rel="import" href="/tracing/ui/base/utils.html"> |
| |
| <style> |
| x-base-interactive-test-runner { |
| display: flex; |
| flex-direction: column; |
| flex: 0 0 auto; |
| } |
| |
| x-base-interactive-test-runner > * { |
| flex: 0 0 auto; |
| } |
| x-base-interactive-test-runner > #title { |
| font-size: 16pt; |
| } |
| |
| x-base-interactive-test-runner { |
| font-family: sans-serif; |
| } |
| |
| x-base-interactive-test-runner > h1 { |
| margin: 5px 0px 10px 0px; |
| } |
| |
| x-base-interactive-test-runner > #stats { |
| } |
| |
| x-base-interactive-test-runner > #controls { |
| display: block; |
| margin-bottom: 5px; |
| } |
| |
| x-base-interactive-test-runner > #controls > ul { |
| list-style-type: none; |
| padding: 0; |
| margin: 0; |
| } |
| |
| x-base-interactive-test-runner > #controls > ul > li { |
| float: left; |
| margin-right: 10px; |
| padding-top: 5px; |
| padding-bottom: 5px; |
| } |
| |
| x-base-interactive-test-runner > #shortform-results { |
| color: green; |
| height; 40px; |
| word-wrap: break-word; |
| } |
| |
| x-base-interactive-test-runner > #shortform-results > .fail { |
| color: darkred; |
| font-weight: bold; |
| } |
| |
| x-base-interactive-test-runner > #shortform-results > .flaky { |
| color: darkorange; |
| } |
| |
| x-base-interactive-test-runner > #results-container { |
| flex: 1 1 auto; |
| min-height: 0; |
| overflow: auto; |
| padding: 0 4px 0 4px; |
| } |
| |
| .unittest-pending { |
| color: orange; |
| } |
| .unittest-running { |
| color: orange; |
| font-weight: bold; |
| } |
| |
| .unittest-passed { |
| color: darkgreen; |
| } |
| |
| .unittest-failed { |
| color: darkred; |
| font-weight: bold; |
| } |
| |
| .unittest-flaky { |
| color: darkorange; |
| } |
| |
| .unittest-exception { |
| color: red; |
| font-weight: bold; |
| } |
| |
| .unittest-failure { |
| border: 1px solid grey; |
| border-radius: 5px; |
| padding: 5px; |
| } |
| </style> |
| |
| <template id="x-base-interactive-test-runner-template"> |
| <h1 id="title">Tests</h1> |
| <div id="stats"></div> |
| <div id="controls"> |
| <ul id="links"> |
| </ul> |
| <div style="clear: both;"></div> |
| |
| <div> |
| <span> |
| <label> |
| <input type="radio" name="test-type-to-run" value="unit" /> |
| Run unit tests |
| </label> |
| </span> |
| <span> |
| <label> |
| <input type="radio" name="test-type-to-run" value="perf" /> |
| Run perf tests |
| </label> |
| </span> |
| <span> |
| <label> |
| <input type="radio" name="test-type-to-run" value="all" /> |
| Run all tests |
| </label> |
| </span> |
| </div> |
| <span> |
| <label> |
| <input type="checkbox" id="short-format" /> Short format</label> |
| </span> |
| </div> |
| <div id="shortform-results"> |
| </div> |
| <div id="results-container"> |
| </div> |
| </template> |
| |
| <script> |
| 'use strict'; |
| |
| tr.exportTo('tr.b.unittest', function() { |
| var THIS_DOC = document.currentScript.ownerDocument; |
| var ALL_TEST_TYPES = 'all'; |
| |
| /** |
| * @constructor |
| */ |
| var InteractiveTestRunner = tr.ui.b.define('x-base-interactive-test-runner'); |
| |
| InteractiveTestRunner.prototype = { |
| __proto__: HTMLUnknownElement.prototype, |
| |
| decorate: function() { |
| this.allTests_ = undefined; |
| |
| this.suppressStateChange_ = false; |
| |
| this.testFilterString_ = ''; |
| this.testTypeToRun_ = tr.b.unittest.TestTypes.UNITTEST; |
| this.shortFormat_ = false; |
| this.testSuiteName_ = ''; |
| |
| this.rerunPending_ = false; |
| this.runner_ = undefined; |
| this.results_ = undefined; |
| this.headless_ = false; |
| |
| this.onResultsStatsChanged_ = this.onResultsStatsChanged_.bind(this); |
| this.onTestFailed_ = this.onTestFailed_.bind(this); |
| this.onTestFlaky_ = this.onTestFlaky_.bind(this); |
| this.onTestPassed_ = this.onTestPassed_.bind(this); |
| |
| this.appendChild(tr.ui.b.instantiateTemplate( |
| '#x-base-interactive-test-runner-template', THIS_DOC)); |
| |
| this.querySelector( |
| 'input[name=test-type-to-run][value=unit]').checked = true; |
| var testTypeToRunEls = tr.b.asArray(this.querySelectorAll( |
| 'input[name=test-type-to-run]')); |
| |
| testTypeToRunEls.forEach( |
| function(inputEl) { |
| inputEl.addEventListener( |
| 'click', this.onTestTypeToRunClick_.bind(this)); |
| }, this); |
| |
| var shortFormatEl = this.querySelector('#short-format'); |
| shortFormatEl.checked = this.shortFormat_; |
| shortFormatEl.addEventListener( |
| 'click', this.onShortFormatClick_.bind(this)); |
| this.updateShortFormResultsDisplay_(); |
| |
| // Oh, DOM, how I love you. Title is such a convenient property name and I |
| // refuse to change my worldview because of tooltips. |
| this.__defineSetter__( |
| 'title', |
| function(title) { |
| this.querySelector('#title').textContent = title; |
| }); |
| }, |
| |
| get allTests() { |
| return this.allTests_; |
| }, |
| |
| set allTests(allTests) { |
| this.allTests_ = allTests; |
| this.scheduleRerun_(); |
| }, |
| |
| get testLinks() { |
| return this.testLinks_; |
| }, |
| set testLinks(testLinks) { |
| this.testLinks_ = testLinks; |
| var linksEl = this.querySelector('#links'); |
| linksEl.textContent = ''; |
| this.testLinks_.forEach(function(l) { |
| var link = document.createElement('a'); |
| link.href = l.linkPath; |
| link.textContent = l.title; |
| |
| var li = document.createElement('li'); |
| li.appendChild(link); |
| |
| linksEl.appendChild(li); |
| }, this); |
| }, |
| |
| get testFilterString() { |
| return this.testFilterString_; |
| }, |
| |
| set testFilterString(testFilterString) { |
| this.testFilterString_ = testFilterString; |
| this.scheduleRerun_(); |
| if (!this.suppressStateChange_) |
| tr.b.dispatchSimpleEvent(this, 'statechange'); |
| }, |
| |
| get shortFormat() { |
| return this.shortFormat_; |
| }, |
| |
| set shortFormat(shortFormat) { |
| this.shortFormat_ = shortFormat; |
| this.querySelector('#short-format').checked = shortFormat; |
| if (this.results_) |
| this.results_.shortFormat = shortFormat; |
| if (!this.suppressStateChange_) |
| tr.b.dispatchSimpleEvent(this, 'statechange'); |
| }, |
| |
| onShortFormatClick_: function(e) { |
| this.shortFormat_ = this.querySelector('#short-format').checked; |
| this.updateShortFormResultsDisplay_(); |
| this.updateResultsGivenShortFormat_(); |
| if (!this.suppressStateChange_) |
| tr.b.dispatchSimpleEvent(this, 'statechange'); |
| }, |
| |
| updateShortFormResultsDisplay_: function() { |
| var display = this.shortFormat_ ? '' : 'none'; |
| this.querySelector('#shortform-results').style.display = display; |
| }, |
| |
| updateResultsGivenShortFormat_: function() { |
| if (!this.results_) |
| return; |
| |
| if (this.testFilterString_.length || this.testSuiteName_.length) |
| this.results_.showHTMLOutput = true; |
| else |
| this.results_.showHTMLOutput = false; |
| this.results_.showPendingAndPassedTests = this.shortFormat_; |
| }, |
| |
| get testTypeToRun() { |
| return this.testTypeToRun_; |
| }, |
| |
| set testTypeToRun(testTypeToRun) { |
| this.testTypeToRun_ = testTypeToRun; |
| var sel; |
| switch (testTypeToRun) { |
| case tr.b.unittest.TestTypes.UNITTEST: |
| sel = 'input[name=test-type-to-run][value=unit]'; |
| break; |
| case tr.b.unittest.TestTypes.PERFTEST: |
| sel = 'input[name=test-type-to-run][value=perf]'; |
| break; |
| case ALL_TEST_TYPES: |
| sel = 'input[name=test-type-to-run][value=all]'; |
| break; |
| default: |
| throw new Error('Invalid test type to run: ' + testTypeToRun); |
| } |
| this.querySelector(sel).checked = true; |
| this.scheduleRerun_(); |
| if (!this.suppressStateChange_) |
| tr.b.dispatchSimpleEvent(this, 'statechange'); |
| }, |
| |
| onTestTypeToRunClick_: function(e) { |
| switch (e.target.value) { |
| case 'unit': |
| this.testTypeToRun_ = tr.b.unittest.TestTypes.UNITTEST; |
| break; |
| case 'perf': |
| this.testTypeToRun_ = tr.b.unittest.TestTypes.PERFTEST; |
| break; |
| case 'all': |
| this.testTypeToRun_ = ALL_TEST_TYPES; |
| break; |
| default: |
| throw new Error('Inalid test type: ' + e.target.value); |
| } |
| |
| this.scheduleRerun_(); |
| if (!this.suppressStateChange_) |
| tr.b.dispatchSimpleEvent(this, 'statechange'); |
| }, |
| |
| onTestPassed_: function() { |
| this.querySelector('#shortform-results'). |
| appendChild(document.createTextNode('.')); |
| }, |
| |
| onTestFailed_: function() { |
| var span = document.createElement('span'); |
| span.classList.add('fail'); |
| span.appendChild(document.createTextNode('F')); |
| this.querySelector('#shortform-results').appendChild(span); |
| }, |
| |
| onTestFlaky_: function() { |
| var span = document.createElement('span'); |
| span.classList.add('flaky'); |
| span.appendChild(document.createTextNode('~')); |
| this.querySelector('#shortform-results').appendChild(span); |
| }, |
| |
| onResultsStatsChanged_: function() { |
| var statsEl = this.querySelector('#stats'); |
| var stats = this.results_.getStats(); |
| var numTestsOverall = this.runner_.testCases.length; |
| var numTestsThatRan = stats.numTestsThatPassed + |
| stats.numTestsThatFailed + stats.numFlakyTests; |
| statsEl.innerHTML = |
| '<span>' + numTestsThatRan + '/' + numTestsOverall + |
| '</span> tests run, ' + |
| '<span class="unittest-failed">' + stats.numTestsThatFailed + |
| '</span> failures, ' + |
| '<span class="unittest-flaky">' + stats.numFlakyTests + |
| '</span> flaky, ' + |
| ' in ' + stats.totalRunTime.toFixed(2) + 'ms.'; |
| }, |
| |
| scheduleRerun_: function() { |
| if (this.rerunPending_) |
| return; |
| if (this.runner_) { |
| this.rerunPending_ = true; |
| this.runner_.beginToStopRunning(); |
| var doRerun = function() { |
| this.rerunPending_ = false; |
| this.scheduleRerun_(); |
| }.bind(this); |
| this.runner_.runCompletedPromise.then( |
| doRerun, doRerun); |
| return; |
| } |
| this.beginRunning_(); |
| }, |
| |
| beginRunning_: function() { |
| var resultsContainer = this.querySelector('#results-container'); |
| if (this.results_) { |
| this.results_.removeEventListener('testpassed', this.onTestPassed_); |
| this.results_.removeEventListener('testfailed', this.onTestFailed_); |
| this.results_.removeEventListener('testflaky', this.onTestFlaky_); |
| this.results_.removeEventListener('statschange', |
| this.onResultsStatsChanged_); |
| delete this.results_.getHRefForTestCase; |
| resultsContainer.removeChild(this.results_); |
| } |
| |
| this.results_ = new tr.b.unittest.HTMLTestResults(); |
| this.results_.headless = this.headless_; |
| this.results_.getHRefForTestCase = this.getHRefForTestCase.bind(this); |
| this.updateResultsGivenShortFormat_(); |
| |
| this.results_.shortFormat = this.shortFormat_; |
| this.results_.addEventListener('testpassed', this.onTestPassed_); |
| this.results_.addEventListener('testfailed', this.onTestFailed_); |
| this.results_.addEventListener('testflaky', this.onTestFlaky_); |
| this.results_.addEventListener('statschange', |
| this.onResultsStatsChanged_); |
| resultsContainer.appendChild(this.results_); |
| |
| var tests = this.allTests_.filter(function(test) { |
| var i = test.fullyQualifiedName.indexOf(this.testFilterString_); |
| if (i == -1) |
| return false; |
| if (this.testTypeToRun_ !== ALL_TEST_TYPES && |
| test.testType !== this.testTypeToRun_) |
| return false; |
| return true; |
| }, this); |
| |
| this.runner_ = new tr.b.unittest.TestRunner(this.results_, tests); |
| this.runner_.beginRunning(); |
| |
| this.runner_.runCompletedPromise.then( |
| this.runCompleted_.bind(this), |
| this.runCompleted_.bind(this)); |
| }, |
| |
| setState: function(state, opt_suppressStateChange) { |
| this.suppressStateChange_ = true; |
| if (state.testFilterString !== undefined) |
| this.testFilterString = state.testFilterString; |
| else |
| this.testFilterString = ''; |
| |
| if (state.shortFormat === undefined) |
| this.shortFormat = false; |
| else |
| this.shortFormat = state.shortFormat; |
| |
| if (state.testTypeToRun === undefined) |
| this.testTypeToRun = tr.b.unittest.TestTypes.UNITTEST; |
| else |
| this.testTypeToRun = state.testTypeToRun; |
| |
| this.testSuiteName_ = state.testSuiteName || ''; |
| this.headless_ = state.headless || false; |
| |
| if (!opt_suppressStateChange) |
| this.suppressStateChange_ = false; |
| |
| this.onShortFormatClick_(); |
| this.scheduleRerun_(); |
| this.suppressStateChange_ = false; |
| }, |
| |
| getDefaultState: function() { |
| return { |
| testFilterString: '', |
| testSuiteName: '', |
| shortFormat: false, |
| testTypeToRun: tr.b.unittest.TestTypes.UNITTEST |
| }; |
| }, |
| |
| getState: function() { |
| return { |
| testFilterString: this.testFilterString_, |
| testSuiteName: this.testSuiteName_, |
| shortFormat: this.shortFormat_, |
| testTypeToRun: this.testTypeToRun_ |
| }; |
| }, |
| |
| getHRefForTestCase: function(testCases) { |
| return undefined; |
| }, |
| |
| runCompleted_: function() { |
| this.runner_ = undefined; |
| } |
| }; |
| |
| function loadAndRunTests(runnerConfig) { |
| |
| // The test runner no-ops pushState so keep it around. |
| var realWindowHistoryPushState = window.history.pushState.bind( |
| window.history); |
| |
| function stateToSearchString(defaultState, state) { |
| var parts = []; |
| for (var k in state) { |
| if (state[k] === defaultState[k]) |
| continue; |
| var v = state[k]; |
| var kv; |
| if (v === true) { |
| kv = k; |
| } else if (v === false) { |
| kv = k + '=false'; |
| } else if (v === '') { |
| continue; |
| } else { |
| kv = k + '=' + v; |
| } |
| parts.push(kv); |
| } |
| return parts.join('&'); |
| } |
| |
| function stateFromSearchString(string) { |
| var state = {}; |
| string.split('&').forEach(function(part) { |
| if (part == '') |
| return; |
| var kv = part.split('='); |
| var k, v; |
| if (kv.length == 1) { |
| k = kv[0]; |
| v = true; |
| } else { |
| k = kv[0]; |
| if (kv[1] == 'false') |
| v = false; |
| else |
| v = kv[1]; |
| } |
| state[k] = v; |
| }); |
| return state; |
| } |
| |
| function getSuiteRelpathsToLoad(state) { |
| if (state.testSuiteName) { |
| return new Promise(function(resolve) { |
| var parts = state.testSuiteName.split('.'); |
| var testSuiteRelPath = '/' + parts.join('/') + '.html'; |
| |
| var suiteRelpathsToLoad = [testSuiteRelPath]; |
| resolve(suiteRelpathsToLoad); |
| }); |
| } |
| return runnerConfig.getAllSuiteRelPathsAsync(); |
| } |
| |
| |
| function loadAndRunTestsImpl() { |
| var state = stateFromSearchString( |
| window.location.search.substring(1)); |
| updateTitle(state); |
| |
| |
| showLoadingOverlay(); |
| |
| var loader; |
| var p = getSuiteRelpathsToLoad(state); |
| p = p.then( |
| function(suiteRelpathsToLoad) { |
| loader = new tr.b.unittest.SuiteLoader(suiteRelpathsToLoad); |
| return loader.allSuitesLoadedPromise; |
| }, |
| function(e) { |
| hideLoadingOverlay(); |
| throw e; |
| }); |
| p = p.then( |
| function() { |
| hideLoadingOverlay(); |
| Polymer.whenReady(function() { |
| runTests(loader, state); |
| }); |
| }, |
| function(err) { |
| hideLoadingOverlay(); |
| tr.showPanic('Module loading failure', err); |
| throw err; |
| }); |
| return p; |
| } |
| |
| function showLoadingOverlay() { |
| var overlay = document.createElement('div'); |
| overlay.id = 'tests-loading-overlay'; |
| overlay.style.backgroundColor = 'white'; |
| overlay.style.boxSizing = 'border-box'; |
| overlay.style.color = 'black'; |
| overlay.style.display = 'flex'; |
| overlay.style.height = '100%'; |
| overlay.style.left = 0; |
| overlay.style.padding = '8px'; |
| overlay.style.position = 'fixed'; |
| overlay.style.top = 0; |
| overlay.style.flexDirection = 'column'; |
| overlay.style.width = '100%'; |
| |
| var element = document.createElement('div'); |
| element.style.flex = '1 1 auto'; |
| element.style.overflow = 'auto'; |
| overlay.appendChild(element); |
| |
| element.textContent = 'Loading tests...'; |
| document.body.appendChild(overlay); |
| } |
| function hideLoadingOverlay() { |
| var overlay = document.body.querySelector('#tests-loading-overlay'); |
| document.body.removeChild(overlay); |
| } |
| |
| function updateTitle(state) { |
| var testFilterString = state.testFilterString || ''; |
| var testSuiteName = state.testSuiteName || ''; |
| |
| var title; |
| if (testSuiteName && testFilterString.length) { |
| title = testFilterString + ' in ' + testSuiteName; |
| } else if (testSuiteName) { |
| title = testSuiteName; |
| } else if (testFilterString) { |
| title = testFilterString + ' in all tests'; |
| } else { |
| title = runnerConfig.title; |
| } |
| |
| if (state.shortFormat) |
| title += '(s)'; |
| document.title = title; |
| var runner = document.querySelector('x-base-interactive-test-runner'); |
| if (runner) |
| runner.title = title; |
| } |
| |
| function runTests(loader, state) { |
| var runner = new tr.b.unittest.InteractiveTestRunner(); |
| runner.style.width = '100%'; |
| runner.style.height = '100%'; |
| runner.testLinks = runnerConfig.testLinks; |
| runner.allTests = loader.getAllTests(); |
| document.body.appendChild(runner); |
| |
| runner.setState(state); |
| updateTitle(state); |
| |
| runner.addEventListener('statechange', function() { |
| var state = runner.getState(); |
| var stateString = stateToSearchString(runner.getDefaultState(), |
| state); |
| if (window.location.search.substring(1) == stateString) |
| return; |
| |
| updateTitle(state); |
| var stateURL; |
| if (stateString.length > 0) |
| stateURL = window.location.pathname + '?' + stateString; |
| else |
| stateURL = window.location.pathname; |
| realWindowHistoryPushState(state, document.title, stateURL); |
| }); |
| |
| window.addEventListener('popstate', function(state) { |
| runner.setState(state, true); |
| }); |
| |
| runner.getHRefForTestCase = function(testCase) { |
| var state = runner.getState(); |
| if (state.testFilterString === '' && |
| state.testSuiteName === '') { |
| state.testSuiteName = testCase.suite.name; |
| state.testFilterString = ''; |
| state.shortFormat = false; |
| } else { |
| state.testSuiteName = testCase.suite.name; |
| state.testFilterString = testCase.name; |
| state.shortFormat = false; |
| } |
| var stateString = stateToSearchString(runner.getDefaultState(), |
| state); |
| if (stateString.length > 0) |
| return window.location.pathname + '?' + stateString; |
| else |
| return window.location.pathname; |
| }; |
| } |
| |
| loadAndRunTestsImpl(); |
| } |
| |
| return { |
| InteractiveTestRunner: InteractiveTestRunner, |
| loadAndRunTests: loadAndRunTests |
| }; |
| }); |
| </script> |