| Index: webkit/tools/layout_tests/flakiness_dashboard.html
|
| ===================================================================
|
| --- webkit/tools/layout_tests/flakiness_dashboard.html (revision 26165)
|
| +++ webkit/tools/layout_tests/flakiness_dashboard.html (working copy)
|
| @@ -15,6 +15,10 @@
|
| font-size: 16px;
|
| margin-bottom: .25em;
|
| }
|
| + h3 {
|
| + font-size: 13px;
|
| + margin: 0;
|
| + }
|
| #max-results-form {
|
| display: inline;
|
| }
|
| @@ -124,6 +128,12 @@
|
| .O {
|
| background-color: #69f;
|
| }
|
| + .merge {
|
| + background-color: green;
|
| + }
|
| + :not(#legend) > .merge {
|
| + width: 1px;
|
| + }
|
| .separator {
|
| border: 1px solid lightgray;
|
| height: 0px;
|
| @@ -136,10 +146,10 @@
|
| font-weight: bold;
|
| }
|
| #passing-tests {
|
| - -webkit-column-count: 2;
|
| + -webkit-column-count: 3;
|
| -webkit-column-gap: 25px;
|
| -webkit-column-rule: 1px dashed black;
|
| - -moz-column-count: 2;
|
| + -moz-column-count: 3;
|
| -moz-column-gap: 25px;
|
| -moz-column-rule: 1px dashed black;
|
| }
|
| @@ -157,6 +167,26 @@
|
| text-align: center;
|
| font-weight: bold;
|
| }
|
| + #popup {
|
| + background-color: white;
|
| + overflow: hidden;
|
| + width: 250px;
|
| + z-index: 1;
|
| + position: absolute;
|
| + border: 3px solid grey;
|
| + padding: 3px;
|
| + -webkit-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
|
| + -moz-box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.5);
|
| + -webkit-border-radius: 5px;
|
| + -moz-border-radius: 5px;
|
| + }
|
| + #popup > * {
|
| + width: 100%;
|
| + }
|
| + #popup > ul {
|
| + margin: 0;
|
| + padding-left: 20px;
|
| + }
|
| </style>
|
|
|
| <script>
|
| @@ -177,39 +207,44 @@
|
| * -add the builder name to the list of builders below.
|
| */
|
|
|
| - // 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'};
|
| + // 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 TEST_RESULTS_BASE_PATH =
|
| + 'http://build.chromium.org/buildbot/layout_test_results/';
|
| + 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 = {};
|
| + // 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 = {};
|
| + // Map of builder to arrays of paths that are skipped. This shows the raw
|
| + // path used in test_expectations.txt rather than the test path since we
|
| + // don't actually have any data here for skipped tests.
|
| + var perBuilderSkippedPaths = {};
|
|
|
| -
|
| // Generic utility functions.
|
| function $(id) {
|
| return document.getElementById(id);
|
| @@ -306,24 +341,23 @@
|
| showWontFix: false,
|
| showCorrectExpectations: false,
|
| showFlaky: true,
|
| + showSkipped: false,
|
| maxResults: 200,
|
| testType: 'layout_test_results'
|
| };
|
|
|
| - 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];
|
| }
|
| +
|
| + if (!currentState.tests && !('builder' in currentState)) {
|
| + for (var builder in builders) {
|
| + currentState.builder = builder;
|
| + break;
|
| + }
|
| + }
|
| }
|
|
|
| function handleValidHashParameter(key, value) {
|
| @@ -602,15 +636,21 @@
|
| * whether the modifiers match the builders platform and buildType.
|
| */
|
| function addTestAndExpectations(test, prefixPath, builder, expectationsMap,
|
| - expectations, testPrefixes) {
|
| + expectations, testPrefixes, skippedPaths) {
|
| var modifiersForBuilder =
|
| getModifierThatHasPlatformAndBuildType(builder, expectations);
|
| - if (modifiersForBuilder) {
|
| - if (getAllTestsWithSamePlatformAndBuildType(builder)[test]) {
|
| - expectationsMap[test] = expectations;
|
| - testPrefixes[test] = prefixPath;
|
| - } else if (!stringContains(modifiersForBuilder.modifiers, 'SKIP') &&
|
| - !modifiersForBuilder.expectations.match(/^\s*PASS\s*$/)) {
|
| +
|
| + if (!modifiersForBuilder)
|
| + return false;
|
| +
|
| + if (getAllTestsWithSamePlatformAndBuildType(builder)[test]) {
|
| + expectationsMap[test] = expectations;
|
| + testPrefixes[test] = prefixPath;
|
| + } else if (!stringContains(modifiersForBuilder.modifiers, 'WONTFIX')) {
|
| +
|
| + if (stringContains(modifiersForBuilder.modifiers, 'SKIP')) {
|
| + skippedPaths[prefixPath] = true;
|
| + } else if (!modifiersForBuilder.expectations.match(/^\s*PASS\s*$/)) {
|
| // Don't include skip tests here as they'll look the same as a test
|
| // that passes on all builders.
|
| // Also don't include tests that are only expected to pass, e.g.
|
| @@ -618,9 +658,9 @@
|
| // TODO(ojan): Should we also exclude WONTFIX tests here?
|
| perBuilderWithExpectationsButNoFailures[builder].push(test);
|
| }
|
| - return true;
|
| }
|
| - return false;
|
| +
|
| + return true;
|
| }
|
|
|
| /**
|
| @@ -665,11 +705,12 @@
|
|
|
| var testPrefixes = {};
|
| perBuilderWithExpectationsButNoFailures[builderName] = [];
|
| + var skippedPaths = {};
|
| for (var path in expectationsByTest) {
|
| var expectations = expectationsByTest[path];
|
| if (!isDirectory(path) &&
|
| addTestAndExpectations(path, path, builderName, expectationsMap,
|
| - expectations, testPrefixes)) {
|
| + expectations, testPrefixes, skippedPaths)) {
|
| continue;
|
| }
|
| // Test path doesn't match a specific test, see if it prefix matches
|
| @@ -679,10 +720,16 @@
|
| (!testPrefixes[test] ||
|
| !stringContains(testPrefixes[test], path))) {
|
| addTestAndExpectations(test, path, builderName, expectationsMap,
|
| - expectations, testPrefixes);
|
| + expectations, testPrefixes, skippedPaths);
|
| }
|
| }
|
| }
|
| +
|
| + perBuilderSkippedPaths[builderName] = [];
|
| + for (var path in skippedPaths) {
|
| + perBuilderSkippedPaths[builderName].push(path);
|
| + }
|
| + perBuilderSkippedPaths[builderName].sort();
|
| perBuilderWithExpectationsButNoFailures[builderName].sort();
|
|
|
| var allTestsForThisBuilder = resultsByBuilder[builderName].tests;
|
| @@ -776,7 +823,7 @@
|
| times[i][1]);
|
| }
|
|
|
| - if (resultsForTest.slowestTime &&
|
| + if (resultsForTest.slowestTime && !resultsMap['TIMEOUT'] &&
|
| (!resultsForTest.expectations ||
|
| !stringContains(resultsForTest.expectations, 'TIMEOUT')) &&
|
| (!resultsForTest.modifiers ||
|
| @@ -829,10 +876,56 @@
|
| return bugs;
|
| }
|
|
|
| - function loadBuilderPageForBuildNumber(builderName, buildNumber) {
|
| - window.open(BUILDERS_BASE_PATH + builderName + '/builds/' + buildNumber);
|
| + function getLinkHTMLToOpenWindow(url, text) {
|
| + return '<li class=link onclick="window.open(\'' + url + '\')">' + text +
|
| + '</li>';
|
| }
|
|
|
| + function showPopupForBuild(e, builder, index) {
|
| + var html = '';
|
| +
|
| + var time = resultsByBuilder[builder].secondsSinceEpoch[index];
|
| + if (time) {
|
| + var date = new Date(time * 1000);
|
| + html += date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
|
| + }
|
| +
|
| + html += '<ul>';
|
| +
|
| + var webkitRevision = resultsByBuilder[builder].webkitRevision;
|
| + var thisWebkitRevision = webkitRevision[index];
|
| + if (thisWebkitRevision) {
|
| + var previousWebkitRevision = webkitRevision[index + 1] ||
|
| + thisWebkitRevision;
|
| + html += getLinkHTMLToOpenWindow('http://trac.webkit.org/log/?rev=' +
|
| + thisWebkitRevision + '&stop_rev=' + previousWebkitRevision,
|
| + 'WebKit blamelist r' + previousWebkitRevision + ':r' +
|
| + thisWebkitRevision);
|
| + }
|
| +
|
| + var chromeRevision = resultsByBuilder[builder].chromeRevision;
|
| + var thisChromeRevision = chromeRevision[index];
|
| + if (thisChromeRevision) {
|
| + var previousChromeRevision = chromeRevision[index + 1] ||
|
| + thisChromeRevision;
|
| + html += getLinkHTMLToOpenWindow(
|
| + 'http://build.chromium.org/buildbot/perf/dashboard/ui/' +
|
| + 'changelog.html?url=/trunk/src&mode=html&range=' +
|
| + previousChromeRevision + ':' + thisChromeRevision,
|
| + 'Chrome blamelist r' + previousChromeRevision + ':r' +
|
| + thisChromeRevision) +
|
| + getLinkHTMLToOpenWindow(TEST_RESULTS_BASE_PATH + builders[builder] +
|
| + '/' + thisChromeRevision + '/layout-test-results.zip',
|
| + 'layout-test-results.zip');
|
| + }
|
| +
|
| + var buildNumbers = resultsByBuilder[builder].buildNumbers;
|
| + html += getLinkHTMLToOpenWindow(BUILDERS_BASE_PATH + builder + '/builds/' +
|
| + buildNumbers[index], 'Build log and blamelist') + '</ul>';
|
| +
|
| + showPopup(e, html);
|
| + }
|
| +
|
| function getHtmlForTestResults(test, builder) {
|
| var html = '';
|
| var results = test.rawResults.concat();
|
| @@ -842,9 +935,8 @@
|
| var indexToReplaceCurrentResult = -1;
|
| var indexToReplaceCurrentTime = -1;
|
| var currentResultArray, currentTimeArray, currentResult, innerHTML;
|
| - for (var i = 0;
|
| - i < buildNumbers.length && i < currentState.maxResults;
|
| - i++) {
|
| + var maxIndex = Math.min(buildNumbers.length, currentState.maxResults);
|
| + for (var i = 0; i < maxIndex; i++) {
|
| if (i > indexToReplaceCurrentResult) {
|
| currentResultArray = results.shift();
|
| if (currentResultArray) {
|
| @@ -868,25 +960,49 @@
|
| innerHTML = currentTime || ' ';
|
| }
|
|
|
| - var buildNumber = buildNumbers[i];
|
| - html += '<td title="Build:' + buildNumber + '" class="results ' +
|
| - currentResult + '" onclick=\'loadBuilderPageForBuildNumber("' +
|
| - builder + '","' + buildNumber + '")\'>' + innerHTML + '</td>';
|
| + html += '<td title="Click results for handy links." class="results ' +
|
| + currentResult + '" onclick=\'showPopupForBuild(event, "' + builder +
|
| + '",' + i + ')\'>' + innerHTML + '</td>';
|
| +
|
| + var webkitRevision = resultsByBuilder[builder].webkitRevision;
|
| + var isWebkitMerge = webkitRevision[i + 1] &&
|
| + webkitRevision[i] != webkitRevision[i + 1];
|
| + if (isWebkitMerge)
|
| + html += '<td class=merge></td>';
|
| }
|
| return html;
|
| }
|
|
|
| function getHTMLForTestsWithExpectationsButNoFailures(builder) {
|
| var tests = perBuilderWithExpectationsButNoFailures[builder];
|
| - if (!tests.length)
|
| - return '';
|
| + var skippedPaths = perBuilderSkippedPaths[builder];
|
|
|
| - var buildInfo = getPlatFormAndBuildType(builder);
|
| - return '<h2>Have expectations for ' + buildInfo.platform + '-' +
|
| - buildInfo.buildType + ' but have not failed in last ' +
|
| + var html = '';
|
| + if (tests.length || skippedPaths.length) {
|
| + var buildInfo = getPlatFormAndBuildType(builder);
|
| + html += '<h2>Expectations for ' + buildInfo.platform + '-' +
|
| + buildInfo.buildType + ':</h2>';
|
| + }
|
| +
|
| + if (tests.length) {
|
| + html += '<h3>Have not failed in last ' +
|
| resultsByBuilder[builderName].buildNumbers.length +
|
| - ' runs.</h2><div id="passing-tests"><div>' +
|
| + ' runs.</h3><div id="passing-tests"><div>' +
|
| tests.join('</div><div>') + '</div></div>';
|
| + }
|
| +
|
| + if (skippedPaths.length) {
|
| + html += '<h3>' +
|
| + getLinkHTMLToToggleState('showSkipped',
|
| + 'Skipped tests in text_expectations.txt') +
|
| + '</h3>';
|
| +
|
| + if (currentState.showSkipped) {
|
| + html += '<div id="passing-tests"><div>' +
|
| + skippedPaths.join('</div><div>') + '</div></div>';
|
| + }
|
| + }
|
| + return html;
|
| }
|
|
|
| /**
|
| @@ -1145,13 +1261,13 @@
|
| EXPECTATIONS_MAP[expectation] + '</div>';
|
| }
|
| return html + '<div class=wrong-expectations>WRONG EXPECTATIONS</div>' +
|
| - '</div>';
|
| + '<div class=merge>WEBKIT MERGE</div></div>';
|
| }
|
|
|
| function getLinkHTMLToToggleState(key, linkText) {
|
| var isTrue = currentState[key];
|
| return '<span class=link onclick="setState(\'' + key + '\', ' + !isTrue +
|
| - ')">' + (isTrue ? 'Hide' : 'Show') + ' ' + linkText + '</span> | ';
|
| + ')">' + (isTrue ? 'Hide' : 'Show') + ' ' + linkText + '</span>';
|
| }
|
|
|
| function generatePageForBuilder(builderName) {
|
| @@ -1170,13 +1286,13 @@
|
| var html = getHTMLForNavBar(builderName) +
|
| getHTMLForTestsWithExpectationsButNoFailures(builderName) +
|
| '<h2>Failing tests</h2><div>' +
|
| - getLinkHTMLToToggleState('showWontFix', 'WONTFIX tests') +
|
| + getLinkHTMLToToggleState('showWontFix', 'WONTFIX tests') + ' | ' +
|
| getLinkHTMLToToggleState('showCorrectExpectations',
|
| - 'tests with correct expectations') +
|
| - getLinkHTMLToToggleState('showFlaky', 'flaky tests') +
|
| + '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>' +
|
| + '><span>Number of 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>' +
|
| @@ -1193,9 +1309,6 @@
|
| $('max-results-input').value = currentState.maxResults;
|
| }
|
|
|
| - var singleBuilderViewParameters = ['sortOrder', 'sortColumn', 'showWontFix',
|
| - 'showCorrectExpectations', 'showFlaky: true', 'maxResults'];
|
| -
|
| /**
|
| * Sets the page state and regenerates the page. Takes varargs of key, value
|
| * pairs.
|
| @@ -1203,11 +1316,7 @@
|
| function setState(key, value) {
|
| for (var i = 0; i < arguments.length; i = i + 2) {
|
| var key = arguments[i];
|
| - if (key == 'tests') {
|
| - for (var state in singleBuilderViewParameters) {
|
| - delete currentState[state];
|
| - }
|
| - } else {
|
| + if (key != 'tests') {
|
| delete currentState.tests;
|
| }
|
|
|
| @@ -1241,6 +1350,44 @@
|
| console.log(msg + ': ' + (Date.now() - startTime));
|
| }
|
|
|
| + function hidePopup() {
|
| + var popup = $('popup');
|
| + popup.parentNode.removeChild(popup);
|
| + }
|
| +
|
| + function showPopup(e, html) {
|
| + var popup = $('popup');
|
| + if (!popup) {
|
| + popup = document.createElement('div');
|
| + popup.id = 'popup';
|
| + document.body.appendChild(popup);
|
| + }
|
| +
|
| + // Set html first so that we can get accurate size metrics on the popup.
|
| + popup.innerHTML = html;
|
| +
|
| + var targetRect = e.target.getBoundingClientRect();
|
| +
|
| + var x = Math.min(targetRect.left - 10,
|
| + document.documentElement.clientWidth - popup.offsetWidth);
|
| + popup.style.left = x + document.body.scrollLeft + 'px';
|
| +
|
| + var y = targetRect.top + targetRect.height;
|
| + if (y + popup.offsetHeight > document.documentElement.clientHeight) {
|
| + y = targetRect.top - popup.offsetHeight;
|
| + }
|
| + popup.style.top = y + document.body.scrollTop + 'px';
|
| + }
|
| +
|
| + document.onmousedown = function(e) {
|
| + // Clear the open popup, unless the click was inside the popup.
|
| + var popup = $('popup');
|
| + if (popup && e.target != popup &&
|
| + !(popup.compareDocumentPosition(e.target) & 16)) {
|
| + hidePopup();
|
| + }
|
| + };
|
| +
|
| window.onload = function() {
|
| // This doesn't seem totally accurate as there is a race between
|
| // onload firing and the last script tag being executed.
|
|
|