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 controllers = controllers || {}; 27 28 (function(){ 29 30 var kCheckoutUnavailableMessage = 'Failed! Garden-o-matic needs a local server to modify your working copy. Please run "webkit-patch garden-o-matic" start the local server.'; 31 32 // FIXME: Where should this function go? 33 function rebaselineWithStatusUpdates(failureInfoList, resultsByTest) 34 { 35 var statusView = new ui.StatusArea('Rebaseline'); 36 var id = statusView.newId(); 37 38 var failuresToRebaseline = []; 39 var testNamesLogged = []; 40 failureInfoList.forEach(function(failureInfo) { 41 if (isAnyReftest(failureInfo.testName, resultsByTest)) { 42 if (testNamesLogged.indexOf(failureInfo.testName) == -1) { 43 statusView.addMessage(id, failureInfo.testName + ' is a ref test, skipping'); 44 testNamesLogged.push(failureInfo.testName); 45 } 46 } else { 47 failuresToRebaseline.push(failureInfo); 48 if (testNamesLogged.indexOf(failureInfo.testName) == -1) { 49 statusView.addMessage(id, 'Rebaselining ' + failureInfo.testName + '...'); 50 testNamesLogged.push(failureInfo.testName); 51 } 52 } 53 }); 54 55 if (failuresToRebaseline.length) { 56 checkout.rebaseline(failuresToRebaseline, function() { 57 statusView.addFinalMessage(id, 'Rebaseline done! Please land with "webkit-patch land-cowhand".'); 58 }, function(failureInfo) { 59 statusView.addMessage(id, failureInfo.testName + ' on ' + ui.displayNameForBuilder(failureInfo.builderName)); 60 }, function() { 61 statusView.addFinalMessage(id, kCheckoutUnavailableMessage); 62 }, function(failureInfo) { 63 statusView.addMessage(id, 'Skipping rebaseline for ' + failureInfo.testName + ' on ' + ui.displayNameForBuilder(failureInfo.builderName) + ' because we only rebaseline from release bots.'); 64 }); 65 } else { 66 statusView.addFinalMessage(id, 'No non-reftests left to rebaseline!') 67 } 68 } 69 70 // FIXME: This is duplicated from ui/results.js :(. 71 function isAnyReftest(testName, resultsByTest) 72 { 73 return Object.keys(resultsByTest[testName]).map(function(builder) { 74 return resultsByTest[testName][builder]; 75 }).some(function(resultNode) { 76 return resultNode.reftest_type && resultNode.reftest_type.length; 77 }); 78 } 79 80 // FIXME: Where should this function go? 81 function updateExpectationsWithStatusUpdates(failureInfoList) 82 { 83 var statusView = new ui.StatusArea('Expectations Update'); 84 var id = statusView.newId(); 85 86 var testNames = base.uniquifyArray(failureInfoList.map(function(failureInfo) { return failureInfo.testName; })); 87 var testName = testNames.length == 1 ? testNames[0] : testNames.length + ' tests'; 88 statusView.addMessage(id, 'Updating expectations of ' + testName + '...'); 89 90 checkout.updateExpectations(failureInfoList, function() { 91 statusView.addFinalMessage(id, 'Expectations update done! Please land with "webkit-patch land-cowhand".'); 92 }, function() { 93 statusView.addFinalMessage(id, kCheckoutUnavailableMessage); 94 }); 95 } 96 97 controllers.ResultsDetails = base.extends(Object, { 98 init: function(view, resultsByTest) 99 { 100 this._view = view; 101 this._resultsByTest = resultsByTest; 102 this._view.setResultsByTest(resultsByTest); 103 104 this._view.firstResult(); 105 106 $(this._view).bind('next', this.onNext.bind(this)); 107 $(this._view).bind('previous', this.onPrevious.bind(this)); 108 $(this._view).bind('rebaseline', this.onRebaseline.bind(this)); 109 $(this._view).bind('expectfailure', this.onUpdateExpectations.bind(this)); 110 }, 111 onNext: function() 112 { 113 this._view.nextResult(); 114 }, 115 onPrevious: function() 116 { 117 this._view.previousResult(); 118 }, 119 _failureInfoList: function() 120 { 121 var testName = this._view.currentTestName(); 122 return Object.keys(this._resultsByTest[testName]).map(function(builderName) { 123 return results.failureInfoForTestAndBuilder(this._resultsByTest, testName, builderName); 124 }.bind(this)); 125 }, 126 onRebaseline: function() 127 { 128 rebaselineWithStatusUpdates(this._failureInfoList(), this._resultsByTest); 129 this._view.nextTest(); 130 }, 131 onUpdateExpectations: function() 132 { 133 updateExpectationsWithStatusUpdates(this._failureInfoList()); 134 } 135 }); 136 137 controllers.ExpectedFailures = base.extends(Object, { 138 init: function(model, view, delegate) 139 { 140 this._model = model; 141 this._view = view; 142 this._delegate = delegate; 143 }, 144 update: function() 145 { 146 var expectedFailures = results.expectedFailuresByTest(this._model.resultsByBuilder); 147 var failingTestsList = Object.keys(expectedFailures); 148 149 $(this._view).empty(); 150 base.forEachDirectory(failingTestsList, function(label, testsFailingInDirectory) { 151 var listItem = new ui.failures.ListItem(label, testsFailingInDirectory); 152 this._view.appendChild(listItem); 153 $(listItem).bind('examine', function() { 154 this.onExamine(testsFailingInDirectory); 155 }.bind(this)); 156 }.bind(this)); 157 }, 158 onExamine: function(failingTestsList) 159 { 160 var resultsView = new ui.results.View({ 161 fetchResultsURLs: results.fetchResultsURLs 162 }); 163 var failuresByTest = base.filterDictionary( 164 results.expectedFailuresByTest(this._model.resultsByBuilder), 165 function(key) { 166 return failingTestsList.indexOf(key) != -1; 167 }); 168 var controller = new controllers.ResultsDetails(resultsView, failuresByTest); 169 this._delegate.showResults(resultsView); 170 } 171 }); 172 173 var FailureStreamController = base.extends(Object, { 174 _resultsFilter: null, 175 _keyFor: function(failureAnalysis) { throw "Not implemented!"; }, 176 _createFailureView: function(failureAnalysis) { throw "Not implemented!"; }, 177 178 init: function(model, view, delegate) 179 { 180 this._model = model; 181 this._view = view; 182 this._delegate = delegate; 183 this._testFailures = new base.UpdateTracker(); 184 }, 185 update: function(failureAnalysis) 186 { 187 var key = this._keyFor(failureAnalysis); 188 var failure = this._testFailures.get(key); 189 if (!failure) { 190 failure = this._createFailureView(failureAnalysis); 191 this._view.add(failure); 192 $(failure).bind('examine', function() { 193 this.onExamine(failure); 194 }.bind(this)); 195 $(failure).bind('rebaseline', function() { 196 this.onRebaseline(failure); 197 }.bind(this)); 198 $(failure).bind('expectfailure', function() { 199 this.onUpdateExpectations(failure); 200 }.bind(this)); 201 } 202 failure.addFailureAnalysis(failureAnalysis); 203 this._testFailures.update(key, failure); 204 return failure; 205 }, 206 purge: function() { 207 this._testFailures.purge(function(failure) { 208 failure.dismiss(); 209 }); 210 this._testFailures.forEach(function(failure) { 211 failure.purge(); 212 }); 213 }, 214 onExamine: function(failures) 215 { 216 var resultsView = new ui.results.View({ 217 fetchResultsURLs: results.fetchResultsURLs 218 }); 219 220 var testNameList = failures.testNameList(); 221 var failuresByTest = base.filterDictionary( 222 this._resultsFilter(this._model.resultsByBuilder), 223 function(key) { 224 return testNameList.indexOf(key) != -1; 225 }); 226 227 var controller = new controllers.ResultsDetails(resultsView, failuresByTest); 228 this._delegate.showResults(resultsView); 229 }, 230 _toFailureInfoList: function(failures) 231 { 232 return base.flattenArray(failures.testNameList().map(model.unexpectedFailureInfoForTestName)); 233 }, 234 onRebaseline: function(failures) 235 { 236 var testNameList = failures.testNameList(); 237 var failuresByTest = base.filterDictionary( 238 this._resultsFilter(this._model.resultsByBuilder), 239 function(key) { 240 return testNameList.indexOf(key) != -1; 241 }); 242 243 rebaselineWithStatusUpdates(this._toFailureInfoList(failures), failuresByTest); 244 }, 245 onUpdateExpectations: function(failures) 246 { 247 updateExpectationsWithStatusUpdates(this._toFailureInfoList(failures)); 248 } 249 }); 250 251 controllers.UnexpectedFailures = base.extends(FailureStreamController, { 252 _resultsFilter: results.unexpectedFailuresByTest, 253 254 _impliedFirstFailingRevision: function(failureAnalysis) 255 { 256 return failureAnalysis.newestPassingRevision + 1; 257 }, 258 _keyFor: function(failureAnalysis) 259 { 260 return failureAnalysis.newestPassingRevision + "+" + failureAnalysis.oldestFailingRevision; 261 }, 262 _createFailureView: function(failureAnalysis) 263 { 264 var failure = new ui.notifications.FailingTestsSummary(); 265 model.commitDataListForRevisionRange(this._impliedFirstFailingRevision(failureAnalysis), failureAnalysis.oldestFailingRevision).forEach(function(commitData) { 266 var suspiciousCommit = failure.addCommitData(commitData); 267 $(suspiciousCommit).bind('rollout', function() { 268 this.onRollout(commitData.revision, failure.testNameList()); 269 }.bind(this)); 270 $(failure).bind('blame', function() { 271 this.onBlame(failure, commitData); 272 }.bind(this)); 273 }, this); 274 275 return failure; 276 }, 277 update: function(failureAnalysis) 278 { 279 var failure = FailureStreamController.prototype.update.call(this, failureAnalysis); 280 failure.updateBuilderResults(model.buildersInFlightForRevision(this._impliedFirstFailingRevision(failureAnalysis))); 281 }, 282 length: function() 283 { 284 return this._testFailures.length(); 285 }, 286 onBlame: function(failure, commitData) 287 { 288 failure.pinToCommitData(commitData); 289 $('.action', failure).each(function() { 290 // FIXME: This isn't the right way of finding and disabling this action. 291 if (this.textContent == 'Blame') 292 this.disabled = true; 293 }); 294 }, 295 onRollout: function(revision, testNameList) 296 { 297 checkout.rollout(revision, ui.rolloutReasonForTestNameList(testNameList), $.noop, function() { 298 // FIXME: We should have a better error UI. 299 alert(kCheckoutUnavailableMessage); 300 }); 301 } 302 }); 303 304 controllers.Failures = base.extends(FailureStreamController, { 305 _resultsFilter: results.expectedFailuresByTest, 306 307 _keyFor: function(failureAnalysis) 308 { 309 return base.dirName(failureAnalysis.testName); 310 }, 311 _createFailureView: function(failureAnalysis) 312 { 313 return new ui.notifications.FailingTests(); 314 }, 315 }); 316 317 controllers.FailingBuilders = base.extends(Object, { 318 init: function(view, message) 319 { 320 this._view = view; 321 this._message = message; 322 this._notification = null; 323 }, 324 hasFailures: function() 325 { 326 return !!this._notification; 327 }, 328 update: function(failuresList) 329 { 330 if (Object.keys(failuresList).length == 0) { 331 if (this._notification) { 332 this._notification.dismiss(); 333 this._notification = null; 334 } 335 return; 336 } 337 if (!this._notification) { 338 this._notification = new ui.notifications.BuildersFailing(this._message); 339 this._view.add(this._notification); 340 } 341 // FIXME: We should provide regression ranges for the failing builders. 342 // This doesn't seem to happen often enough to worry too much about that, however. 343 this._notification.setFailingBuilders(failuresList); 344 } 345 }); 346 347 })(); 348