blob: d5caac2f603f886c53590d9bb53b5cba5fbf22bf [file] [log] [blame]
// Copyright (C) 2013 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.
//
// @fileoverview Creates a dashboard for tracking number of passes/failures per run.
//
// Currently, only webkit tests are supported, but adding other test types
// should just require the following steps:
// -generate results.json for these tests
// -copy them to the appropriate location
// -add the builder name to the list of builders in dashboard_base.js.
function generatePage(historyInstance)
{
var html = ui.html.testTypeSwitcher(true);
html += '<div>' +
ui.html.checkbox('rawValues', 'Show raw values', g_history.dashboardSpecificState.rawValues) +
ui.html.checkbox('showOutliers', 'Show outliers', g_history.dashboardSpecificState.showOutliers) +
'</div>';
for (var builder in currentBuilders())
html += htmlForBuilder(builder);
document.body.innerHTML = html;
}
function handleValidHashParameter(historyInstance, key, value)
{
switch(key) {
case 'rawValues':
case 'showOutliers':
historyInstance.dashboardSpecificState[key] = value == 'true';
return true;
default:
return false;
}
}
var defaultDashboardSpecificStateValues = {
rawValues: false,
showOutliers: true
};
var aggregateResultsConfig = {
defaultStateValues: defaultDashboardSpecificStateValues,
generatePage: generatePage,
handleValidHashParameter: handleValidHashParameter,
};
// FIXME(jparent): Eventually remove all usage of global history object.
var g_history = new history.History(aggregateResultsConfig);
g_history.parseCrossDashboardParameters();
g_totalFailureCounts = {};
function totalFailureCountFor(builder)
{
if (!g_totalFailureCounts[builder])
g_totalFailureCounts[builder] = results.testCounts(g_resultsByBuilder[builder][results.NUM_FAILURES_BY_TYPE]);
return g_totalFailureCounts[builder];
}
function htmlForBuilder(builder)
{
var html = '<div class=container><h2>' + builder + '</h2>';
if (g_history.dashboardSpecificState.rawValues) {
html += htmlForTestType(builder);
} else {
html += '<a href="timeline_explorer.html' + (location.hash ? location.hash + '&' : '#') + 'builder=' + builder + '">' +
chartHTML(builder) + '</a>';
}
return html + '</div>';
}
function chartHTML(builder)
{
var resultsForBuilder = g_resultsByBuilder[builder];
var totalFailingTests = totalFailureCountFor(builder).totalFailingTests;
// Some bots don't properly record revision numbers. Handle that gracefully.
var label, values;
if (currentBuilderGroup().isToTBlink && resultsForBuilder[results.BLINK_REVISIONS]) {
label = 'Blink Revision';
values = resultsForBuilder[results.BLINK_REVISIONS]
} else if (resultsForBuilder[results.CHROME_REVISIONS]) {
label = 'Chrome Revision';
values = resultsForBuilder[results.CHROME_REVISIONS];
} else {
label = 'Build Number';
values = resultsForBuilder[results.BUILD_NUMBERS];
}
var start = values[totalFailingTests.length - 1];
var end = values[0];
var html = chart("Total failing", {"": totalFailingTests}, label, start, end);
var values = resultsForBuilder[results.NUM_FAILURES_BY_TYPE];
// Don't care about number of passes for the charts.
delete(values[results.PASS]);
return html + chart("Detailed breakdown", values, label, start, end);
}
var LABEL_COLORS = ['FF0000', '00FF00', '0000FF', '000000', 'FF6EB4', 'FFA812', '9B30FF', '00FFCC'];
// FIXME: Find a better way to exclude outliers. This is just so we exclude
// runs where every test failed.
var MAX_VALUE = 2000;
function filteredValues(values, desiredNumberOfPoints)
{
// Filter out values to make the graph a bit more readable and to keep URLs
// from exceeding the browsers max length restriction.
var filterAmount = Math.floor(values.length / desiredNumberOfPoints);
return values.filter(function(element, index, array) {
if (!g_history.dashboardSpecificState.showOutliers && element > MAX_VALUE)
return false;
if (filterAmount <= 1)
return true;
// Include the most recent and oldest values.
return index % filterAmount == 0 || index == array.length - 1;
});
}
function chartUrl(title, values, revisionLabel, startRevision, endRevision, desiredNumberOfPoints)
{
var maxValue = 0;
for (var expectation in values)
maxValue = Math.max(maxValue, Math.max.apply(null, filteredValues(values[expectation], desiredNumberOfPoints)));
var chartData = '';
var labels = '';
var numLabels = 0;
for (var expectation in values) {
chartData += (chartData ? ',' : 'e:') + extendedEncode(filteredValues(values[expectation], desiredNumberOfPoints).reverse(), maxValue);
if (expectation) {
numLabels++;
labels += (labels ? '|' : '') + expectation;
}
}
var url = "http://chart.apis.google.com/chart?cht=lc&chs=600x400&chd=" +
chartData + "&chg=15,15,1,3&chxt=x,x,y&chxl=1:||" + revisionLabel +
"|&chxr=0," + startRevision + "," + endRevision + "|2,0," + maxValue + "&chtt=" + title;
if (labels)
url += "&chdl=" + labels + "&chco=" + LABEL_COLORS.slice(0, numLabels).join(',');
return url;
}
function chart(title, values, revisionLabel, startRevision, endRevision)
{
var desiredNumberOfPoints = 400;
var url = chartUrl(title, values, revisionLabel, startRevision, endRevision, desiredNumberOfPoints);
while (url.length >= 2048) {
// Decrease the desired number of points gradually until we get a URL that
// doesn't exceed chartserver's max URL length.
desiredNumberOfPoints = 3 / 4 * desiredNumberOfPoints;
url = chartUrl(title, values, revisionLabel, startRevision, endRevision, desiredNumberOfPoints);
}
return '<img src="' + url + '">';
}
function htmlForRevisionRows(resultsForBuilder, numColumns)
{
var html = '';
if (resultsForBuilder[results.BLINK_REVISIONS])
html += htmlForTableRow('Blink Revision', resultsForBuilder[results.BLINK_REVISIONS].slice(0, numColumns));
if (resultsForBuilder[results.CHROME_REVISIONS])
html += htmlForTableRow('Chrome Revision', resultsForBuilder[results.CHROME_REVISIONS].slice(0, numColumns));
return html;
}
function htmlForTestType(builder)
{
var counts = totalFailureCountFor(builder);
var totalFailing = counts.totalFailingTests;
var totalTests = counts.totalTests;
var percent = [];
for (var i = 0; i < totalTests.length; i++) {
var percentage = 100 * (totalTests[i] - totalFailing[i]) / totalTests[i];
// Round to the nearest tenth of a percent.
percent.push(Math.round(percentage * 10) / 10 + '%');
}
var resultsForBuilder = g_resultsByBuilder[builder];
html = '<table><tbody>' +
htmlForRevisionRows(resultsForBuilder, totalTests.length) +
htmlForTableRow('Percent passed', percent) +
htmlForTableRow('Failures', totalFailing) +
htmlForTableRow('Total Tests', totalTests);
var values = resultsForBuilder[results.NUM_FAILURES_BY_TYPE];
for (var expectation in values)
html += htmlForTableRow(expectation, values[expectation]);
return html + '</tbody></table>';
}
function htmlForTableRow(columnName, values)
{
return '<tr><td>' + columnName + '</td><td>' + values.join('</td><td>') + '</td></tr>';
}
// Taken from http://code.google.com/apis/chart/docs/data_formats.html.
function extendedEncode(arrVals, maxVal)
{
var map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.';
var mapLength = map.length;
var mapLengthSquared = mapLength * mapLength;
var chartData = '';
for (var i = 0, len = arrVals.length; i < len; i++) {
// In case the array vals were translated to strings.
var numericVal = new Number(arrVals[i]);
// Scale the value to maxVal.
var scaledVal = Math.floor(mapLengthSquared * numericVal / maxVal);
if(scaledVal > mapLengthSquared - 1)
chartData += "..";
else if (scaledVal < 0)
chartData += '__';
else {
// Calculate first and second digits and add them to the output.
var quotient = Math.floor(scaledVal / mapLength);
var remainder = scaledVal - mapLength * quotient;
chartData += map.charAt(quotient) + map.charAt(remainder);
}
}
return chartData;
}
window.addEventListener('load', function() {
var resourceLoader = new loader.Loader();
resourceLoader.load();
}, false);