summaryrefslogtreecommitdiffstats
path: root/Tools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js
diff options
context:
space:
mode:
Diffstat (limited to 'Tools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js')
-rw-r--r--Tools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js543
1 files changed, 543 insertions, 0 deletions
diff --git a/Tools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js b/Tools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js
new file mode 100644
index 0000000..aeaac04
--- /dev/null
+++ b/Tools/Scripts/webkitpy/tool/commands/data/rebaselineserver/main.js
@@ -0,0 +1,543 @@
+/*
+ * Copyright (c) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+var ALL_DIRECTORY_PATH = '[all]';
+
+var STATE_NEEDS_REBASELINE = 'needs_rebaseline';
+var STATE_REBASELINE_FAILED = 'rebaseline_failed';
+var STATE_REBASELINE_SUCCEEDED = 'rebaseline_succeeded';
+var STATE_IN_QUEUE = 'in_queue';
+var STATE_TO_DISPLAY_STATE = {};
+STATE_TO_DISPLAY_STATE[STATE_NEEDS_REBASELINE] = 'Needs rebaseline';
+STATE_TO_DISPLAY_STATE[STATE_REBASELINE_FAILED] = 'Rebaseline failed';
+STATE_TO_DISPLAY_STATE[STATE_REBASELINE_SUCCEEDED] = 'Rebaseline succeeded';
+STATE_TO_DISPLAY_STATE[STATE_IN_QUEUE] = 'In queue';
+
+var results;
+var testsByFailureType = {};
+var testsByDirectory = {};
+var selectedTests = [];
+var loupe;
+var queue;
+
+function main()
+{
+ $('failure-type-selector').addEventListener('change', selectFailureType);
+ $('directory-selector').addEventListener('change', selectDirectory);
+ $('test-selector').addEventListener('change', selectTest);
+ $('next-test').addEventListener('click', nextTest);
+ $('previous-test').addEventListener('click', previousTest);
+
+ $('toggle-log').addEventListener('click', function() { toggle('log'); });
+
+ loupe = new Loupe();
+ queue = new RebaselineQueue();
+
+ document.addEventListener('keydown', function(event) {
+ if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
+ return;
+ }
+
+ switch (event.keyIdentifier) {
+ case 'Left':
+ event.preventDefault();
+ previousTest();
+ break;
+ case 'Right':
+ event.preventDefault();
+ nextTest();
+ break;
+ case 'U+0051': // q
+ queue.addCurrentTest();
+ break;
+ case 'U+0058': // x
+ queue.removeCurrentTest();
+ break;
+ case 'U+0052': // r
+ queue.rebaseline();
+ break;
+ }
+ });
+
+ loadText('/platforms.json', function(text) {
+ var platforms = JSON.parse(text);
+ platforms.platforms.forEach(function(platform) {
+ var platformOption = document.createElement('option');
+ platformOption.value = platform;
+ platformOption.textContent = platform;
+
+ var targetOption = platformOption.cloneNode(true);
+ targetOption.selected = platform == platforms.defaultPlatform;
+ $('baseline-target').appendChild(targetOption);
+ $('baseline-move-to').appendChild(platformOption.cloneNode(true));
+ });
+ });
+
+ loadText('/results.json', function(text) {
+ results = JSON.parse(text);
+ displayResults();
+ });
+}
+
+/**
+ * Groups test results by failure type.
+ */
+function displayResults()
+{
+ var failureTypeSelector = $('failure-type-selector');
+ var failureTypes = [];
+
+ for (var testName in results.tests) {
+ var test = results.tests[testName];
+ if (test.actual == 'PASS') {
+ continue;
+ }
+ var failureType = test.actual + ' (expected ' + test.expected + ')';
+ if (!(failureType in testsByFailureType)) {
+ testsByFailureType[failureType] = [];
+ failureTypes.push(failureType);
+ }
+ testsByFailureType[failureType].push(testName);
+ }
+
+ // Sort by number of failures
+ failureTypes.sort(function(a, b) {
+ return testsByFailureType[b].length - testsByFailureType[a].length;
+ });
+
+ for (var i = 0, failureType; failureType = failureTypes[i]; i++) {
+ var failureTypeOption = document.createElement('option');
+ failureTypeOption.value = failureType;
+ failureTypeOption.textContent = failureType + ' - ' + testsByFailureType[failureType].length + ' tests';
+ failureTypeSelector.appendChild(failureTypeOption);
+ }
+
+ selectFailureType();
+
+ document.body.className = '';
+}
+
+/**
+ * For a given failure type, gets all the tests and groups them by directory
+ * (populating the directory selector with them).
+ */
+function selectFailureType()
+{
+ var selectedFailureType = getSelectValue('failure-type-selector');
+ var tests = testsByFailureType[selectedFailureType];
+
+ testsByDirectory = {}
+ var displayDirectoryNamesByDirectory = {};
+ var directories = [];
+
+ // Include a special option for all tests
+ testsByDirectory[ALL_DIRECTORY_PATH] = tests;
+ displayDirectoryNamesByDirectory[ALL_DIRECTORY_PATH] = 'all';
+ directories.push(ALL_DIRECTORY_PATH);
+
+ // Roll up tests by ancestor directories
+ tests.forEach(function(test) {
+ var pathPieces = test.split('/');
+ var pathDirectories = pathPieces.slice(0, pathPieces.length -1);
+ var ancestorDirectory = '';
+
+ pathDirectories.forEach(function(pathDirectory, index) {
+ ancestorDirectory += pathDirectory + '/';
+ if (!(ancestorDirectory in testsByDirectory)) {
+ testsByDirectory[ancestorDirectory] = [];
+ var displayDirectoryName = new Array(index * 6).join(' ') + pathDirectory;
+ displayDirectoryNamesByDirectory[ancestorDirectory] = displayDirectoryName;
+ directories.push(ancestorDirectory);
+ }
+
+ testsByDirectory[ancestorDirectory].push(test);
+ });
+ });
+
+ directories.sort();
+
+ var directorySelector = $('directory-selector');
+ directorySelector.innerHTML = '';
+
+ directories.forEach(function(directory) {
+ var directoryOption = document.createElement('option');
+ directoryOption.value = directory;
+ directoryOption.innerHTML =
+ displayDirectoryNamesByDirectory[directory] + ' - ' +
+ testsByDirectory[directory].length + ' tests';
+ directorySelector.appendChild(directoryOption);
+ });
+
+ selectDirectory();
+}
+
+/**
+ * For a given failure type and directory and failure type, gets all the tests
+ * in that directory and populatest the test selector with them.
+ */
+function selectDirectory()
+{
+ var previouslySelectedTest = getSelectedTest();
+
+ var selectedDirectory = getSelectValue('directory-selector');
+ selectedTests = testsByDirectory[selectedDirectory];
+ selectedTests.sort();
+
+ var testsByState = {};
+ selectedTests.forEach(function(testName) {
+ var state = results.tests[testName].state;
+ if (state == STATE_IN_QUEUE) {
+ state = STATE_NEEDS_REBASELINE;
+ }
+ if (!(state in testsByState)) {
+ testsByState[state] = [];
+ }
+ testsByState[state].push(testName);
+ });
+
+ var optionIndexByTest = {};
+
+ var testSelector = $('test-selector');
+ testSelector.innerHTML = '';
+
+ for (var state in testsByState) {
+ var stateOption = document.createElement('option');
+ stateOption.textContent = STATE_TO_DISPLAY_STATE[state];
+ stateOption.disabled = true;
+ testSelector.appendChild(stateOption);
+
+ testsByState[state].forEach(function(testName) {
+ var testOption = document.createElement('option');
+ testOption.value = testName;
+ var testDisplayName = testName;
+ if (testName.lastIndexOf(selectedDirectory) == 0) {
+ testDisplayName = testName.substring(selectedDirectory.length);
+ }
+ testOption.innerHTML = '  ' + testDisplayName;
+ optionIndexByTest[testName] = testSelector.options.length;
+ testSelector.appendChild(testOption);
+ });
+ }
+
+ if (previouslySelectedTest in optionIndexByTest) {
+ testSelector.selectedIndex = optionIndexByTest[previouslySelectedTest];
+ } else if (STATE_NEEDS_REBASELINE in testsByState) {
+ testSelector.selectedIndex =
+ optionIndexByTest[testsByState[STATE_NEEDS_REBASELINE][0]];
+ selectTest();
+ } else {
+ testSelector.selectedIndex = 1;
+ selectTest();
+ }
+
+ selectTest();
+}
+
+function getSelectedTest()
+{
+ return getSelectValue('test-selector');
+}
+
+function selectTest()
+{
+ var selectedTest = getSelectedTest();
+
+ if (results.tests[selectedTest].actual.indexOf('IMAGE') != -1) {
+ $('image-outputs').style.display = '';
+ displayImageResults(selectedTest);
+ } else {
+ $('image-outputs').style.display = 'none';
+ }
+
+ if (results.tests[selectedTest].actual.indexOf('TEXT') != -1) {
+ $('text-outputs').style.display = '';
+ displayTextResults(selectedTest);
+ } else {
+ $('text-outputs').style.display = 'none';
+ }
+
+ var currentBaselines = $('current-baselines');
+ currentBaselines.textContent = '';
+ var baselines = results.tests[selectedTest].baselines;
+ var testName = selectedTest.split('.').slice(0, -1).join('.');
+ getSortedKeys(baselines).forEach(function(platform, i) {
+ if (i != 0) {
+ currentBaselines.appendChild(document.createTextNode('; '));
+ }
+ var platformName = document.createElement('span');
+ platformName.className = 'platform';
+ platformName.textContent = platform;
+ currentBaselines.appendChild(platformName);
+ currentBaselines.appendChild(document.createTextNode(' ('));
+ getSortedKeys(baselines[platform]).forEach(function(extension, j) {
+ if (j != 0) {
+ currentBaselines.appendChild(document.createTextNode(', '));
+ }
+ var link = document.createElement('a');
+ var baselinePath = '';
+ if (platform != 'base') {
+ baselinePath += 'platform/' + platform + '/';
+ }
+ baselinePath += testName + '-expected' + extension;
+ link.href = getTracUrl(baselinePath);
+ if (extension == '.checksum') {
+ link.textContent = 'chk';
+ } else {
+ link.textContent = extension.substring(1);
+ }
+ link.target = '_blank';
+ if (baselines[platform][extension]) {
+ link.className = 'was-used-for-test';
+ }
+ currentBaselines.appendChild(link);
+ });
+ currentBaselines.appendChild(document.createTextNode(')'));
+ });
+
+ updateState();
+ loupe.hide();
+
+ prefetchNextImageTest();
+}
+
+function prefetchNextImageTest()
+{
+ var testSelector = $('test-selector');
+ if (testSelector.selectedIndex == testSelector.options.length - 1) {
+ return;
+ }
+ var nextTest = testSelector.options[testSelector.selectedIndex + 1].value;
+ if (results.tests[nextTest].actual.indexOf('IMAGE') != -1) {
+ new Image().src = getTestResultUrl(nextTest, 'expected-image');
+ new Image().src = getTestResultUrl(nextTest, 'actual-image');
+ }
+}
+
+function updateState()
+{
+ var testName = getSelectedTest();
+ var testIndex = selectedTests.indexOf(testName);
+ var testCount = selectedTests.length
+ $('test-index').textContent = testIndex + 1;
+ $('test-count').textContent = testCount;
+
+ $('next-test').disabled = testIndex == testCount - 1;
+ $('previous-test').disabled = testIndex == 0;
+
+ $('test-link').href = getTracUrl(testName);
+
+ var state = results.tests[testName].state;
+ $('state').className = state;
+ $('state').innerHTML = STATE_TO_DISPLAY_STATE[state];
+
+ queue.updateState();
+}
+
+function getTestResultUrl(testName, mode)
+{
+ return '/test_result?test=' + testName + '&mode=' + mode;
+}
+
+var currentExpectedImageTest;
+var currentActualImageTest;
+
+function displayImageResults(testName)
+{
+ if (currentExpectedImageTest == currentActualImageTest
+ && currentExpectedImageTest == testName) {
+ return;
+ }
+
+ function displayImageResult(mode, callback) {
+ var image = $(mode);
+ image.className = 'loading';
+ image.src = getTestResultUrl(testName, mode);
+ image.onload = function() {
+ image.className = '';
+ callback();
+ updateImageDiff();
+ };
+ }
+
+ displayImageResult(
+ 'expected-image',
+ function() { currentExpectedImageTest = testName; });
+ displayImageResult(
+ 'actual-image',
+ function() { currentActualImageTest = testName; });
+
+ $('diff-canvas').className = 'loading';
+ $('diff-canvas').style.display = '';
+ $('diff-checksum').style.display = 'none';
+}
+
+/**
+ * Computes a graphical a diff between the expected and actual images by
+ * rendering each to a canvas, getting the image data, and comparing the RGBA
+ * components of each pixel. The output is put into the diff canvas, with
+ * identical pixels appearing at 12.5% opacity and different pixels being
+ * highlighted in red.
+ */
+function updateImageDiff() {
+ if (currentExpectedImageTest != currentActualImageTest)
+ return;
+
+ var expectedImage = $('expected-image');
+ var actualImage = $('actual-image');
+
+ function getImageData(image) {
+ var imageCanvas = document.createElement('canvas');
+ imageCanvas.width = image.width;
+ imageCanvas.height = image.height;
+ imageCanvasContext = imageCanvas.getContext('2d');
+
+ imageCanvasContext.fillStyle = 'rgba(255, 255, 255, 1)';
+ imageCanvasContext.fillRect(
+ 0, 0, image.width, image.height);
+
+ imageCanvasContext.drawImage(image, 0, 0);
+ return imageCanvasContext.getImageData(
+ 0, 0, image.width, image.height);
+ }
+
+ var expectedImageData = getImageData(expectedImage);
+ var actualImageData = getImageData(actualImage);
+
+ var diffCanvas = $('diff-canvas');
+ var diffCanvasContext = diffCanvas.getContext('2d');
+ var diffImageData =
+ diffCanvasContext.createImageData(diffCanvas.width, diffCanvas.height);
+
+ // Avoiding property lookups for all these during the per-pixel loop below
+ // provides a significant performance benefit.
+ var expectedWidth = expectedImage.width;
+ var expectedHeight = expectedImage.height;
+ var expected = expectedImageData.data;
+
+ var actualWidth = actualImage.width;
+ var actual = actualImageData.data;
+
+ var diffWidth = diffImageData.width;
+ var diff = diffImageData.data;
+
+ var hadDiff = false;
+ for (var x = 0; x < expectedWidth; x++) {
+ for (var y = 0; y < expectedHeight; y++) {
+ var expectedOffset = (y * expectedWidth + x) * 4;
+ var actualOffset = (y * actualWidth + x) * 4;
+ var diffOffset = (y * diffWidth + x) * 4;
+ if (expected[expectedOffset] != actual[actualOffset] ||
+ expected[expectedOffset + 1] != actual[actualOffset + 1] ||
+ expected[expectedOffset + 2] != actual[actualOffset + 2] ||
+ expected[expectedOffset + 3] != actual[actualOffset + 3]) {
+ hadDiff = true;
+ diff[diffOffset] = 255;
+ diff[diffOffset + 1] = 0;
+ diff[diffOffset + 2] = 0;
+ diff[diffOffset + 3] = 255;
+ } else {
+ diff[diffOffset] = expected[expectedOffset];
+ diff[diffOffset + 1] = expected[expectedOffset + 1];
+ diff[diffOffset + 2] = expected[expectedOffset + 2];
+ diff[diffOffset + 3] = 32;
+ }
+ }
+ }
+
+ diffCanvasContext.putImageData(
+ diffImageData,
+ 0, 0,
+ 0, 0,
+ diffImageData.width, diffImageData.height);
+ diffCanvas.className = '';
+
+ if (!hadDiff) {
+ diffCanvas.style.display = 'none';
+ $('diff-checksum').style.display = '';
+ loadTextResult(currentExpectedImageTest, 'expected-checksum');
+ loadTextResult(currentExpectedImageTest, 'actual-checksum');
+ }
+}
+
+function loadTextResult(testName, mode, responseIsHtml)
+{
+ loadText(getTestResultUrl(testName, mode), function(text) {
+ if (responseIsHtml) {
+ $(mode).innerHTML = text;
+ } else {
+ $(mode).textContent = text;
+ }
+ });
+}
+
+function displayTextResults(testName)
+{
+ loadTextResult(testName, 'expected-text');
+ loadTextResult(testName, 'actual-text');
+ loadTextResult(testName, 'diff-text-pretty', true);
+}
+
+function nextTest()
+{
+ var testSelector = $('test-selector');
+ var nextTestIndex = testSelector.selectedIndex + 1;
+ while (true) {
+ if (nextTestIndex == testSelector.options.length) {
+ return;
+ }
+ if (testSelector.options[nextTestIndex].disabled) {
+ nextTestIndex++;
+ } else {
+ testSelector.selectedIndex = nextTestIndex;
+ selectTest();
+ return;
+ }
+ }
+}
+
+function previousTest()
+{
+ var testSelector = $('test-selector');
+ var previousTestIndex = testSelector.selectedIndex - 1;
+ while (true) {
+ if (previousTestIndex == -1) {
+ return;
+ }
+ if (testSelector.options[previousTestIndex].disabled) {
+ previousTestIndex--;
+ } else {
+ testSelector.selectedIndex = previousTestIndex;
+ selectTest();
+ return
+ }
+ }
+}
+
+window.addEventListener('DOMContentLoaded', main);