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 |