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