diff options
-rw-r--r-- | webkit/tools/layout_tests/flakiness_dashboard.html | 482 |
1 files changed, 431 insertions, 51 deletions
diff --git a/webkit/tools/layout_tests/flakiness_dashboard.html b/webkit/tools/layout_tests/flakiness_dashboard.html index ab950cb..052fad5 100644 --- a/webkit/tools/layout_tests/flakiness_dashboard.html +++ b/webkit/tools/layout_tests/flakiness_dashboard.html @@ -81,17 +81,15 @@ position: fixed; top: 5px; right: 5px; - width: 130px; + width: 200px; + padding: 2px; border: 2px solid grey; background-color: white; } #legend-contents * { - margin: 3px; + margin: 3px 0; padding: 0 2px; } - body > div > :not(#legend) { - margin-right: 145px; - } #builders * { margin: 0 5px; display: inline-block; @@ -128,7 +126,7 @@ .merge { background-color: grey; } - :not(#legend-contents) > .merge { + table .merge { width: 1px; } .separator { @@ -179,6 +177,49 @@ margin: 0; padding-left: 20px; } + .expectations-container { + clear: both; + } + .expectations-item { + float: left; + border: 1px solid grey; + } + .expectations-item .expectation { + width: 400px; + height: 300px; + border: 0; + border-top: 1px solid grey; + } + .expectations-item .large { + width: 800px; + height: 600px; + } + .expectations-item .checksum { + height: 30px; + } + .fallback-list { + margin-top: 0; + } + .used-platform { + float: right; + color: darkblue; + margin: 0 5px; + } + .expectations-title { + /* Hack to make a containing block for absolute positioned elements. */ + position: relative; + overflow: hidden; + } + .title { + /* Position absolutely so the container does not grow to contain this. */ + position: absolute; + } + .platforms { + position: absolute; + background-color: white; + right: 0; + z-index: 1; + } </style> <script src="dashboards/dashboard_base.js"></script> @@ -206,6 +247,9 @@ var ALL = 'ALL'; var FORWARD = 'forward'; var BACKWARD = 'backward'; + var LAYOUT_TESTS_PREFIX = 'LayoutTests/'; + var CHROME_TEST_BASE_URL = 'http://src.chromium.org/viewvc/chrome/trunk/' + + 'src/webkit/data/layout_tests/platform/'; var TEST_URL_BASE_PATH = 'http://trac.webkit.org/projects/webkit/browser/trunk/'; var BUILDERS_BASE_PATH = @@ -251,7 +295,6 @@ } $('max-results-input').value = currentState.maxResults; - updateLegendDisplay(); for (var builder in builders) { processTestResultsForBuilderAsync(builder); @@ -305,11 +348,12 @@ return true; - case 'showWontFix': case 'showCorrectExpectations': + case 'showExpectations': case 'showFlaky': - case 'showLegend': + case 'showLargeExpectations': case 'showSkipped': + case 'showWontFix': currentState[key] = value == 'true'; return true; @@ -322,12 +366,13 @@ defaultStateValues = { sortOrder: BACKWARD, sortColumn: 'flakiness', - showWontFix: false, showCorrectExpectations: false, - showLegend: true, + showExpectations: false, showFlaky: true, + showLargeExpectations: false, + showWontFix: false, showSkipped: false, - maxResults: 200, + maxResults: 200 }; ////////////////////////////////////////////////////////////////////////////// @@ -894,6 +939,7 @@ function showPopupForTest(e, test) { showPopup(e, getHTMLForIndividulTestOnAllBuilders(test)); + appendExpectations(); } function getHtmlForTestResults(test, builder) { @@ -1186,9 +1232,10 @@ processTestRunsForBuilder(builder); var testResults = testToResultsMap[test]; + var html = ''; if (testResults && testResults.length) { var tracURL = TEST_URL_BASE_PATH + test - var html = getLinkHTMLToOpenWindow(tracURL, tracURL) + + html += getLinkHTMLToOpenWindow(tracURL, tracURL) + '<div><b>If a builder is not listed, that means the builder does ' + 'run that test or all runs of the test passed.</b></div>'; @@ -1196,28 +1243,321 @@ html += getHTMLForSingleTestRow(testResults[j].results, testResults[j].builder, true); } - return getHTMLForTestTable(html); + html = getHTMLForTestTable(html); } else { - var html = ''; if (expectationsByTest[test]) { for (var i = 0; i < expectationsByTest[test].length; i++) { html += '<div>' + expectationsByTest[test][i].modifiers + ' | ' + expectationsByTest[test][i].expectations + '</div>'; } } - return html + '<div class="not-found">Test not found. Either it does ' + + html += '<div class="not-found">Test not found. Either it does ' + 'not exist, is skipped or passes on all platforms.</div>'; } + return html + '<div class=expectations test=' + test + '><div>' + + getLinkHTMLToToggleState('showExpectations', 'expectations') + ' | ' + + getLinkHTMLToToggleState('showLargeExpectations', 'large thumbnails') + + '</div></div>'; + } + + function getExpectationsContainer(expectationsContainers, parentContainer, + expectationsType) { + if (!expectationsContainers[expectationsType]) { + var container = document.createElement('div'); + container.className = 'expectations-container'; + parentContainer.appendChild(container); + expectationsContainers[expectationsType] = container; + } + return expectationsContainers[expectationsType]; + } + + function getExtension(path) { + var parts = path.split('.') + var extension = parts[parts.length - 1]; + return extension == 'html' ? 'txt' : extension; + } + + function ensureTrailingSlash(path) { + if (path.match(/\/$/)) + return path; + return path + '/'; + } + + /** + * Adds a specific expectation. If it's an image, it's only added on the + * image's onload handler. If it's a text file, then a script tag is appended + * as a hack to see if the file 404s (necessary since it's cross-domain). + * Once all the expectations for a specific type have loaded or errored + * (e.g. all the checksums), then we go through and identify which platform + * uses which expectation. + * + * @param {Object} expectationsContainers Map from expectations type to + * container DIV. + * @param {Element} parentContainer Container element for + * expectationsContainer divs. + * @param {string} platform Platform string. "LayoutTests/" for non-platform + * specific expectations. + * @param {string} path Relative path to the expectation. + * @param {string} base Base path for the expectation URL. + * @param {string} opt_suffix Suffix to place at the end of the path. + */ + function addExpectationItem(expectationsContainers, parentContainer, platform, + path, base, opt_suffix) { + var fileExtension = getExtension(path); + var container = getExpectationsContainer(expectationsContainers, + parentContainer, fileExtension); + var isImage = path.match(/\.png$/); + + // TODO(ojan): Is there any way to do this that doesn't rely on script + // tags? They spew a lot of errors to the console. + var dummyNode = document.createElement(isImage ? 'img' : 'script'); + var suffix = opt_suffix || ''; + var platformPart = platform ? ensureTrailingSlash(platform) : ''; + dummyNode.src = base + platformPart + path + suffix; + + var childContainer = document.createElement('span'); + childContainer.className = 'unloaded'; + + dummyNode.onload = function() { + childContainer.appendChild(getExpectationsTitle(platform, path)); + childContainer.className = 'expectations-item'; + + var item; + if (isImage) { + item = dummyNode; + } else { + item = document.createElement('iframe'); + item.src = dummyNode.src; + } + + item.className = 'expectation ' + fileExtension; + if (currentState.showLargeExpectations) + item.className += ' large'; + childContainer.appendChild(item); + handleFinishedLoadingExpectations(container); + } + dummyNode.onerror = function() { + childContainer.parentNode.removeChild(childContainer); + handleFinishedLoadingExpectations(container); + } + + // Append script elements now so that they load. Images load without being + // appended to the DOM. + if (!isImage) { + childContainer.appendChild(dummyNode); + } + + container.appendChild(childContainer); + } + + /** + * Identifies which expectations are used on which platform once all the + * expectations of a given type have loaded (e.g. the container for checksum + * expectations for this test had no child elements with the class + * "unloaded"). + * + * @param {string} container Element containing the expectations for a given + * test and a given type (e.g. checksum). + */ + function handleFinishedLoadingExpectations(container) { + if (container.getElementsByClassName('unloaded').length) + return; + + var titles = container.getElementsByClassName('expectations-title'); + for (var platform in fallbacksMap) { + var fallbacks = fallbacksMap[platform]; + var winner = null; + var winningIndex = -1; + for (var i = 0; i < titles.length; i++) { + var title = titles[i]; + + if (!winner && title.platform == LAYOUT_TESTS_PREFIX) { + winner = title; + continue; + } + + for (var j = 0; j < fallbacks.length; j++) { + if ((winningIndex == -1 || winningIndex > j) && + title.platform == fallbacks[j]) { + winningIndex = j; + winner = title; + break; + } + } + } + if (winner) { + winner.getElementsByClassName('platforms')[0].innerHTML += + '<div class=used-platform>' + platform + '</div>'; + } else { + console.log('No expectations identified for this test. This means ' + + 'there is a logic bug in the dashboard for which expectations a ' + + 'platform uses or trac.webkit.org/src.chromium.org is giving ' + + ' 5XXs.'); + } + } + } + + var TRAC_IMAGE_BASE_URL; + /** + * Trac seems to only show the raw image if you're viewing at a specific + * revision. Use the latest revision on any builder. + */ + function getTracImageBaseURL() { + if (!TRAC_IMAGE_BASE_URL) { + TRAC_IMAGE_BASE_URL = 'http://trac.webkit.org/export/' + + getLatestKnownRevision(false) + '/trunk/'; + } + return TRAC_IMAGE_BASE_URL; + } + + function getLatestKnownRevision(isChrome) { + var revision = 0; + for (var builder in builders) { + var results = resultsByBuilder[builder]; + var revisions = isChrome ? results.chromeRevision : + results.webkitRevision; + if (revision < revisions[0]) + revision = revisions[0]; + } + return revision; + } + + function addExpectations(expectationsContainers, container, base, imageBase, + platform, text, checksum, png, textSuffix) { + addExpectationItem(expectationsContainers, container, platform, text, base, + textSuffix); + addExpectationItem(expectationsContainers, container, platform, checksum, + base, textSuffix); + addExpectationItem(expectationsContainers, container, platform, png, + imageBase); + } + + function getExpectationsTitle(platform, path) { + var header = document.createElement('h3'); + header.className = 'expectations-title'; + + var innerHTML; + if (platform == LAYOUT_TESTS_PREFIX) { + var parts = path.split('/'); + innerHTML = parts[parts.length - 1]; + } else { + innerHTML = platform || path; + } + + header.innerHTML = '<div class=title>' + innerHTML + + '</div><div style="float:left"> </div>' + + '<div class=platforms style="float:right"></div>'; + header.style.clear = 'both'; + header.platform = platform; + return header; + } + + function loadExpectations(expectationsContainer) { + // Map from file extension to container div for expectations of that type. + var expectationsContainers = {}; + + var test = expectationsContainer.getAttribute('test'); + var textSuffixWebKit = '?format=txt'; + addExpectationItem(expectationsContainers, expectationsContainer, null, + test, TEST_URL_BASE_PATH, textSuffixWebKit); + + var testWithoutSuffix = test.substring(0, test.lastIndexOf('.')); + + var isUpstreamTest = startsWith(test, LAYOUT_TESTS_PREFIX); + if (isUpstreamTest) { + var testWithoutPrefix = testWithoutSuffix.substring( + LAYOUT_TESTS_PREFIX.length); + var textWithoutPrefix = testWithoutPrefix + "-expected.txt"; + var checksumWithoutPrefix = testWithoutPrefix + "-expected.checksum" + var pngWithoutPrefix = testWithoutPrefix + "-expected.png"; + + addExpectations(expectationsContainers, expectationsContainer, + TEST_URL_BASE_PATH, getTracImageBaseURL(), LAYOUT_TESTS_PREFIX, + textWithoutPrefix, checksumWithoutPrefix, pngWithoutPrefix, + textSuffixWebKit); + } + + var text = testWithoutSuffix + "-expected.txt"; + var checksum = testWithoutSuffix + "-expected.checksum" + var png = testWithoutSuffix + "-expected.png"; + + var textSuffixChrome = '?revision=' + getLatestKnownRevision(true); + + var fallbacks = getAllFallbacks(); + for (var i = 0; i < fallbacks.length; i++) { + var fallback = fallbacks[i]; + if (startsWith(fallback, 'platform')) { + if (isUpstreamTest) { + addExpectations(expectationsContainers, expectationsContainer, + TEST_URL_BASE_PATH + LAYOUT_TESTS_PREFIX, + getTracImageBaseURL() + LAYOUT_TESTS_PREFIX, + fallback, textWithoutPrefix, + checksumWithoutPrefix, pngWithoutPrefix, textSuffixWebKit); + } + } else { + addExpectations(expectationsContainers, expectationsContainer, + CHROME_TEST_BASE_URL, CHROME_TEST_BASE_URL, fallback, text, + checksum, png, textSuffixChrome); + } + } + + // Add a clearing element so floated elements don't bleed out of their + // containing block. + var br = document.createElement('br'); + br.style.clear = 'both'; + expectationsContainer.appendChild(br); + } + + var allFallbacks; + + /** + * Returns the reverse sorted, deduped list of all platform fallback + * directories. + */ + function getAllFallbacks() { + if (!allFallbacks) { + var holder = {}; + for (var platform in fallbacksMap) { + var fallbacks = fallbacksMap[platform]; + for (var i = 0; i < fallbacks.length; i++) { + holder[fallbacks[i]] = 1; + } + } + + allFallbacks = []; + for (var fallback in holder) { + allFallbacks.push(fallback); + } + allFallbacks.sort(function(a, b) { + if (a == b) + return 0; + return a < b; + }); + } + return allFallbacks; + } + + /** + * Appends the expectations for each test listed. + */ + function appendExpectations() { + if (currentState.showExpectations) { + var expectations = document.getElementsByClassName('expectations'); + for (var i = 0, len = expectations.length; i < len; i++) { + loadExpectations(expectations[i]); + } + } } function generatePageForIndividualTests(tests) { - var html = getHTMLForNavBar(); + var testsHTML = []; for (var i = 0; i < tests.length; i++) { - html += '<h2>' + tests[i] + '</h2>' + - getHTMLForIndividulTestOnAllBuilders(tests[i]); + testsHTML.push('<h2>' + tests[i] + '</h2>' + + getHTMLForIndividulTestOnAllBuilders(tests[i])); } - setFullPageHTML(html); + setFullPageHTML(getHTMLForNavBar() + testsHTML.join('<hr>')); + appendExpectations(); $('tests-input').value = currentState.tests; } @@ -1229,7 +1569,7 @@ ' onclick=\'setState("builder", "' + builder + '")\'>' + builder + '</span>'; } - html += '</div>' + + return html + '</div>' + '<form id=tests-form ' + 'onsubmit="setState(\'tests\', tests.value);return false;">' + '<div>Show tests on all platforms: </div><input name=tests ' + @@ -1240,17 +1580,9 @@ '<form id=max-results-form ' + 'onsubmit="setState(\'maxResults\', maxResults.value);return false;"' + '><span>Number of results to show (max=500): </span>' + - '<input name=maxResults id=max-results-input></form>' + - '<div id="loading-ui">LOADING...</div><div id=legend>' + - '<div id=legend-toggle>' + getLinkHTMLToToggleLegendDisplay() + - '</div><div id=legend-contents>'; - - for (var expectation in EXPECTATIONS_MAP) { - html += '<div class=' + expectation + '>' + - EXPECTATIONS_MAP[expectation] + '</div>'; - } - return html + '<div class=wrong-expectations>WRONG EXPECTATIONS</div>' + - '<div class=merge>WEBKIT MERGE</div></div></div>'; + '<input name=maxResults id=max-results-input></form> | ' + + '<b>Type ? for legend and expectations fallback order</b>' + + '<div id="loading-ui">LOADING...</div>'; } function getLinkHTMLToToggleState(key, linkText) { @@ -1279,8 +1611,8 @@ getLinkHTMLToToggleState('showCorrectExpectations', 'tests with correct expectations') + ' | ' + getLinkHTMLToToggleState('showFlaky', 'flaky tests') + ' | ' + - '<b>All columns are sortable. | ' + - 'Flakiness reader order is newer --> older runs.</b></div>' + + 'All columns are sortable. | ' + + 'Flakiness reader order is newer --> older runs.</div>' + testsHTML; setFullPageHTML(html); @@ -1292,14 +1624,6 @@ } } - function getLinkHTMLToToggleLegendDisplay() { - return getLinkHTMLToToggleState('showLegend', 'Legend'); - } - - function updateLegendDisplay() { - $('legend-contents').style.display = currentState.showLegend ? '' : 'none'; - } - function createTableHeadersArray(firstColumnHeader) { tableHeaders = [firstColumnHeader].concat(BASE_TABLE_HEADERS); } @@ -1324,6 +1648,13 @@ } } + var VALID_KEYS_FOR_INDIVIDUAL_TESTS = { + tests: 1, + maxResults: 1, + showExpectations: 1, + showLargeExpectations: 1 + }; + /** * Sets the page state and regenerates the page. Takes varargs of key, value * pairs. @@ -1333,7 +1664,7 @@ for (var i = 0; i < arguments.length; i += 2) { var key = arguments[i]; - if (key != 'tests' && key != 'maxResults') { + if (!(key in VALID_KEYS_FOR_INDIVIDUAL_TESTS)) { delete currentState.tests; } @@ -1343,14 +1674,6 @@ // but is considerably easier than refactoring all the other code. clearProcessedTestState(); } - - if (key == 'showLegend') { - // No need to regenerate the page if only the legend's display is being - // updated. - shouldRegeneratePage = keys.length == 1; - updateLegendDisplay(); - $('legend-toggle').innerHTML = getLinkHTMLToToggleLegendDisplay(); - } } // Set all the custom state for this dashboard before calling @@ -1361,6 +1684,63 @@ handleLocationChange(); } + function hideLegend() { + var legend = $('legend'); + if (legend) + legend.parentNode.removeChild(legend); + } + + var fallbacksMap = {}; + fallbacksMap['WIN-VISTA'] = ['chromium-win-vista', 'chromium-win', + 'platform/win', 'platform/mac']; + fallbacksMap['WIN-XP'] = ['chromium-win-xp'].concat( + fallbacksMap['WIN-VISTA']); + // Should mac look at the tiger results? + fallbacksMap['MAC'] = ['chromium-mac', 'platform/mac', + 'platform/mac-snowleopard', 'platform/mac-leopard', 'platform/mac-tiger']; + fallbacksMap['LINUX'] = ['chromium-linux', 'chromium-win', 'platform/win', + 'platform/mac']; + + function htmlForFallbackHelp(fallbacks) { + return '<ol class=fallback-list><li>' + fallbacks.join('</li><li>') + + '</li></ol>'; + } + + function showLegend() { + var legend = $('legend'); + if (!legend) { + legend = document.createElement('div'); + legend.id = 'legend'; + document.body.appendChild(legend); + } + + var innerHTML = '<div id=legend-toggle onclick="hideLegend()">Hide ' + + 'legend (or hit esc to close)</div><div id=legend-contents>'; + for (var expectation in EXPECTATIONS_MAP) { + innerHTML += '<div class=' + expectation + '>' + + EXPECTATIONS_MAP[expectation] + '</div>'; + } + innerHTML += '<div class=wrong-expectations>WRONG EXPECTATIONS</div>' + + '<div class=merge>WEBKIT MERGE</div></div>' +'</div>' + + '<h3>Test expectatons fallback order.</h3>'; + + for (var platform in fallbacksMap) { + innerHTML += '<div class=fallback-header>' + platform + '</div>' + + htmlForFallbackHelp(fallbacksMap[platform]); + } + legend.innerHTML = innerHTML; + } + + document.addEventListener('keydown', function(e) { + if (e.keyIdentifier == 'U+003F') { + // ? key + showLegend(); + } else if (e.keyIdentifier == 'U+001B') { + // escape key + hideLegend(); + hidePopup(); + } + }, false); </script> </head> |