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