Home | History | Annotate | Download | only in scripts
      1 /*
      2  * Copyright (C) 2011 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
     14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
     15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
     17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
     23  * THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 var model = model || {};
     27 
     28 (function () {
     29 
     30 var kCommitLogLength = 50;
     31 
     32 model.state = {};
     33 model.state.failureAnalysisByTest = {};
     34 model.state.rebaselineQueue = [];
     35 model.state.expectationsUpdateQueue = [];
     36 
     37 function findAndMarkRevertedRevisions(commitDataList)
     38 {
     39     var revertedRevisions = {};
     40     $.each(commitDataList, function(index, commitData) {
     41         if (commitData.revertedRevision)
     42             revertedRevisions[commitData.revertedRevision] = true;
     43     });
     44     $.each(commitDataList, function(index, commitData) {
     45         if (commitData.revision in revertedRevisions)
     46             commitData.wasReverted = true;
     47     });
     48 }
     49 
     50 function fuzzyFind(testName, commitData)
     51 {
     52     var indexOfLastDot = testName.lastIndexOf('.');
     53     var stem = indexOfLastDot == -1 ? testName : testName.substr(0, indexOfLastDot);
     54     return commitData.message.indexOf(stem) != -1;
     55 }
     56 
     57 function heuristicallyNarrowRegressionRange(failureAnalysis)
     58 {
     59     var commitDataList = model.state.recentCommits;
     60     var commitDataIndex = commitDataList.length - 1;
     61 
     62     for(var revision = failureAnalysis.newestPassingRevision + 1; revision <= failureAnalysis.oldestFailingRevision; ++revision) {
     63         while (commitDataIndex >= 0 && commitDataList[commitDataIndex].revision < revision)
     64             --commitDataIndex;
     65         var commitData = commitDataList[commitDataIndex];
     66         if (commitData.revision != revision)
     67             continue;
     68         if (fuzzyFind(failureAnalysis.testName, commitData)) {
     69             failureAnalysis.oldestFailingRevision = revision;
     70             failureAnalysis.newestPassingRevision = revision - 1;
     71             return;
     72         }
     73     }
     74 }
     75 
     76 model.queueForRebaseline = function(failureInfo)
     77 {
     78     model.state.rebaselineQueue.push(failureInfo);
     79 };
     80 
     81 model.takeRebaselineQueue = function()
     82 {
     83     var queue = model.state.rebaselineQueue;
     84     model.state.rebaselineQueue = [];
     85     return queue;
     86 };
     87 
     88 model.queueForExpectationUpdate = function(failureInfo)
     89 {
     90     model.state.expectationsUpdateQueue.push(failureInfo);
     91 };
     92 
     93 model.takeExpectationUpdateQueue = function()
     94 {
     95     var queue = model.state.expectationsUpdateQueue;
     96     model.state.expectationsUpdateQueue = [];
     97     return queue;
     98 };
     99 
    100 var g_commitIndex = {};
    101 
    102 model.updateRecentCommits = function()
    103 {
    104     return trac.recentCommitData('trunk', kCommitLogLength).then(function(commitDataList) {
    105         model.state.recentCommits = commitDataList;
    106         updateCommitIndex();
    107         findAndMarkRevertedRevisions(model.state.recentCommits);
    108     });
    109 };
    110 
    111 function updateCommitIndex()
    112 {
    113     model.state.recentCommits.forEach(function(commitData) {
    114         g_commitIndex[commitData.revision] = commitData;
    115     });
    116 }
    117 
    118 model.commitDataListForRevisionRange = function(fromRevision, toRevision)
    119 {
    120     var result = [];
    121     for (var revision = fromRevision; revision <= toRevision; ++revision) {
    122         var commitData = g_commitIndex[revision];
    123         if (commitData)
    124             result.push(commitData);
    125     }
    126     return result;
    127 };
    128 
    129 model.buildersInFlightForRevision = function(revision)
    130 {
    131     var builders = {};
    132     Object.keys(model.state.resultsByBuilder).forEach(function(builderName) {
    133         var results = model.state.resultsByBuilder[builderName];
    134         if (parseInt(results.blink_revision) < revision)
    135             builders[builderName] = { actual: 'BUILDING' };
    136     });
    137     return builders;
    138 };
    139 
    140 model.latestRevision = function()
    141 {
    142     return model.state.recentCommits[0].revision;
    143 };
    144 
    145 model.latestRevisionWithNoBuildersInFlight = function()
    146 {
    147     var revision = 0;
    148     Object.keys(model.state.resultsByBuilder).forEach(function(builderName) {
    149         var results = model.state.resultsByBuilder[builderName];
    150         if (!results.blink_revision)
    151             return;
    152         var testedRevision = parseInt(results.blink_revision);
    153         revision = revision ? Math.min(revision, testedRevision) : testedRevision;
    154     });
    155     return revision;
    156 }
    157 
    158 model.latestRevisionByBuilder = function()
    159 {
    160     var revision = {};
    161     Object.keys(model.state.resultsByBuilder).forEach(function(builderName) {
    162         revision[builderName] = model.state.resultsByBuilder[builderName].blink_revision;
    163     });
    164     return revision;
    165 }
    166 
    167 model.updateResultsByBuilder = function()
    168 {
    169     return results.fetchResultsByBuilder(Object.keys(config.builders)).then(function(resultsByBuilder) {
    170         model.state.resultsByBuilder = resultsByBuilder;
    171     });
    172 };
    173 
    174 // failureCallback is called multiple times: once for each failure
    175 model.analyzeUnexpectedFailures = function(failureCallback)
    176 {
    177     var unexpectedFailures = results.unexpectedFailuresByTest(model.state.resultsByBuilder);
    178 
    179     $.each(model.state.failureAnalysisByTest, function(testName, failureAnalysis) {
    180         if (!(testName in unexpectedFailures))
    181             delete model.state.failureAnalysisByTest[testName];
    182     });
    183 
    184     var failurePromises = [];
    185     $.each(unexpectedFailures, function(testName, resultNodesByBuilder) {
    186         var builderNameList = Object.keys(resultNodesByBuilder);
    187         failurePromises.push(results.unifyRegressionRanges(builderNameList, testName).then(function(result) {
    188             var oldestFailingRevision = result[0];
    189             var newestPassingRevision = result[1];
    190             var failureAnalysis = {
    191                 'testName': testName,
    192                 'resultNodesByBuilder': resultNodesByBuilder,
    193                 'oldestFailingRevision': oldestFailingRevision,
    194                 'newestPassingRevision': newestPassingRevision,
    195             };
    196 
    197             heuristicallyNarrowRegressionRange(failureAnalysis);
    198 
    199             var previousFailureAnalysis = model.state.failureAnalysisByTest[testName];
    200             if (previousFailureAnalysis
    201                 && previousFailureAnalysis.oldestFailingRevision <= failureAnalysis.oldestFailingRevision
    202                 && previousFailureAnalysis.newestPassingRevision >= failureAnalysis.newestPassingRevision) {
    203                 failureAnalysis.oldestFailingRevision = previousFailureAnalysis.oldestFailingRevision;
    204                 failureAnalysis.newestPassingRevision = previousFailureAnalysis.newestPassingRevision;
    205             }
    206 
    207             model.state.failureAnalysisByTest[testName] = failureAnalysis;
    208 
    209             failureCallback(failureAnalysis, failurePromises.length);
    210         }));
    211     });
    212     return Promise.all(failurePromises);
    213 };
    214 
    215 model.unexpectedFailureInfoForTestName = function(testName)
    216 {
    217     var resultsByTest = results.unexpectedFailuresByTest(model.state.resultsByBuilder);
    218 
    219     return Object.keys(resultsByTest[testName]).map(function(builderName) {
    220         return results.failureInfoForTestAndBuilder(resultsByTest, testName, builderName);
    221     });
    222 };
    223 
    224 // failureCallback is called multiple times: once for each failure
    225 model.analyzeexpectedFailures = function(failureCallback)
    226 {
    227     var expectedFailures = results.expectedFailuresByTest(model.state.resultsByBuilder);
    228     $.each(expectedFailures, function(testName, resultNodesByBuilder) {
    229         var failureAnalysis = {
    230             'testName': testName,
    231             'resultNodesByBuilder': resultNodesByBuilder,
    232         };
    233 
    234         // FIXME: Consider looking at the history to see how long this test
    235         // has been failing.
    236 
    237         failureCallback(failureAnalysis);
    238     });
    239 };
    240 
    241 })();
    242