blob: 3c3f852435a7c35bbde0433142944e6be9a2cf09 [file] [log] [blame]
/*
* Copyright (C) 2011 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:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS 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 APPLE INC. OR ITS 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 model = model || {};
(function () {
var kCommitLogLength = 50;
model.state = {};
model.state.failureAnalysisByTest = {};
model.state.rebaselineQueue = [];
model.state.expectationsUpdateQueue = [];
function findAndMarkRevertedRevisions(commitDataList)
{
var revertedRevisions = {};
$.each(commitDataList, function(index, commitData) {
if (commitData.revertedRevision)
revertedRevisions[commitData.revertedRevision] = true;
});
$.each(commitDataList, function(index, commitData) {
if (commitData.revision in revertedRevisions)
commitData.wasReverted = true;
});
}
function fuzzyFind(testName, commitData)
{
var indexOfLastDot = testName.lastIndexOf('.');
var stem = indexOfLastDot == -1 ? testName : testName.substr(0, indexOfLastDot);
return commitData.message.indexOf(stem) != -1;
}
function heuristicallyNarrowRegressionRange(failureAnalysis)
{
var commitDataList = model.state.recentCommits;
var commitDataIndex = commitDataList.length - 1;
for(var revision = failureAnalysis.newestPassingRevision + 1; revision <= failureAnalysis.oldestFailingRevision; ++revision) {
while (commitDataIndex >= 0 && commitDataList[commitDataIndex].revision < revision)
--commitDataIndex;
var commitData = commitDataList[commitDataIndex];
if (commitData.revision != revision)
continue;
if (fuzzyFind(failureAnalysis.testName, commitData)) {
failureAnalysis.oldestFailingRevision = revision;
failureAnalysis.newestPassingRevision = revision - 1;
return;
}
}
}
model.queueForRebaseline = function(failureInfo)
{
model.state.rebaselineQueue.push(failureInfo);
};
model.takeRebaselineQueue = function()
{
var queue = model.state.rebaselineQueue;
model.state.rebaselineQueue = [];
return queue;
};
model.queueForExpectationUpdate = function(failureInfo)
{
model.state.expectationsUpdateQueue.push(failureInfo);
};
model.takeExpectationUpdateQueue = function()
{
var queue = model.state.expectationsUpdateQueue;
model.state.expectationsUpdateQueue = [];
return queue;
};
var g_commitIndex = {};
model.updateRecentCommits = function(callback)
{
trac.recentCommitData('trunk', kCommitLogLength, function(commitDataList) {
model.state.recentCommits = commitDataList;
updateCommitIndex();
findAndMarkRevertedRevisions(model.state.recentCommits);
callback();
});
};
function updateCommitIndex()
{
model.state.recentCommits.forEach(function(commitData) {
g_commitIndex[commitData.revision] = commitData;
});
}
model.commitDataListForRevisionRange = function(fromRevision, toRevision)
{
var result = [];
for (var revision = fromRevision; revision <= toRevision; ++revision) {
var commitData = g_commitIndex[revision];
if (commitData)
result.push(commitData);
}
return result;
};
model.buildersInFlightForRevision = function(revision)
{
var builders = {};
Object.keys(model.state.resultsByBuilder).forEach(function(builderName) {
var results = model.state.resultsByBuilder[builderName];
if (parseInt(results.revision) < revision)
builders[builderName] = { actual: 'BUILDING' };
});
return builders;
};
model.latestRevision = function()
{
return model.state.recentCommits[0].revision;
}
model.latestRevisionWithNoBuildersInFlight = function()
{
var revision = 0;
Object.keys(model.state.resultsByBuilder).forEach(function(builderName) {
var results = model.state.resultsByBuilder[builderName];
if (!results.revision)
return;
var testedRevision = parseInt(results.revision);
revision = revision ? Math.min(revision, testedRevision) : testedRevision;
});
return revision;
}
model.updateResultsByBuilder = function(callback)
{
var platformBuilders = config.currentBuilders();
results.fetchResultsByBuilder(Object.keys(platformBuilders), function(resultsByBuilder) {
model.state.resultsByBuilder = resultsByBuilder;
callback();
});
};
model.analyzeUnexpectedFailures = function(callback, completionCallback)
{
var unexpectedFailures = results.unexpectedFailuresByTest(model.state.resultsByBuilder);
$.each(model.state.failureAnalysisByTest, function(testName, failureAnalysis) {
if (!(testName in unexpectedFailures))
delete model.state.failureAnalysisByTest[testName];
});
var tracker = new base.RequestTracker(Object.keys(unexpectedFailures).length, completionCallback);
$.each(unexpectedFailures, function(testName, resultNodesByBuilder) {
var builderNameList = Object.keys(resultNodesByBuilder);
results.unifyRegressionRanges(builderNameList, testName, function(oldestFailingRevision, newestPassingRevision) {
var failureAnalysis = {
'testName': testName,
'resultNodesByBuilder': resultNodesByBuilder,
'oldestFailingRevision': oldestFailingRevision,
'newestPassingRevision': newestPassingRevision,
};
heuristicallyNarrowRegressionRange(failureAnalysis);
var previousFailureAnalysis = model.state.failureAnalysisByTest[testName];
if (previousFailureAnalysis
&& previousFailureAnalysis.oldestFailingRevision <= failureAnalysis.oldestFailingRevision
&& previousFailureAnalysis.newestPassingRevision >= failureAnalysis.newestPassingRevision) {
failureAnalysis.oldestFailingRevision = previousFailureAnalysis.oldestFailingRevision;
failureAnalysis.newestPassingRevision = previousFailureAnalysis.newestPassingRevision;
}
model.state.failureAnalysisByTest[testName] = failureAnalysis;
callback(failureAnalysis);
tracker.requestComplete();
});
});
};
model.unexpectedFailureInfoForTestName = function(testName)
{
var resultsByTest = results.unexpectedFailuresByTest(model.state.resultsByBuilder);
return Object.keys(resultsByTest[testName]).map(function(builderName) {
return results.failureInfoForTestAndBuilder(resultsByTest, testName, builderName);
});
};
model.analyzeUnexpectedSuccesses = function(callback)
{
var unexpectedSuccesses = results.unexpectedSuccessesByTest(model.state.resultsByBuilder);
$.each(unexpectedSuccesses, function(testName, resultNodesByBuilder) {
var successAnalysis = {
'testName': testName,
'resultNodesByBuilder': resultNodesByBuilder,
};
// FIXME: Consider looking at the history to see how long this test
// has been unexpectedly passing.
callback(successAnalysis);
});
};
model.analyzeexpectedFailures = function(callback)
{
var expectedFailures = results.expectedFailuresByTest(model.state.resultsByBuilder);
$.each(expectedFailures, function(testName, resultNodesByBuilder) {
var failureAnalysis = {
'testName': testName,
'resultNodesByBuilder': resultNodesByBuilder,
};
// FIXME: Consider looking at the history to see how long this test
// has been failing.
callback(failureAnalysis);
});
};
})();