blob: 59f7cc2d7268a467c1911c91eceeeabf51e92e74 [file] [log] [blame]
// Copyright (C) 2012 Google Inc. All rights reserved.
// Copyright (C) 2012 Zan Dobersek <zandobersek@gmail.com>
//
// 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 loader = loader || {};
(function() {
var TEST_RESULTS_SERVER = 'https://webkit-test-results.webkit.org/';
function pathToBuilderResultsFile(builderName) {
return TEST_RESULTS_SERVER + 'testfile?builder=' + builderName +
'&master=' + builderMaster(builderName).name +
'&testtype=' + g_history.crossDashboardState.testType + '&name=';
}
loader.request = function(url, success, error, opt_isBinaryData)
{
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
if (opt_isBinaryData)
xhr.overrideMimeType('text/plain; charset=x-user-defined');
xhr.onreadystatechange = function(e) {
if (xhr.readyState == 4) {
if (xhr.status == 200)
success(xhr);
else
error(xhr);
}
}
xhr.send();
}
loader.Loader = function()
{
this._loadingSteps = [
this._loadBuildersList,
this._loadResultsFiles,
this._loadExpectationsFiles,
];
this._buildersThatFailedToLoad = [];
this._staleBuilders = [];
this._errors = new ui.Errors();
// TODO(jparent): Pass in the appropriate history obj per db.
this._history = g_history;
}
// TODO(aboxhall): figure out whether this is a performance bottleneck and
// change calling code to understand the trie structure instead if necessary.
loader.Loader._flattenTrie = function(trie, prefix)
{
var result = {};
for (var name in trie) {
var fullName = prefix ? prefix + "/" + name : name;
var data = trie[name];
if ("results" in data)
result[fullName] = data;
else {
var partialResult = loader.Loader._flattenTrie(data, fullName);
for (var key in partialResult) {
result[key] = partialResult[key];
}
}
}
return result;
}
loader.Loader.prototype = {
load: function()
{
this._loadNext();
},
showErrors: function()
{
this._errors.show();
},
_loadNext: function()
{
var loadingStep = this._loadingSteps.shift();
if (!loadingStep) {
this._addErrors();
this._history.initialize();
return;
}
loadingStep.apply(this);
},
_loadBuildersList: function()
{
loadBuildersList(currentBuilderGroupName(), this._history.crossDashboardState.testType);
this._loadNext();
},
_loadResultsFiles: function()
{
for (var builderName in currentBuilders())
this._loadResultsFileForBuilder(builderName);
},
_loadResultsFileForBuilder: function(builderName)
{
var resultsFilename;
if (history.isTreeMap())
resultsFilename = 'times_ms.json';
else if (this._history.crossDashboardState.showAllRuns)
resultsFilename = 'results.json';
else
resultsFilename = 'results-small.json';
var resultsFileLocation = pathToBuilderResultsFile(builderName) + resultsFilename;
loader.request(resultsFileLocation,
partial(function(loader, builderName, xhr) {
loader._handleResultsFileLoaded(builderName, xhr.responseText);
}, this, builderName),
partial(function(loader, builderName, xhr) {
loader._handleResultsFileLoadError(builderName);
}, this, builderName));
},
_handleResultsFileLoaded: function(builderName, fileData)
{
if (history.isTreeMap())
this._processTimesJSONData(builderName, fileData);
else
this._processResultsJSONData(builderName, fileData);
// We need this work-around for webkit.org/b/50589.
if (!g_resultsByBuilder[builderName]) {
this._handleResultsFileLoadError(builderName);
return;
}
this._handleResourceLoad();
},
_processTimesJSONData: function(builderName, fileData)
{
// FIXME: We should probably include the builderName in the JSON
// rather than relying on only loading one JSON file per page.
g_resultsByBuilder[builderName] = JSON.parse(fileData);
},
_processResultsJSONData: function(builderName, fileData)
{
var builds = JSON.parse(fileData);
var json_version = builds['version'];
for (var builderName in builds) {
if (builderName == 'version')
continue;
// If a test suite stops being run on a given builder, we don't want to show it.
// Assume any builder without a run in two weeks for a given test suite isn't
// running that suite anymore.
// FIXME: Grab which bots run which tests directly from the buildbot JSON instead.
var lastRunSeconds = builds[builderName].secondsSinceEpoch[0];
if ((Date.now() / 1000) - lastRunSeconds > ONE_WEEK_SECONDS)
continue;
if ((Date.now() / 1000) - lastRunSeconds > ONE_DAY_SECONDS)
this._staleBuilders.push(builderName);
if (json_version >= 4)
builds[builderName][TESTS_KEY] = loader.Loader._flattenTrie(builds[builderName][TESTS_KEY]);
g_resultsByBuilder[builderName] = builds[builderName];
}
},
_handleResultsFileLoadError: function(builderName)
{
console.error('Failed to load results file for ' + builderName + '.');
// FIXME: loader shouldn't depend on state defined in dashboard_base.js.
this._buildersThatFailedToLoad.push(builderName);
// Remove this builder from builders, so we don't try to use the
// data that isn't there.
delete currentBuilders()[builderName];
// Proceed as if the resource had loaded.
this._handleResourceLoad();
},
_handleResourceLoad: function()
{
if (this._haveResultsFilesLoaded())
this._loadNext();
},
_haveResultsFilesLoaded: function()
{
for (var builder in currentBuilders()) {
if (!g_resultsByBuilder[builder])
return false;
}
return true;
},
_loadExpectationsFiles: function()
{
if (!isFlakinessDashboard() && !this._history.crossDashboardState.useTestData) {
this._loadNext();
return;
}
var expectationsFilesToRequest = {};
traversePlatformsTree(function(platform, platformName) {
if (platform.fallbackPlatforms)
platform.fallbackPlatforms.forEach(function(fallbackPlatform) {
var fallbackPlatformObject = platformObjectForName(fallbackPlatform);
if (fallbackPlatformObject.expectationsDirectory && !(fallbackPlatform in expectationsFilesToRequest))
expectationsFilesToRequest[fallbackPlatform] = EXPECTATIONS_URL_BASE_PATH + fallbackPlatformObject.expectationsDirectory + '/TestExpectations';
});
if (platform.expectationsDirectory)
expectationsFilesToRequest[platformName] = EXPECTATIONS_URL_BASE_PATH + platform.expectationsDirectory + '/TestExpectations';
});
for (platformWithExpectations in expectationsFilesToRequest)
loader.request(expectationsFilesToRequest[platformWithExpectations],
partial(function(loader, platformName, xhr) {
g_expectationsByPlatform[platformName] = getParsedExpectations(xhr.responseText);
delete expectationsFilesToRequest[platformName];
if (!Object.keys(expectationsFilesToRequest).length)
loader._loadNext();
}, this, platformWithExpectations),
partial(function(platformName, xhr) {
console.error('Could not load expectations file for ' + platformName);
}, platformWithExpectations));
},
_addErrors: function()
{
if (this._buildersThatFailedToLoad.length)
this._errors.addError('ERROR: Failed to get data from ' + this._buildersThatFailedToLoad.toString() +'.');
if (this._staleBuilders.length)
this._errors.addError('ERROR: Data from ' + this._staleBuilders.toString() + ' is more than 1 day stale.');
}
}
})();