Index: webkit/tools/layout_tests/flakiness_dashboard.html |
=================================================================== |
--- webkit/tools/layout_tests/flakiness_dashboard.html (revision 25738) |
+++ webkit/tools/layout_tests/flakiness_dashboard.html (working copy) |
@@ -15,17 +15,22 @@ |
font-size: 16px; |
margin-bottom: .25em; |
} |
- form { |
- margin: 3px 0; |
+ #max-results-form { |
+ display: inline; |
+ } |
+ #max-results-input { |
+ width: 30px; |
+ } |
+ #tests-form { |
display: -webkit-box; |
} |
- form > * { |
+ #tests-form > * { |
display: -webkit-box; |
} |
- form > div { |
+ #tests-form > div { |
-webkit-box-flex: 0; |
} |
- form > input { |
+ #tests-input { |
-webkit-box-flex: 1; |
} |
.test-link { |
@@ -172,60 +177,268 @@ |
* -add the builder name to the list of builders below. |
*/ |
- // Default to layout_tests. |
- var testType = 'layout_test_results'; |
- var params = window.location.search.substring(1).split('&'); |
- for (var i = 0; i < params.length; i++) { |
- var thisParam = params[i].split('='); |
- if (thisParam[0] == 'testtype') { |
- testType = thisParam[1]; |
- break; |
+ // CONSTANTS |
+ var FORWARD = 'forward'; |
+ var BACKWARD = 'backward'; |
+ var TEST_URL_BASE_PATH = |
+ 'http://trac.webkit.org/projects/webkit/browser/trunk/'; |
+ var BUILDERS_BASE_PATH = |
+ 'http://build.chromium.org/buildbot/waterfall/builders/'; |
+ var EXPECTATIONS_MAP = { |
+ 'T': 'TIMEOUT', |
+ 'C': 'CRASH', |
+ 'P': 'PASS', |
+ 'F': 'TEXT FAIL', |
+ 'S': 'SIMPLIFIED', |
+ 'I': 'IMAGE', |
+ 'O': 'OTHER', |
+ 'N': 'NO DATA' |
+ }; |
+ var PLATFORMS = {'MAC': 'MAC', 'LINUX': 'LINUX', 'WIN': 'WIN'}; |
+ var BUILD_TYPES = {'DEBUG': 'DBG', 'RELEASE': 'RELEASE'}; |
+ |
+ // GLOBALS |
+ // The DUMMYVALUE gets shifted off the array in the first call to |
+ // generatePage. |
+ var tableHeaders = ['DUMMYVALUE', 'bugs', 'modifiers', 'expectations', |
+ 'missing', 'extra', 'slowest run', |
+ 'flakiness (numbers are runtimes in seconds)']; |
+ var perBuilderPlatformAndBuildType = {}; |
+ var perBuilderFailures = {}; |
+ // Map of builder to arrays of tests that are listed in the expectations file |
+ // but have for that builder. |
+ var perBuilderWithExpectationsButNoFailures = {}; |
+ |
+ |
+ // Generic utility functions. |
+ function $(id) { |
+ return document.getElementById(id); |
+ } |
+ |
+ function stringContains(a, b) { |
+ return a.indexOf(b) != -1; |
+ } |
+ |
+ function isValidName(str) { |
+ return str.match(/[A-Za-z0-9\-\_,]/); |
+ } |
+ |
+ function trimString(str) { |
+ return str.replace(/^\s+|\s+$/g, ''); |
+ } |
+ |
+ function anyKeyInString(object, string) { |
+ for (var key in object) { |
+ if (stringContains(string, key)) |
+ return true; |
} |
+ return false; |
} |
- // Map of builderName (the name shown in the waterfall) |
- // to builderPath (the path used in the builder's URL) |
- // TODO(ojan): Make this switch based off of the testType. |
- var builders = { |
- 'Webkit': 'webkit-rel', |
- 'Webkit (dbg)(1)': 'webkit-dbg-1', |
- 'Webkit (dbg)(2)': 'webkit-dbg-2', |
- 'Webkit (dbg)(3)': 'webkit-dbg-3', |
- 'Webkit Linux': 'webkit-rel-linux', |
- 'Webkit Linux (dbg)(1)': 'webkit-dbg-linux-1', |
- 'Webkit Linux (dbg)(2)': 'webkit-dbg-linux-2', |
- 'Webkit Linux (dbg)(3)': 'webkit-dbg-linux-3', |
- 'Webkit Mac10.5': 'webkit-rel-mac5', |
- 'Webkit Mac10.5 (dbg)(1)': 'webkit-dbg-mac5-1', |
- 'Webkit Mac10.5 (dbg)(2)': 'webkit-dbg-mac5-2', |
- 'Webkit Mac10.5 (dbg)(3)': 'webkit-dbg-mac5-3' |
+ function validateParameter(state, key, value, validateFn) { |
+ if (validateFn()) { |
+ state[key] = value; |
+ } else { |
+ console.log(key + ' value is not valid: ' + value); |
+ } |
+ } |
+ |
+ /** |
+ * Parses a string (e.g. window.location.hash) and calls |
+ * validValueHandler(key, value) for each key-value pair in the string. |
+ */ |
+ function parseParameters(parameterStr, validValueHandler) { |
+ var params = parameterStr.split('&'); |
+ for (var i = 0; i < params.length; i++) { |
+ var thisParam = params[i].split('='); |
+ if (thisParam.length != 2) { |
+ console.log('Invalid query parameter: ' + params[i]); |
+ continue; |
+ } |
+ |
+ var key = thisParam[0]; |
+ var value = decodeURIComponent(thisParam[1]); |
+ if (!validValueHandler(key, value)) |
+ console.log('Invalid key: ' + key + ' value: ' + value); |
+ } |
+ } |
+ |
+ function appendScript(path) { |
+ var script = document.createElement('script'); |
+ script.src = path; |
+ document.getElementsByTagName('head')[0].appendChild(script); |
+ } |
+ |
+ |
+ // Parse query parameters. |
+ var queryState = {'debug': false, 'testType': 'layout_test_results'}; |
+ |
+ function handleValidQueryParameter(key, value) { |
+ switch (key) { |
+ case 'testType': |
+ validateParameter(queryState, key, value, |
+ function() { return isValidName(value); }); |
+ |
+ return true; |
+ |
+ case 'debug': |
+ queryState[key] = value == 'true'; |
+ |
+ return true; |
+ |
+ default: |
+ return false; |
+ } |
+ } |
+ |
+ parseParameters(window.location.search.substring(1), |
+ handleValidQueryParameter); |
+ if (queryState['debug']) { |
+ // In debug mode point to the results.json and expectations.json in the |
+ // local tree. Useful for debugging changes to the python JSON generator. |
+ var builders = {'DUMMY_BUILDER_NAME': ''}; |
+ var builderBase = '../../Debug/'; |
+ queryState['testType'] = 'layout-test-results'; |
+ } else { |
+ // Map of builderName (the name shown in the waterfall) |
+ // to builderPath (the path used in the builder's URL) |
+ // TODO(ojan): Make this switch based off of the testType. |
+ var builders = { |
+ 'Webkit': 'webkit-rel', |
+ 'Webkit (dbg)(1)': 'webkit-dbg-1', |
+ 'Webkit (dbg)(2)': 'webkit-dbg-2', |
+ 'Webkit (dbg)(3)': 'webkit-dbg-3', |
+ 'Webkit Linux': 'webkit-rel-linux', |
+ 'Webkit Linux (dbg)(1)': 'webkit-dbg-linux-1', |
+ 'Webkit Linux (dbg)(2)': 'webkit-dbg-linux-2', |
+ 'Webkit Linux (dbg)(3)': 'webkit-dbg-linux-3', |
+ 'Webkit Mac10.5': 'webkit-rel-mac5', |
+ 'Webkit Mac10.5 (dbg)(1)': 'webkit-dbg-mac5-1', |
+ 'Webkit Mac10.5 (dbg)(2)': 'webkit-dbg-mac5-2', |
+ 'Webkit Mac10.5 (dbg)(3)': 'webkit-dbg-mac5-3' |
+ }; |
+ var builderBase = 'http://build.chromium.org/buildbot/'; |
+ } |
+ |
+ // Parse hash parameters. |
+ // Permalinkable state of the page. |
+ var currentState = {}; |
+ |
+ var defaultStateValues = { |
+ sortOrder: BACKWARD, |
+ sortColumn: 'flakiness', |
+ showWontFix: false, |
+ showCorrectExpectations: false, |
+ showFlaky: true, |
+ maxResults: 200 |
}; |
+ for (var builder in builders) { |
+ defaultStateValues.builder = builder; |
+ break; |
+ } |
+ |
+ function fillDefaultStateValues() { |
+ // tests has no states with default values. |
+ if (currentState.tests) |
+ return; |
+ |
+ for (var state in defaultStateValues) { |
+ if (!(state in currentState)) |
+ currentState[state] = defaultStateValues[state]; |
+ } |
+ } |
+ |
+ function handleValidHashParameter(key, value) { |
+ switch(key) { |
+ case 'tests': |
+ validateParameter(currentState, key, value, |
+ function() { return isValidName(value); }); |
arv (Not doing code reviews)
2009/09/09 23:46:25
function() {
return isValidName(value);
}
|
+ |
+ return true; |
+ |
+ case 'builder': |
+ validateParameter(currentState, key, value, |
+ function() { return value in builders; }); |
+ |
+ return true; |
+ |
+ case 'sortColumn': |
+ validateParameter(currentState, key, value, |
+ function() { |
+ for (var i = 0; i < tableHeaders.length; i++) { |
+ if (value == getSortColumnFromTableHeader(tableHeaders[i])) |
+ return true; |
+ } |
+ return value == 'test'; |
+ }); |
+ |
+ return true; |
+ |
+ case 'sortOrder': |
+ validateParameter(currentState, key, value, |
+ function() { return value == FORWARD || value == BACKWARD; }); |
+ |
+ return true; |
+ |
+ case 'maxResults': |
+ validateParameter(currentState, key, value, |
+ function() { return value.match(/^\d+$/) }); |
+ |
+ return true; |
+ |
+ case 'showWontFix': |
+ case 'showCorrectExpectations': |
+ case 'showFlaky': |
+ currentState[key] = value == 'true'; |
+ |
+ return true; |
+ |
+ default: |
+ return false; |
+ } |
+ } |
+ |
+ // Keep the location around for detecting changes to hash arguments |
+ // manually typed into the URL bar. |
+ var oldLocation; |
+ |
+ function parseAllParameters() { |
+ oldLocation = window.location.href; |
+ parseParameters(window.location.search.substring(1), |
+ handleValidQueryParameter); |
+ parseParameters(window.location.hash.substring(1), |
+ handleValidHashParameter); |
+ fillDefaultStateValues(); |
+ } |
+ |
+ parseAllParameters(); |
+ |
+ // Append JSON script elements. |
var resultsByBuilder = {}; |
// Maps test path to an array of {builder, testResults} objects. |
var testToResultsMap = {}; |
var expectationsByTest = {}; |
function ADD_RESULTS(builds) { |
for (var builderName in builds) { |
- resultsByBuilder[builderName] = builds[builderName]; |
+ if (builderName != 'version') |
+ resultsByBuilder[builderName] = builds[builderName]; |
} |
generatePage(); |
} |
- var BUILDER_BASE = 'http://build.chromium.org/buildbot/'; |
+ |
function getPathToBuilderResultsFile(builderName) { |
- return BUILDER_BASE + testType + '/' + builders[builderName] + '/'; |
+ return builderBase + queryState['testType'] + '/' + |
+ builders[builderName] + '/'; |
} |
+ |
for (var builderName in builders) { |
- var script = document.createElement('script'); |
- script.src = getPathToBuilderResultsFile(builderName) + 'results.json'; |
- document.getElementsByTagName('head')[0].appendChild(script); |
+ appendScript(getPathToBuilderResultsFile(builderName) + 'results.json'); |
} |
- var script = document.createElement('script'); |
// Grab expectations file from any builder. |
- script.src = getPathToBuilderResultsFile(builderName) + 'expectations.json'; |
- document.getElementsByTagName('head')[0].appendChild(script); |
+ appendScript(getPathToBuilderResultsFile(builderName) + 'expectations.json'); |
var expectationsLoaded = false; |
function ADD_EXPECTATIONS(expectations) { |
@@ -234,39 +447,7 @@ |
generatePage(); |
} |
- // CONSTANTS |
- var FORWARD = 'forward'; |
- var BACKWARD = 'backward'; |
- var TEST_URL_BASE_PATH = |
- 'http://trac.webkit.org/projects/webkit/browser/trunk/'; |
- var BUILDERS_BASE_PATH = |
- 'http://build.chromium.org/buildbot/waterfall/builders/'; |
- var EXPECTATIONS_MAP = { |
- 'T': 'TIMEOUT', |
- 'C': 'CRASH', |
- 'P': 'PASS', |
- 'F': 'TEXT FAIL', |
- 'S': 'SIMPLIFIED', |
- 'I': 'IMAGE', |
- 'O': 'OTHER', |
- 'N': 'NO DATA' |
- }; |
- var PLATFORMS = {'MAC': 'MAC', 'LINUX': 'LINUX', 'WIN': 'WIN'}; |
- var BUILD_TYPES = {'DEBUG': 'DBG', 'RELEASE': 'RELEASE'}; |
- // GLOBALS |
- // The DUMMYVALUE gets shifted off the array in the first call to |
- // generatePage. |
- var tableHeaders = ['DUMMYVALUE', 'bugs', 'modifiers', 'expectations', |
- 'missing', 'extra', 'slowest run', |
- 'flakiness (numbers are runtimes in seconds)']; |
- var perBuilderPlatformAndBuildType = {}; |
- var oldLocation; |
- var perBuilderFailures = {}; |
- // Map of builder to arrays of tests that are listed in the expectations file |
- // but have for that builder. |
- var perBuilderWithExpectationsButNoFailures = {}; |
- |
function createResultsObjectForTest(test) { |
return { |
test: test, |
@@ -297,26 +478,6 @@ |
} |
} |
- function $(id) { |
- return document.getElementById(id); |
- } |
- |
- function stringContains(a, b) { |
- return a.indexOf(b) != -1; |
- } |
- |
- function trimString(str) { |
- return str.replace(/^\s+|\s+$/g, ''); |
- } |
- |
- function anyKeyInString(object, string) { |
- for (var key in object) { |
- if (stringContains(string, key)) |
- return true; |
- } |
- return false; |
- } |
- |
/** |
* Returns whether the given string of modifiers applies to the platform and |
* build type of the given builder. |
@@ -585,17 +746,16 @@ |
htmlArrays.modifiers.join('<div class=separator></div>'); |
} |
- var rawResults = resultsByBuilder[builderName].tests[test].results; |
+ var rawTest = resultsByBuilder[builderName].tests[test]; |
+ resultsForTest.rawTimes = rawTest.times; |
+ var rawResults = rawTest.results; |
resultsForTest.rawResults = rawResults; |
- var results = rawResults.split(''); |
+ resultsForTest.flips = rawResults.length - 1; |
var unexpectedExpectations = []; |
var resultsMap = {} |
- for (var i = 0; i < results.length - 1; i++) { |
- if (results[i] != results[i + 1]) |
- resultsForTest.flips++; |
- |
- var expectation = getExpectationsFileStringForResult(results[i]); |
+ for (var i = 0; i < rawResults.length; i++) { |
+ var expectation = getExpectationsFileStringForResult(rawResults[i][1]); |
resultsMap[expectation] = true; |
} |
@@ -618,20 +778,25 @@ |
missingExpectations.push(result); |
} |
- // TODO(ojan): Make this detect the case of a test that has NODATA, |
- // then fails for a few runs, then passes for the rest. We should |
- // consider that as meetsExpectations since every new test will have |
- // that pattern. |
+ var times = resultsByBuilder[builderName].tests[test].times; |
+ for (var i = 0; i < times.length; i++) { |
+ resultsForTest.slowestTime = Math.max(resultsForTest.slowestTime, |
+ times[i][1]); |
+ } |
+ |
+ if (resultsForTest.slowestTime && |
+ (!resultsForTest.expectations || |
+ !stringContains(resultsForTest.expectations, 'TIMEOUT')) && |
+ (!resultsForTest.modifiers || |
+ !stringContains(resultsForTest.modifiers, 'SLOW'))) { |
+ missingExpectations.push('SLOW'); |
+ } |
+ |
resultsForTest.meetsExpectations = |
!missingExpectations.length && !extraExpectations.length; |
resultsForTest.missing = missingExpectations.sort().join(' '); |
resultsForTest.extra = extraExpectations.sort().join(' '); |
- var times = resultsByBuilder[builderName].tests[test].times; |
- resultsForTest.slowestTime = Math.max.apply(null, times) |
- |
- resultsForTest.html = getHtmlForTestResults(builderName, test); |
- |
failures.push(resultsForTest); |
if (!testToResultsMap[test]) |
@@ -672,29 +837,49 @@ |
return bugs; |
} |
- function didTestPassAllRuns(builderName, testPath) { |
- var numBuilds = resultsByBuilder[builderName].buildNumbers.length; |
- var passingResults = Array(numBuilds + 1).join('P'); |
- var results = resultsByBuilder[builderName].tests[testPath].results; |
- return results == passingResults; |
- } |
- |
function loadBuilderPageForBuildNumber(builderName, buildNumber) { |
window.open(BUILDERS_BASE_PATH + builderName + '/builds/' + buildNumber); |
} |
- function getHtmlForTestResults(builderName, testPath) { |
+ function getHtmlForTestResults(test, builder) { |
var html = ''; |
- var test = resultsByBuilder[builderName].tests[testPath]; |
- var results = test.results.split(''); |
- var times = test.times; |
- var buildNumbers = resultsByBuilder[builderName].buildNumbers; |
- for (var i = 0; i < results.length; i++) { |
+ var results = test.rawResults.concat(); |
+ var times = test.rawTimes.concat(); |
+ var buildNumbers = resultsByBuilder[builder].buildNumbers; |
+ |
+ var indexToReplaceCurrentResult = -1; |
+ var indexToReplaceCurrentTime = -1; |
+ var currentResultArray, currentTimeArray, currentResult, innerHTML; |
+ for (var i = 0; |
+ i < buildNumbers.length && i < currentState.maxResults; |
+ i++) { |
+ if (i > indexToReplaceCurrentResult) { |
+ currentResultArray = results.shift(); |
+ if (currentResultArray) { |
+ currentResult = currentResultArray[1]; |
+ indexToReplaceCurrentResult += currentResultArray[0]; |
+ } else { |
+ currentResult = 'N'; |
+ indexToReplaceCurrentResult += buildNumbers.length; |
+ } |
+ } |
+ |
+ if (i > indexToReplaceCurrentTime) { |
+ currentTimeArray = times.shift(); |
+ var currentTime = 0; |
+ if (currentResultArray) { |
+ currentTime = currentTimeArray[1]; |
+ indexToReplaceCurrentTime += currentTimeArray[0]; |
+ } else { |
+ indexToReplaceCurrentTime += buildNumbers.length; |
+ } |
+ innerHTML = currentTime || ' '; |
+ } |
+ |
var buildNumber = buildNumbers[i]; |
- var innerHTML = times[i] > 0 ? times[i] : ' '; |
html += '<td title="Build:' + buildNumber + '" class="results ' + |
- results[i] + '" onclick=\'loadBuilderPageForBuildNumber("' + |
- builderName + '","' + buildNumber + '")\'>' + innerHTML + '</td>'; |
+ currentResult + '" onclick=\'loadBuilderPageForBuildNumber("' + |
+ builder + '","' + buildNumber + '")\'>' + innerHTML + '</td>'; |
} |
return html; |
} |
@@ -717,7 +902,7 @@ |
* avoid common non-flaky cases. |
*/ |
function isTestFlaky(testResult) { |
- return testResult.flips > 1 && !isFixedNewTest(testResult); |
+ return testResult.flips > 1 && !isFixedTest(testResult); |
} |
/** |
@@ -727,12 +912,21 @@ |
* Where that middle part can be a series of F's, S's or I's for the |
* different types of failures. |
*/ |
- function isFixedNewTest(testResult) { |
- if (testResult.isFixedNewTest === undefined) { |
- testResult.isFixedNewTest = |
- testResult.rawResults.match(/^P+(S+|F+|I+)N+$/); |
+ function isFixedTest(testResult) { |
+ if (testResult.isFixedTest === undefined) { |
+ var results = testResult.rawResults; |
+ var isFixedTest = results[0][1] == 'P'; |
+ if (isFixedTest && results.length > 1) { |
+ var secondResult = results[1][1]; |
+ isFixedTest = secondResult == 'S' || secondResult == 'F' || |
+ secondResult == 'I'; |
+ } |
+ if (isFixedTest && results.length > 2) { |
+ isFixedTest = results.length == 3 && results[2][1] == 'N'; |
+ } |
+ testResult.isFixedTest = isFixedTest; |
} |
- return testResult.isFixedNewTest; |
+ return testResult.isFixedTest; |
} |
/** |
@@ -748,7 +942,7 @@ |
if (testResult.isWontFix && !currentState.showWontFix) |
return true; |
- if ((testResult.meetsExpectations || isFixedNewTest(testResult)) && |
+ if ((testResult.meetsExpectations || isFixedTest(testResult)) && |
!currentState.showCorrectExpectations) { |
// Only hide flaky tests that match their expectations if showFlaky |
// is false. |
@@ -758,17 +952,17 @@ |
return !currentState.showFlaky && isTestFlaky(testResult); |
} |
- function getHTMLForSingleTestRow(test, opt_builder) { |
+ function getHTMLForSingleTestRow(test, builder, opt_isCrossBuilderView) { |
if (shouldHideTest(test)) { |
// The innerHTML call is considerably faster if we exclude the rows for |
// items we're not showing than if we hide them using display:none. |
return ''; |
} |
- // If opt_builder is provided, we're just viewing a single test |
+ // If opt_isCrossBuilderView is true, we're just viewing a single test |
// with results for many builders, so the first column is builder names |
// instead of test paths. |
- var testCellHTML = opt_builder ? opt_builder : |
+ var testCellHTML = opt_isCrossBuilderView ? builder : |
'<span class="link" onclick="setState(\'tests\', \'' + test.test + |
'\');return false;">' + test.test + '</span>'; |
@@ -783,9 +977,7 @@ |
'</td><td>' + test.missing + |
'</td><td>' + test.extra + |
'</td><td>' + (test.slowestTime ? test.slowestTime + 's' : '') + |
- '</td>' + |
- test.html + |
- '</tr>'; |
+ '</td>' + getHtmlForTestResults(test, builder) + '</tr>'; |
} |
function getSortColumnFromTableHeader(headerText) { |
@@ -890,8 +1082,6 @@ |
} |
function generatePage() { |
- parseCurrentLocation(); |
- |
// Only continue if all the JSON files have loaded. |
if (!expectationsLoaded) |
return; |
@@ -930,7 +1120,7 @@ |
var tableRowsHTML = ''; |
for (var j = 0; j < testResults.length; j++) { |
tableRowsHTML += getHTMLForSingleTestRow(testResults[j].results, |
- testResults[j].builder); |
+ testResults[j].builder, true); |
} |
html += getHTMLForTestTable(tableRowsHTML); |
} else { |
@@ -952,7 +1142,8 @@ |
builder + '</span>'; |
} |
html += '</div>' + |
- '<form onsubmit="setState(\'tests\', tests.value);return false;">' + |
+ '<form id=tests-form ' + |
+ 'onsubmit="setState(\'tests\', tests.value);return false;">' + |
'<div>Show tests on all platforms (slow): </div><input name=tests ' + |
'placeholder="LayoutTests/foo/bar.html,LayoutTests/foo/baz.html" ' + |
'id=tests-input></form><div id="loading-ui">LOADING...</div>' + |
@@ -968,8 +1159,8 @@ |
function getLinkHTMLToToggleState(key, linkText) { |
var isTrue = currentState[key]; |
- return '<span class=link onclick="setState(\'' + key + '\', \'' + !isTrue + |
- '\')">' + (isTrue ? 'Hide' : 'Show') + ' ' + linkText + '</span> | '; |
+ return '<span class=link onclick="setState(\'' + key + '\', ' + !isTrue + |
+ ')">' + (isTrue ? 'Hide' : 'Show') + ' ' + linkText + '</span> | '; |
} |
function generatePageForBuilder(builderName) { |
@@ -979,9 +1170,12 @@ |
var results = perBuilderFailures[builderName]; |
sortTests(results, currentState.sortColumn, currentState.sortOrder); |
for (var i = 0; i < results.length; i++) { |
- tableRowsHTML += getHTMLForSingleTestRow(results[i]); |
+ tableRowsHTML += getHTMLForSingleTestRow(results[i], builderName); |
} |
+ var testsHTML = tableRowsHTML ? getHTMLForTestTable(tableRowsHTML) : |
+ '<div>No tests. Try showing tests with correct expectations.</div>'; |
+ |
var html = getHTMLForNavBar(builderName) + |
getHTMLForTestsWithExpectationsButNoFailures(builderName) + |
'<h2>Failing tests</h2><div>' + |
@@ -989,9 +1183,13 @@ |
getLinkHTMLToToggleState('showCorrectExpectations', |
'tests with correct expectations') + |
getLinkHTMLToToggleState('showFlaky', 'flaky tests') + |
+ '<form id=max-results-form ' + |
+ 'onsubmit="setState(\'maxResults\', maxResults.value);return false;"' + |
+ '><span>Max results to show: </span>' + |
+ '<input name=maxResults id=max-results-input></form> | ' + |
'<b>All columns are sortable. | Skipped tests are not listed. | ' + |
'Flakiness reader order is newer --> older runs.</b></div>' + |
- getHTMLForTestTable(tableRowsHTML); |
+ testsHTML; |
setFullPageHTML(html); |
@@ -1000,35 +1198,10 @@ |
ths[i].addEventListener('click', changeSort, false); |
ths[i].className = "sortable"; |
} |
- } |
- // Permalinkable state of the page. |
- var currentState = {}; |
- |
- var defaultStateValues = { |
- sortOrder: BACKWARD, |
- sortColumn: 'flakiness', |
- showWontFix: false, |
- showCorrectExpectations: false, |
- showFlaky: true |
- }; |
- |
- for (var builder in builders) { |
- defaultStateValues.builder = builder; |
- break; |
+ $('max-results-input').value = currentState.maxResults; |
} |
- function fillDefaultStateValues() { |
- // tests has no states with default values. |
- if (currentState.tests) |
- return; |
- |
- for (var state in defaultStateValues) { |
- if (!(state in currentState)) |
- currentState[state] = defaultStateValues[state]; |
- } |
- } |
- |
/** |
* Sets the page state and regenerates the page. Takes varargs of key, value |
* pairs. |
@@ -1046,88 +1219,29 @@ |
currentState[key] = arguments[i + 1]; |
} |
+ window.location.replace(getPermaLinkURL()); |
+ handleLocationChange(); |
+ } |
+ function handleLocationChange() { |
$('loading-ui').style.display = 'block'; |
setTimeout(function() { |
- window.location.replace(getPermaLinkURL()); |
+ oldLocation = window.location.href; |
generatePage(); |
- $('loading-ui').style.display = 'none'; |
}, 0); |
} |
- function parseCurrentLocation() { |
- oldLocation = window.location.href; |
- var hash = window.location.hash; |
- if (!hash) { |
- fillDefaultStateValues(); |
- return; |
- } |
- |
- var hashParts = hash.slice(1).split('&'); |
- var urlHasTests = false; |
- for (var i = 0; i < hashParts.length; i++) { |
- var itemParts = hashParts[i].split('='); |
- if (itemParts.length != 2) { |
- console.log("Invalid parameter: " + hashParts[i]); |
- continue; |
- } |
- |
- var key = itemParts[0]; |
- var value = decodeURIComponent(itemParts[1]); |
- switch(key) { |
- case 'tests': |
- validateParameter(key, value, |
- function() { return value.match(/[A-Za-z0-9\-\_,]/); }); |
- break; |
- |
- case 'builder': |
- validateParameter(key, value, |
- function() { return value in builders; }); |
- break; |
- |
- case 'sortColumn': |
- validateParameter(key, value, |
- function() { |
- for (var i = 0; i < tableHeaders.length; i++) { |
- if (value == getSortColumnFromTableHeader(tableHeaders[i])) |
- return true; |
- } |
- return value == 'test'; |
- }); |
- break; |
- |
- case 'sortOrder': |
- validateParameter(key, value, |
- function() { return value == FORWARD || value == BACKWARD; }); |
- break; |
- |
- case 'showWontFix': |
- case 'showCorrectExpectations': |
- case 'showFlaky': |
- currentState[key] = value == 'true'; |
- break; |
- |
- default: |
- console.log('Invalid key: ' + key + ' value: ' + value); |
- } |
- } |
- fillDefaultStateValues(); |
+ function getPermaLinkURL() { |
+ return window.location.pathname + '?' + joinParameters(queryState) + '#' + |
+ joinParameters(currentState); |
} |
- function validateParameter(key, value, validateFn) { |
- if (validateFn()) { |
- currentState[key] = value; |
- } else { |
- console.log(key + ' value is not valid: ' + value); |
- } |
- } |
- |
- function getPermaLinkURL() { |
+ function joinParameters(stateObject) { |
var state = []; |
- for (var key in currentState) { |
- state.push(key + '=' + currentState[key]); |
+ for (var key in stateObject) { |
+ state.push(key + '=' + encodeURIComponent(stateObject[key])); |
} |
- return window.location.pathname + '#' + state.join('&'); |
+ return state.join('&'); |
} |
function logTime(msg, startTime) { |
@@ -1139,8 +1253,10 @@ |
// onload firing and the last script tag being executed. |
logTime('Time to load JS', pageLoadStartTime); |
setInterval(function() { |
- if (oldLocation != window.location) |
- generatePage(); |
+ if (oldLocation != window.location.href) { |
+ parseAllParameters(); |
+ handleLocationChange(); |
+ } |
}, 100); |
} |
</script> |