Upstream version 9.37.197.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Tools / GardeningServer / scripts / controllers.js
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         // FIXME: checkout.rebaseline() accepts only 3 arguments, we pass 5.
57         checkout.rebaseline(failuresToRebaseline, function(response) {
58             try {
59                 var json = JSON.parse(response);
60                 if (!json.result_code) {
61                     statusView.addFinalMessage(id, 'Rebaseline done! Please commit locally and land with "git cl dcommit".');
62                 } else {
63                     statusView.addMessage(id, 'Rebaseline failed (code=' + json.result_code + ')!');
64                     statusView.addFinalMessage(id, json.output);
65                 }
66             } catch (e) {
67                 statusView.addFinalMessage(id, 'Invalid response received: "' + response + '"');
68             }
69         }, function(failureInfo) {
70             statusView.addMessage(id, failureInfo.testName + ' on ' + ui.displayNameForBuilder(failureInfo.builderName));
71         }, function() {
72             statusView.addFinalMessage(id, kCheckoutUnavailableMessage);
73         }, function(failureInfo) {
74             statusView.addMessage(id, 'Skipping rebaseline for ' + failureInfo.testName + ' on ' + ui.displayNameForBuilder(failureInfo.builderName) + ' because we only rebaseline from release bots.');
75         });
76     } else {
77         statusView.addFinalMessage(id, 'No non-reftests left to rebaseline!')
78     }
79 }
80
81 // FIXME: This is duplicated from ui/results.js :(.
82 function isAnyReftest(testName, resultsByTest)
83 {
84     return Object.keys(resultsByTest[testName]).map(function(builder) {
85         return resultsByTest[testName][builder];
86     }).some(function(resultNode) {
87         return resultNode.reftest_type && resultNode.reftest_type.length;
88     });
89 }
90
91 // FIXME: Where should this function go?
92 function updateExpectationsWithStatusUpdates(failureInfoList)
93 {
94     var statusView = new ui.StatusArea('Expectations Update');
95     var id = statusView.newId();
96
97     var testNames = base.uniquifyArray(failureInfoList.map(function(failureInfo) { return failureInfo.testName; }));
98     var testName = testNames.length == 1 ? testNames[0] : testNames.length + ' tests';
99     statusView.addMessage(id, 'Updating expectations of ' + testName + '...');
100
101     checkout.updateExpectations(failureInfoList, function() {
102         statusView.addFinalMessage(id, 'Expectations update done! Please commit them locally and land with "git cl dcommit".');
103     }, function() {
104         statusView.addFinalMessage(id, kCheckoutUnavailableMessage);
105     });
106 }
107
108 controllers.ResultsDetails = base.extends(Object, {
109     init: function(view, resultsByTest)
110     {
111         this._view = view;
112         this._resultsByTest = resultsByTest;
113         this._view.setResultsByTest(resultsByTest);
114
115         this._view.firstResult();
116
117         $(this._view).bind('next', this.onNext.bind(this));
118         $(this._view).bind('previous', this.onPrevious.bind(this));
119         $(this._view).bind('rebaseline', this.onRebaseline.bind(this));
120         $(this._view).bind('expectfailure', this.onUpdateExpectations.bind(this));
121     },
122     onNext: function()
123     {
124         this._view.nextResult();
125     },
126     onPrevious: function()
127     {
128         this._view.previousResult();
129     },
130     _failureInfoList: function()
131     {
132         var testName = this._view.currentTestName();
133         return Object.keys(this._resultsByTest[testName]).map(function(builderName) {
134             return results.failureInfoForTestAndBuilder(this._resultsByTest, testName, builderName);
135         }.bind(this));
136     },
137     onRebaseline: function()
138     {
139         rebaselineWithStatusUpdates(this._failureInfoList(), this._resultsByTest);
140         this._view.nextTest();
141     },
142     onUpdateExpectations: function()
143     {
144         updateExpectationsWithStatusUpdates(this._failureInfoList());
145     }
146 });
147
148 controllers.ExpectedFailures = base.extends(Object, {
149     init: function(model, view, delegate)
150     {
151         this._model = model;
152         this._view = view;
153         this._delegate = delegate;
154     },
155     update: function()
156     {
157         var expectedFailures = results.expectedFailuresByTest(this._model.resultsByBuilder);
158         var failingTestsList = Object.keys(expectedFailures);
159
160         $(this._view).empty();
161         base.forEachDirectory(failingTestsList, function(label, testsFailingInDirectory) {
162             var listItem = new ui.failures.ListItem(label, testsFailingInDirectory);
163             this._view.appendChild(listItem);
164             $(listItem).bind('examine', function() {
165                 this.onExamine(testsFailingInDirectory);
166             }.bind(this));
167         }.bind(this));
168     },
169     onExamine: function(failingTestsList)
170     {
171         var resultsView = new ui.results.View({
172             fetchResultsURLs: results.fetchResultsURLs
173         });
174         var failuresByTest = base.filterDictionary(
175             results.expectedFailuresByTest(this._model.resultsByBuilder),
176             function(key) {
177                 return failingTestsList.indexOf(key) != -1;
178             });
179         var controller = new controllers.ResultsDetails(resultsView, failuresByTest);
180         this._delegate.showResults(resultsView);
181     }
182 });
183
184 var FailureStreamController = base.extends(Object, {
185     _resultsFilter: null,
186     _keyFor: function(failureAnalysis) { throw "Not implemented!"; },
187     _createFailureView: function(failureAnalysis) { throw "Not implemented!"; },
188
189     init: function(model, view, delegate)
190     {
191         this._model = model;
192         this._view = view;
193         this._delegate = delegate;
194         this._testFailures = new base.UpdateTracker();
195     },
196     update: function(failureAnalysis)
197     {
198         var key = this._keyFor(failureAnalysis);
199         var failure = this._testFailures.get(key);
200         if (!failure) {
201             failure = this._createFailureView(failureAnalysis);
202             this._view.add(failure);
203             $(failure).bind('examine', function() {
204                 this.onExamine(failure);
205             }.bind(this));
206             $(failure).bind('rebaseline', function() {
207                 this.onRebaseline(failure);
208             }.bind(this));
209             $(failure).bind('expectfailure', function() {
210                 this.onUpdateExpectations(failure);
211             }.bind(this));
212         }
213         failure.addFailureAnalysis(failureAnalysis);
214         this._testFailures.update(key, failure);
215         return failure;
216     },
217     purge: function() {
218         this._testFailures.purge(function(failure) {
219             failure.dismiss();
220         });
221         this._testFailures.forEach(function(failure) {
222             failure.purge();
223         });
224     },
225     onExamine: function(failures)
226     {
227         var resultsView = new ui.results.View({
228             fetchResultsURLs: results.fetchResultsURLs
229         });
230
231         var testNameList = failures.testNameList();
232         var failuresByTest = base.filterDictionary(
233             this._resultsFilter(this._model.resultsByBuilder),
234             function(key) {
235                 return testNameList.indexOf(key) != -1;
236             });
237
238         var controller = new controllers.ResultsDetails(resultsView, failuresByTest);
239         this._delegate.showResults(resultsView);
240     },
241     _toFailureInfoList: function(failures)
242     {
243         return base.flattenArray(failures.testNameList().map(model.unexpectedFailureInfoForTestName));
244     },
245     onRebaseline: function(failures)
246     {
247         var testNameList = failures.testNameList();
248         var failuresByTest = base.filterDictionary(
249             this._resultsFilter(this._model.resultsByBuilder),
250             function(key) {
251                 return testNameList.indexOf(key) != -1;
252             });
253
254         rebaselineWithStatusUpdates(this._toFailureInfoList(failures), failuresByTest);
255     },
256     onUpdateExpectations: function(failures)
257     {
258         updateExpectationsWithStatusUpdates(this._toFailureInfoList(failures));
259     }
260 });
261
262 controllers.UnexpectedFailures = base.extends(FailureStreamController, {
263     _resultsFilter: results.unexpectedFailuresByTest,
264
265     _impliedFirstFailingRevision: function(failureAnalysis)
266     {
267         return failureAnalysis.newestPassingRevision + 1;
268     },
269     _keyFor: function(failureAnalysis)
270     {
271         return failureAnalysis.newestPassingRevision + "+" + failureAnalysis.oldestFailingRevision;
272     },
273     _createFailureView: function(failureAnalysis)
274     {
275         var failure = new ui.notifications.FailingTestsSummary();
276         model.commitDataListForRevisionRange(this._impliedFirstFailingRevision(failureAnalysis), failureAnalysis.oldestFailingRevision).forEach(function(commitData) {
277             var suspiciousCommit = failure.addCommitData(commitData);
278             $(suspiciousCommit).bind('rollout', function() {
279                 this.onRollout(commitData.revision, failure.testNameList());
280             }.bind(this));
281             $(failure).bind('blame', function() {
282                 this.onBlame(failure, commitData);
283             }.bind(this));
284         }, this);
285
286         return failure;
287     },
288     update: function(failureAnalysis)
289     {
290         var failure = FailureStreamController.prototype.update.call(this, failureAnalysis);
291         failure.updateBuilderResults(model.buildersInFlightForRevision(this._impliedFirstFailingRevision(failureAnalysis)));
292     },
293     length: function()
294     {
295         return this._testFailures.length();
296     },
297     onBlame: function(failure, commitData)
298     {
299         failure.pinToCommitData(commitData);
300         $('.action', failure).each(function() {
301             // FIXME: This isn't the right way of finding and disabling this action.
302             if (this.textContent == 'Blame')
303                 this.disabled = true;
304         });
305     },
306     onRollout: function(revision, testNameList)
307     {
308         checkout.rollout(revision, ui.rolloutReasonForTestNameList(testNameList)).then($.noop, function() {
309             // FIXME: We should have a better error UI.
310             alert(kCheckoutUnavailableMessage);
311         });
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._message);
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 })();