blob: 384e13735a2261e5f335bd2b3e6426eddbf99c54 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<title>WebKit Test Results</title>
<link rel="stylesheet" href="common.css">
<link rel="stylesheet" href="main.css">
<script src="https://svn.webkit.org/repository/webkit/!svn/bc/128779/trunk/PerformanceTests/Dromaeo/resources/dromaeo/web/lib/jquery-1.6.4.js"></script>
<script src="https://svn.webkit.org/repository/webkit/!svn/bc/128779/trunk/PerformanceTests/resources/jquery.tablesorter.min.js"></script>
<script src="js/autocompleter.js"></script>
<script src="js/build.js"></script>
<script src="js/dom.js"></script>
</head>
<body>
<header id="title">
<h1><a href="/">WebKit Test Results</a></h1>
<ul>
<li><a href="http://build.webkit.org/waterfall">Waterfall</a></li>
</ul>
</header>
<form onsubmit="TestResultsView.fetchTest(this['testName'].value);
TestResultsView.updateLocationHash();
return false;">
<input id="testName" type="text" size="150" onpaste="pasteHelper(this, event)"
placeholder="Type in a test name, or copy and paste test names on results.html or NRWT stdout (including junks)"></form>
<div id="testView"></div>
<div id="buildersView">
<form>
Show
<label>all tests <select id="builderFailureTypeView"><option>failing</option><option>flaky</option><option value="wrongexpectations">has wrong expectations</option></select></label>
<label>on <select id="builderListView"><option value="">Select builder</option></select></label>
<label for="builderDaysView">for
<select id="builderDaysView" disabled><option>5</option><option>15</option><option selected>30</option></select> days</label>
</form>
<div id="builderFailingTestsView"></div>
</div>
<div id="tooltipContainer"></div>
<script>
var TestResultsView = new (function () {
var tooltipContainer = document.getElementById('tooltipContainer');
tooltipContainer.onclick = function (event) { event.insideTooltip = true; }
document.addEventListener('click', function (event) {
if (!event.insideTooltip)
tooltipContainer.style.display = 'none';
});
window.addEventListener('hashchange', function (event) {
TestResultsView.locationHashChanged();
});
this._tooltipContainer = tooltipContainer;
this._tests = [];
this._currentBuilder = null;
this._currentBuilderFailureType = null;
this._currentBuilderDays = null;
this._oldHash = null;
this._builders = {};
this._builderByName = {};
this._slaves = {};
this._repositories = {};
this._testCategories = {};
this._newBugLinks = [];
});
TestResultsView.setAvailableTests = function (availableTests) {
this._availableTests = availableTests;
}
TestResultsView.setBuilders = function (builders) {
this._builders = builders;
for (var builderId in builders) {
var builder = builders[builderId];
this._builderByName[builder.name] = builder;
}
}
TestResultsView.setSlaves = function (slaves) {
this._slaves = slaves;
}
TestResultsView.setRepositories = function (repositories) {
this._repositories = repositories;
}
TestResultsView.setTestCategories = function (testCategories) {
this._testCategories = testCategories;
}
TestResultsView.setNewBugLinks = function (newBugLinks) {
this._newBugLinks = newBugLinks;
}
TestResultsView.showTooltip = function (anchor, contentElement) {
var tooltipContainer = this._tooltipContainer;
tooltipContainer.style.display = null;
while (tooltipContainer.firstChild)
tooltipContainer.removeChild(tooltipContainer.firstChild);
tooltipContainer.appendChild(contentElement);
var position = {x: 0, y: 0};
var currentNode = anchor;
while (currentNode) {
position.x += currentNode.offsetLeft;
position.y += currentNode.offsetTop;
currentNode = currentNode.offsetParent;
}
tooltipContainer.style.left = (position.x - contentElement.offsetWidth / 2 + anchor.offsetWidth / 2) + 'px';
tooltipContainer.style.top = (position.y - contentElement.clientHeight - 5) + 'px';
}
TestResultsView._createResultCell = function (master, builder, result, previousResult) {
var buildTime = result['buildTime'];
var revisions = result['revisions'];
var slave = result['slave'];
var buildNumber = result['buildNumber'];
var actual = result['actual'];
var expected = result['expected'];
var timeIfSlow = result.isSlow ? result.roundedTime : '';
// FIXME: We shouldn't be hard-coding WebKit revisions here.
var webkitRepositoryId;
for (var repositoryId in TestResultsView._repositories) {
if (TestResultsView._repositories[repositoryId].name == 'WebKit')
webkitRepositoryId = repositoryId;
}
var webkitRevision = result.build.revision(webkitRepositoryId);
var resultsPage = webkitRevision ? "http://" + master + "/results/" + builder + "/r" + webkitRevision + "%20(" + buildNumber + ")/results.html"
: 'javascript:alert("Could no resolve WebKit revision")';
var anchor = element('a', {'href': resultsPage }, [timeIfSlow]);
anchor.onmouseenter = function () {
var formattedRevisions = result.build.formattedRevisions(previousResult ? previousResult.build : null);
var revisionDescription = [];
for (var repositoryName in formattedRevisions) {
var revision = formattedRevisions[repositoryName];
if (revisionDescription.length)
revisionDescription.push(', ');
if (revision.url)
revisionDescription.push(element('a', {'href': revision.url}, [revision.label]));
else
revisionDescription.push(revision.label);
}
TestResultsView.showTooltip(anchor, element('div', {'class': 'tooltip'}, [
element('ul', [
element('li', ['Build Time: ' + result.build.formattedBuildTime()]),
element('li', ['Revision: '].concat(revisionDescription)),
element('li', ['Build: ', element('a', {'href': result.build.buildUrl()}, [buildNumber]), ' (',
element('a', {'href': resultsPage}, ['results']), ')']),
element('li', ['Actual: ' + actual]),
element('li', ['Expected: ' + expected]),
])
]));
}
var cell = element('span', {
'class': actual + ' resultCell',
}, [anchor]);
return cell;
}
TestResultsView._populateTestPane = function(testName, results, section) {
var test = {name: testName, category: 'LayoutTest'}; // FIXME: Use the real test object.
var table = element('table', {'class': 'resultsTable tablesorter'}, [element('caption', [this._linkifiedTestName(test)])]);
var resultsByBuilder = results['builders'];
var buildTimes = new Array();
for (var builderId in resultsByBuilder) {
var results = resultsByBuilder[builderId];
this._createBuildsAndComputeSlownessOfResults(builderId, results);
for (var i = 0; i < results.length; i++) {
var time = results[i].build.time();
if (buildTimes.indexOf(time) < 0)
buildTimes.push(time);
}
}
buildTimes.sort(function (a, b) { return b - a; });
var repositories = [];
for (var repositoryId in this._repositories)
repositories.push(this._repositories[repositoryId]);
table.appendChild(this._createTestResultHeader('Builder', repositories));
var tbody = element('tbody');
var builders = [];
for (var builderId in resultsByBuilder)
builders.push(this._builders[builderId]);
var self = this;
this._sortObjectsByName(builders).forEach(function (builder) {
tbody.appendChild(self._createTestResultRow(builder.name, resultsByBuilder[builder.id], test, builder, buildTimes, repositories));
})
table.appendChild(tbody);
section.appendChild(table);
$(table).tablesorter();
}
TestResultsView._sortObjectsByName = function (list) {
return list.sort(function (a, b) {
if (a.name < b.name)
return -1;
if (a.name > b.name)
return 1;
return 0;
});
}
TestResultsView._urlFromTest = function (test) {
var category = this._testCategories[test.category];
if (!category)
return null;
return category.url.replace(/\$testName/g, test.name);
}
TestResultsView._linkifiedTestName = function (test) {
var url = this._urlFromTest(test);
return url ? element('a', {'href': url}, [test.name]) : test.name;
}
TestResultsView._createTestResultHeader = function (labelForFirstColumn, repositories) {
var latestResultsLabel = '';
if (repositories) {
latestResultsLabel = ['Latest results', element('br'),
repositories.map(function (repository) { return repository.name; }).join(' / ')];
}
return element('thead', [element('tr', [
element('th', [labelForFirstColumn]),
element('th', ['Bug']),
element('th', ['Expectations']),
element('th', ['Slowest']),
element('th', latestResultsLabel),
element('th')])]);
}
TestResultsView._createBuildsAndComputeSlownessOfResults = function (builderId, results) {
for (var i = 0; i < results.length; i++) {
var result = results[i];
result.builder = builderId;
result.build = new TestBuild(this._repositories, this._builders, result);
result.roundedTime = result.time > 10000 ? Math.round(result.time / 1000) : Math.round(result.time / 100) / 10;
result.isSlow = result.time > 1000;
}
}
TestResultsView._createTestResultRow = function (title, results, test, builder, buildTimes, repositories) {
var sortedResults = results.sort(function (result1, result2) { return result2.build.time() - result1.build.time(); });
var cells = new Array();
function addEmptyCell() { cells.push(element('span', {'class': 'resultCell'}, [element('a')])); }
var buildTimeIndex = 0;
for (var i = 0; i < sortedResults.length; i++) {
var result = sortedResults[i];
if (buildTimes) {
while (buildTimes[buildTimeIndex] > result.build.time()) {
addEmptyCell();
buildTimeIndex++;
}
if (buildTimes[buildTimeIndex] == result.build.time())
buildTimeIndex++;
}
cells.push(this._createResultCell(builder.master, builder.name, result, sortedResults[i - 1]));
}
if (buildTimes) {
while (buildTimeIndex < buildTimes.length) {
addEmptyCell();
buildTimeIndex++;
}
}
var seenBugLink = false;
var formattedModifiers = sortedResults[0].modifiers.split(' ').map(function (modifier) {
if (modifier.indexOf('/') > 0) {
seenBugLink = true;
return element('a', {'href': (modifier.indexOf('http') == 0 ? '' : 'http://') + modifier}, [modifier]);
}
return modifier;
});
if (!seenBugLink) {
// FIXME: Make bug tracker configurable.
for (var i = 0; i < this._newBugLinks.length; i++) {
if (i)
formattedModifiers.push(' / ');
var link = this._newBugLinks[i];
formattedModifiers.push(element('a', {'href': link.url.replace(/\$testName/g, test.name)}, link.label));
}
}
var slowestTime = Math.max.apply(Math, results.map(function (result) { return result.roundedTime; }));
if (slowestTime >= 1)
slowestTime += 's';
else
slowestTime = '';
var latestRevisions = [];
if (repositories) {
var build = sortedResults[0].build;
for (var i = 0; i < repositories.length; i++) {
if (!build.revision(repositories[i].id))
continue;
var revisionInfo = build.formattedRevision(repositories[i].id);
if (latestRevisions.length)
latestRevisions.push(' / ');
if (revisionInfo.url)
latestRevisions.push(element('a', {'href': revisionInfo.url}, [revisionInfo.label]));
else
latestRevisions.push(revisionInfo.label);
}
}
return element('tr', [
element('th', [title]),
element('td', {'class': 'modifiers'}, formattedModifiers),
element('td', {'class': 'expected'}, [sortedResults[0].expected]),
element('td', {'class': 'slowestTime'}, [slowestTime])]
.concat(element('td', latestRevisions))
.concat([element('td', cells)]));
}
TestResultsView.fetchTest = function (testName) {
if (this._tests.indexOf(testName) >= 0)
return;
var self = this;
var closeButton = element('div', {'class': 'closeButton'});
closeButton.innerHTML = '<svg viewBox="0 0 100 100"><g stroke-width="10">'
+ '<circle cx="50" cy="50" r="45" fill="transparent"></circle><polygon points="30,30 70,70"></polygon>'
+ '<polygon points="30,70 70,30"></polygon></g></svg>';
closeButton.addEventListener('click', function (event) {
self._removeTest(testName);
section.parentNode.removeChild(section);
event.preventDefault();
});
var section = element('section', {'id': testName, 'class': 'testResults'}, [closeButton, 'Loading...']);
document.getElementById('testView').appendChild(section);
var xhr = new XMLHttpRequest();
xhr.open("GET", 'api/results.php?test=' + testName, true);
xhr.onload = function(event) {
var response = JSON.parse(xhr.response);
section.removeChild(section.lastChild); // Remove Loading...
if (response['status'] != 'OK') {
section.appendChild(text('Failed to load results for ' + testName + ': ' + response['status']));
return;
}
self._populateTestPane(testName, response, section);
}
xhr.send();
this._tests.push(testName);
}
TestResultsView._removeTest = function (testName) {
var index = this._tests.indexOf(testName);
if (index < 0)
return;
this._tests.splice(index, 1);
this.updateLocationHash();
}
TestResultsView.fetchTests = function (testNames, doNotUpdateHash) {
for (var i = 0; i < testNames.length; i++)
this.fetchTest(testNames[i], doNotUpdateHash);
if (!doNotUpdateHash)
this.updateLocationHash();
}
TestResultsView._populateBuilderPane = function(builderName, failureType, results, section) {
var table = element('table', {'class': 'resultsTable tablesorter'}, [element('caption', [builderName])]);
var resultsByBuilder = results['builders'];
var resultsByTests;
var builderId;
for (var currentBuilderId in resultsByBuilder) {
builderId = currentBuilderId;
resultsByTests = resultsByBuilder[builderId];
}
if (!resultsByTests)
return;
table.appendChild(this._createTestResultHeader('Test'));
var tests = [];
for (var testId in resultsByTests)
tests.push(this._availableTests[testId]);
var tbody = element('tbody');
var builder = this._builders[builderId];
var self = this;
this._sortObjectsByName(tests).forEach(function (test) {
var results = resultsByTests[test.id];
if (!results.length)
return;
self._createBuildsAndComputeSlownessOfResults(builderId, results);
var externalTestLink = element('a', {'class': 'externalTestLink',
'href': self._urlFromTest(test),
'title': 'View the source code of ' + test.name});
var testName = element('a', {'href':'#',
'title': 'Show results of ' + test.name + ' on all platforms'}, [test.name]);
testName.onclick = function (event) {
self.fetchTests([this.textContent]);
event.preventDefault();
return false;
}
tbody.appendChild(self._createTestResultRow(element('span', [testName, externalTestLink]), results, test, builder));
});
table.appendChild(tbody);
section.appendChild(table);
$(table).tablesorter();
}
TestResultsView.fetchFailingTestsForBuilder = function (builderName, numberOfDays, failureType, doNotUpdateHash) {
var section = element('section', {'class': 'testResults'}, ['Loading...']);
var container = document.getElementById('builderFailingTestsView');
container.innerHTML = '';
container.appendChild(section);
var self = this;
var xhr = new XMLHttpRequest();
var builderId = this._builderByName[builderName].id;
xhr.open('GET', 'data/' + builderId + '-' + failureType + '.json', true);
xhr.onload = function(event) {
if (xhr.status != 200) {
section.appendChild(text('Failed to load results for ' + builderName + ': ' + xhr.status));
return;
}
var response = JSON.parse(xhr.response);
section.innerHTML = '';
if (response['status'] != 'OK') {
section.appendChild(text('Failed to load results for ' + builderName + ': ' + response['status']));
return;
}
// FIXME: Normalize failureType.
self._currentBuilder = builderName;
self._currentBuilderFailureType = failureType;
self._currentBuilderDays = numberOfDays;
self._populateBuilderPane(builderName, failureType, response, section);
if (!doNotUpdateHash)
self.updateLocationHash();
}
xhr.send();
}
TestResultsView._createLocationHash = function (tests) {
var params = {
'tests': tests.join(','),
'builder': this._currentBuilder,
'builderFailureType': this._currentBuilderFailureType,
'builderDays': this._currentBuilderDays,
};
var hash = '';
for (var key in params) {
var value = params[key];
if (value === null || value === undefined)
continue;
hash += '&' + decodeURIComponent(key) + '=' + decodeURIComponent(value);
}
return hash;
}
TestResultsView.updateLocationHash = function () {
location.hash = this._createLocationHash(this._tests);
this._oldHash = location.hash;
}
TestResultsView.locationHashChanged = function () {
var newHash = location.hash;
if (newHash == this._oldHash)
return;
this._oldHash = newHash;
this._tests = [];
this.loadTestsFromLocationHash();
}
TestResultsView.loadTestsFromLocationHash = function () {
var parsed = {};
location.hash.substr(1).split('&').forEach(function (component) {
var equalPosition = component.indexOf('=');
if (equalPosition < 0)
return;
var name = decodeURIComponent(component.substr(0, equalPosition));
parsed[name] = decodeURIComponent(component.substr(equalPosition + 1));
});
var doNotUpdateHash = true;
if (parsed['tests']) {
document.getElementById('testView').innerHTML = '';
this.fetchTests(parsed['tests'].split(','), doNotUpdateHash);
}
if (parsed['builder']) {
this.fetchFailingTestsForBuilder(
parsed['builder'],
parseInt(parsed['builderDays']) || 5,
parsed['builderFailureType'] || 'failing', doNotUpdateHash);
}
return parsed;
}
function fetchManifest(callback) {
var xhr = new XMLHttpRequest();
xhr.open("GET", 'api/manifest.php', true);
xhr.onload = function(event) {
var response = JSON.parse(xhr.response);
if (response['status'] != 'OK') {
alert('Failed to load manifest:' + response['status']);
console.log(response);
return;
}
callback(response);
}
xhr.send();
}
fetchManifest(function (response) {
var testNames = response['tests'].map(function (test) { return test['name']; })
var input = document.getElementById('testName');
input.autocompleter = new Autocompleter(input, testNames);
input.form.onsubmit = function (event) {
addTests(input.value);
event.preventDefault();
return false;
}
function addTests(filter) {
var candidates = input.autocompleter.filterCandidates(filter);
if (candidates.length > 15) {
if (!confirm('Are you sure you want to add ' + candidates.length + ' tests that match this substring?'))
return;
}
for (var i = 0; i < candidates.length; i++)
TestResultsView.fetchTest(candidates[i]);
TestResultsView.updateLocationHash();
}
var builderListView = document.getElementById('builderListView');
TestResultsView._sortObjectsByName(response['builders']).forEach(function (builder) {
builderListView.appendChild(element('option', [builder.name]));
});
var builderDaysView = document.getElementById('builderDaysView');
var builderFailureTypeView = document.getElementById('builderFailureTypeView');
function updateBuilderView() {
if (builderListView.value)
TestResultsView.fetchFailingTestsForBuilder(builderListView.value, builderDaysView.value, builderFailureTypeView.value);
}
builderListView.addEventListener('change', updateBuilderView);
builderDaysView.addEventListener('change', updateBuilderView);
builderFailureTypeView.addEventListener('change', updateBuilderView);
function mapById(items) {
var results = {};
for (var i = 0; i < items.length; i++)
results[items[i].id] = items[i];
return results;
}
TestResultsView.setAvailableTests(mapById(response['tests']));
TestResultsView.setBuilders(mapById(response['builders']));
TestResultsView.setSlaves(mapById(response['slaves']));
TestResultsView.setRepositories(mapById(response['repositories']));
TestResultsView.setTestCategories(response['testCategories']);
TestResultsView.setNewBugLinks(response['newBugLinks']);
// FIXME: Updating location.href shouldn't be TestResultsView's responsibility.
var parsedStates = TestResultsView.loadTestsFromLocationHash();
if (parsedStates['builder']) {
builderListView.value = parsedStates['builder'];
builderDaysView.value = parsedStates['builderDays'];
builderFailureTypeView.value = parsedStates['builderFailureType'];
}
});
function pasteHelper(input, event) {
function removeJunkFromNRWTStdout(input) {
return input.replace(/(\[[\w ]+\])|(.+\:.+)/g, '').replace(/^[ \t]+|[ \t]+$/gm, '');
}
function removeJunkFromResultsPage(input) {
return input.replace(/(^[^\/]+$)|(^\+)/gm, '').replace(/\([^)]+\)/g, '').replace(/\s+[A-Za-z]+(\s+[A-Za-z]+)*\s*$/gm, '');
}
var text = event.clipboardData.getData('text/plain');
if (text.indexOf('\n') < 0)
return;
var urls = removeJunkFromResultsPage(removeJunkFromNRWTStdout(text)).split('\n');
TestResultsView.fetchTests(urls.filter(function (url) { return url.length; }));
event.preventDefault();
}
</script>
</body>
</html>