Crash-only queues on bot watcher's dashboard should not have non-crashing tests in...
[WebKit-https.git] / Tools / BuildSlaveSupport / build.webkit.org-config / public_html / dashboard / Scripts / BuildbotTesterQueueView.js
1 /*
2  * Copyright (C) 2013, 2014 Apple 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 BuildbotTesterQueueView = function(queues)
27 {
28     BuildbotQueueView.call(this, queues);
29
30     this.releaseQueues = this.queues.filter(function(queue) { return queue.debug === false; });
31     this.debugQueues = this.queues.filter(function(queue) { return queue.debug === true; });
32
33     this.update();
34 };
35
36 BaseObject.addConstructorFunctions(BuildbotTesterQueueView);
37
38 BuildbotTesterQueueView.prototype = {
39     constructor: BuildbotTesterQueueView,
40     __proto__: BuildbotQueueView.prototype,
41
42     update: function()
43     {
44         BuildbotQueueView.prototype.update.call(this);
45
46         this.element.removeChildren();
47
48         function appendBuilderQueueStatus(queue)
49         {
50             if (queue.buildbot.needsAuthentication && !queue.buildbot.isAuthenticated) {
51                 this._appendUnauthorizedLineView(queue);
52                 return;
53             }
54
55             this._appendPendingRevisionCount(queue);
56
57             var appendedStatus = false;
58
59             var limit = 2;
60             for (var i = 0; i < queue.iterations.length && limit > 0; ++i) {
61                 var iteration = queue.iterations[i];
62                 if (!iteration.loaded || !iteration.finished)
63                     continue;
64
65                 --limit;
66
67                 var willHaveAnotherStatusLine = i + 1 < queue.iterations.length && limit > 0 && !iteration.successful; // This is not 100% correct, as the remaining iterations may not be finished or loaded yet, but close enough.
68                 var messageElement = this.revisionContentForIteration(iteration, (iteration.productive && willHaveAnotherStatusLine) ? iteration.previousProductiveIteration : null);
69
70                 if (iteration.successful) {
71                     var url = iteration.queue.buildbot.buildPageURLForIteration(iteration);
72                     var status = new StatusLineView(messageElement, StatusLineView.Status.Good, "all tests passed", undefined, url);
73                     limit = 0;
74                 } else if (!iteration.productive) {
75                     var url = iteration.queue.buildbot.buildPageURLForIteration(iteration);
76                     var status = new StatusLineView(messageElement, StatusLineView.Status.Danger, iteration.text, undefined, url);
77                 } else if (iteration.failedTestSteps.length === 0) {
78                     // Something wrong happened, but it was not a test failure.
79                     var url = iteration.queue.buildbot.buildPageURLForIteration(iteration);
80                     var status = new StatusLineView(messageElement, StatusLineView.Status.Danger, iteration.text, undefined, url);
81                 } else if (queue.crashesOnly) {
82                     // A crashes-only queue is a queue where we are only interested in crashes, e.g. a GuardMalloc or an ASan one.
83                     // Currently, only layout tests are supported in such.
84                     var layoutTestResults = iteration.layoutTestResults;
85                     if (!layoutTestResults) {
86                         // Tests did not run.
87                         var url = iteration.queue.buildbot.buildPageURLForIteration(iteration);
88                         var status = new StatusLineView(messageElement, StatusLineView.Status.Danger, iteration.text, undefined, url);
89                     } else if (layoutTestResults.tooManyFailures) {
90                         var status = new StatusLineView(messageElement, StatusLineView.Status.Bad, "failure limit exceeded", undefined, iteration.queue.buildbot.layoutTestResultsURLForIteration(iteration));
91                         new PopoverTracker(status.statusBubbleElement, this._presentPopoverForLayoutTestRegressions.bind(this), iteration);
92                     } else if (layoutTestResults.errorOccurred) {
93                         var url = iteration.queue.buildbot.buildPageURLForIteration(iteration);
94                         var status = new StatusLineView(messageElement, StatusLineView.Status.Danger, iteration.text, undefined, url);
95                     } else if (!layoutTestResults.crashCount) {
96                         var url = iteration.queue.buildbot.buildPageURLForIteration(iteration);
97                         var status = new StatusLineView(messageElement, StatusLineView.Status.Good, "no crashes found", undefined, url);
98                         limit = 0;
99                     } else {
100                         var status = new StatusLineView(messageElement, StatusLineView.Status.Bad, layoutTestResults.crashCount === 1 ? "crash found" : "crashes found", layoutTestResults.crashCount, iteration.queue.buildbot.layoutTestResultsURLForIteration(iteration));
101                         new PopoverTracker(status.statusBubbleElement, this._presentPopoverForLayoutTestRegressions.bind(this), iteration);
102                     }
103                 } else if (iteration.failedTestSteps.length === 1) {
104                     var failedStep = iteration.failedTestSteps[0];
105                     if (failedStep.name === "layout-test") {
106                         var status = new StatusLineView(messageElement, StatusLineView.Status.Bad, this._testStepFailureDescription(failedStep), failedStep.tooManyFailures ? failedStep.failureCount + "\uff0b" : failedStep.failureCount, iteration.queue.buildbot.layoutTestResultsURLForIteration(iteration));
107                         new PopoverTracker(status.statusBubbleElement, this._presentPopoverForLayoutTestRegressions.bind(this), iteration);
108                     } else {
109                         var status = new StatusLineView(messageElement, StatusLineView.Status.Bad, this._testStepFailureDescription(failedStep), failedStep.failureCount ? failedStep.failureCount : undefined, failedStep.URL);
110                         new PopoverTracker(status.statusBubbleElement, this._presentPopoverForGenericTestFailures.bind(this), iteration);
111                     }
112                 } else {
113                     var url = iteration.queue.buildbot.buildPageURLForIteration(iteration);
114                     var failureDescriptions = iteration.failedTestSteps.map(function(failedStep) { return this._testStepFailureDescriptionWithCount(failedStep) }, this);
115                     var status = new StatusLineView(messageElement, StatusLineView.Status.Bad, failureDescriptions.join(", "), undefined, url);
116                     new PopoverTracker(status.statusBubbleElement, this._presentPopoverForGenericTestFailures.bind(this), iteration);
117                 }
118
119                 this.element.appendChild(status.element);
120                 appendedStatus = true;
121             }
122
123             if (!appendedStatus) {
124                 var status = new StatusLineView("unknown", StatusLineView.Status.Neutral, "last passing build");
125                 this.element.appendChild(status.element);
126             }
127         }
128
129         this.appendBuildStyle.call(this, this.releaseQueues, "Release", appendBuilderQueueStatus);
130         this.appendBuildStyle.call(this, this.debugQueues, "Debug", appendBuilderQueueStatus);
131     },
132
133     _testStepFailureDescription: function(failedStep)
134     {
135         if (!failedStep.failureCount)
136             return BuildbotIteration.TestSteps[failedStep.name] + " failed";
137         if (failedStep.failureCount === 1)
138             return BuildbotIteration.TestSteps[failedStep.name] + " failure";
139         return BuildbotIteration.TestSteps[failedStep.name] + " failures";
140     },
141
142     _testStepFailureDescriptionWithCount: function(failedStep)
143     {
144         if (!failedStep.failureCount)
145             return this._testStepFailureDescription(failedStep);
146         if (failedStep.tooManyFailures) {
147             // E.g. "50+ layout test failures", preventing line breaks around the "+".
148             return failedStep.failureCount + "\ufeff\uff0b\u00a0" + this._testStepFailureDescription(failedStep);
149         }
150         // E.g. "1 layout test failure", preventing line break after the number.
151         return failedStep.failureCount + "\u00a0" + this._testStepFailureDescription(failedStep);
152     },
153
154     _popoverContentForLayoutTestRegressions: function(iteration)
155     {
156         var hasTestHistory = typeof testHistory !== "undefined";
157
158         var content = document.createElement("div");
159         content.className = "test-results-popover";
160
161         this._addIterationHeadingToPopover(iteration, content, "layout test failures");
162         this._addDividerToPopover(content);
163
164         if (!iteration.layoutTestResults.regressions) {
165             var message = document.createElement("div");
166             message.className = "loading-failure";
167             message.textContent = "Test results couldn\u2019t be loaded";
168             content.appendChild(message);
169             return content;
170         }
171
172         function addFailureInfoLink(rowElement, className, text, url)
173         {
174             var linkElement = document.createElement("a");
175             linkElement.className = className;
176             linkElement.textContent = text;
177             linkElement.href = url;
178             linkElement.target = "_blank";
179             rowElement.appendChild(linkElement);
180         }
181
182         function addFailureInfoText(rowElement, className, text)
183         {
184             var spanElement = document.createElement("span");
185             spanElement.className = className;
186             spanElement.textContent = text;
187             rowElement.appendChild(spanElement);
188         }
189
190         var sortedRegressions = iteration.layoutTestResults.regressions.slice().sort(function(a, b) { return (a.path === b.path) ? 0 : (a.path > b.path) ? 1 : -1; });
191
192         for (var i = 0, end = sortedRegressions.length; i != end; ++i) {
193             var test = sortedRegressions[i];
194
195             if (iteration.queue.crashesOnly && !test.crash && !iteration.layoutTestResults.tooManyFailures)
196                 continue;
197
198             var rowElement = document.createElement("div");
199
200             var testPathElement = document.createElement("span");
201             testPathElement.className = "test-path";
202             testPathElement.textContent = test.path;
203             rowElement.appendChild(testPathElement);
204
205             if (test.crash)
206                 addFailureInfoLink(rowElement, "failure-kind-indicator", "crash", iteration.queue.buildbot.layoutTestCrashLogURLForIteration(iteration, test.path));
207
208             if (test.timeout)
209                 addFailureInfoText(rowElement, "failure-kind-indicator", "timeout");
210
211             if (test.has_diff) {
212                 addFailureInfoLink(rowElement, "additional-link", "diff", iteration.queue.buildbot.layoutTestDiffURLForIteration(iteration, test.path));
213
214                 if (iteration.hasPrettyPatch)
215                     addFailureInfoLink(rowElement, "additional-link", "pretty\xa0diff", iteration.queue.buildbot.layoutTestPrettyDiffURLForIteration(iteration, test.path));
216             }
217
218             if (test.has_image_diff) {
219                 addFailureInfoLink(rowElement, "additional-link", "images", iteration.queue.buildbot.layoutTestImagesURLForIteration(iteration, test.path));
220                 addFailureInfoLink(rowElement, "additional-link", "image\xa0diff", iteration.queue.buildbot.layoutTestImageDiffURLForIteration(iteration, test.path));
221             }
222
223             if (test.has_stderr)
224                 addFailureInfoLink(rowElement, "additional-link", "stderr", iteration.queue.buildbot.layoutTestStderrURLForIteration(iteration, test.path));
225
226             if (hasTestHistory)
227                 addFailureInfoLink(rowElement, "test-history-link", "history", testHistory.historyPageURLForTest(test.path));
228
229             content.appendChild(rowElement);
230         }
231
232         // Work around bug 80159: -webkit-user-select:none not respected when copying content.
233         // We set clipboard data manually, temporarily making non-selectable content hidden
234         // to easily get accurate selection text.
235         content.oncopy = function(event) {
236             var iterator = document.createNodeIterator(
237                 event.currentTarget,
238                 NodeFilter.SHOW_ELEMENT,
239                 {
240                     acceptNode: function(element) {
241                         if (window.getComputedStyle(element).webkitUserSelect !== "none")
242                             return NodeFilter.FILTER_ACCEPT;
243                         return NodeFilter.FILTER_SKIP;
244                     }
245                 }
246             );
247
248             while ((node = iterator.nextNode()))
249                 node.style.visibility = "visible";
250
251             event.currentTarget.style.visibility = "hidden";
252             event.clipboardData.setData('text', window.getSelection());
253             event.currentTarget.style.visibility = "";
254             return false;
255         }
256
257         return content;
258     },
259
260     _presentPopoverForLayoutTestRegressions: function(element, popover, iteration)
261     {
262         if (iteration.layoutTestResults.regressions)
263             var content = this._popoverContentForLayoutTestRegressions(iteration);
264         else {
265             var content = document.createElement("div");
266             content.className = "test-results-popover";
267
268             this._addIterationHeadingToPopover(iteration, content, "layout test failures");
269             this._addDividerToPopover(content);
270
271             var loadingIndicator = document.createElement("div");
272             loadingIndicator.className = "loading-indicator";
273             loadingIndicator.textContent = "Loading\u2026";
274             content.appendChild(loadingIndicator);
275
276             iteration.loadLayoutTestResults(function() {
277                 popover.content = this._popoverContentForLayoutTestRegressions(iteration);
278             }.bind(this));
279         }
280         var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
281         popover.content = content;
282         popover.present(rect, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
283         return true;
284     },
285
286     _presentPopoverForGenericTestFailures: function(element, popover, iteration)
287     {
288         function addResultKind(message, url) {
289             var line = document.createElement("a");
290             line.className = "failing-test-kind-summary";
291             line.href = url;
292             line.textContent = message;
293             line.target = "_blank";
294             content.appendChild(line);            
295         }
296
297         var content = document.createElement("div");
298         content.className = "test-results-popover";
299
300         this._addIterationHeadingToPopover(iteration, content);
301         this._addDividerToPopover(content);
302
303         iteration.failedTestSteps.forEach(function(failedStep) {
304             if (failedStep.name === "layout-test")
305                 addResultKind(this._testStepFailureDescriptionWithCount(failedStep), iteration.queue.buildbot.layoutTestResultsURLForIteration(iteration));
306             else
307                 addResultKind(this._testStepFailureDescriptionWithCount(failedStep), failedStep.URL);
308         }, this);
309
310         var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
311         popover.content = content;
312         popover.present(rect, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
313         return true;
314     }
315 };