d9a11264d7bacd9bd2d965230f26ed9bac35392d
[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.tooManyFailures) {
86                         var status = new StatusLineView(messageElement, StatusLineView.Status.Bad, "failure limit exceeded", undefined, iteration.queue.buildbot.layoutTestResultsURLForIteration(iteration));
87                         new PopoverTracker(status.statusBubbleElement, this._presentPopoverForLayoutTestRegressions.bind(this), iteration);
88                     } else if (layoutTestResults.errorOccurred) {
89                         var url = iteration.queue.buildbot.buildPageURLForIteration(iteration);
90                         var status = new StatusLineView(messageElement, StatusLineView.Status.Danger, iteration.text, undefined, url);
91                     } else if (!layoutTestResults.crashCount) {
92                         var url = iteration.queue.buildbot.buildPageURLForIteration(iteration);
93                         var status = new StatusLineView(messageElement, StatusLineView.Status.Good, "no crashes found", undefined, url);
94                         limit = 0;
95                     } else {
96                         var status = new StatusLineView(messageElement, StatusLineView.Status.Bad, layoutTestResults.crashCount === 1 ? "crash found" : "crashes found", layoutTestResults.crashCount, iteration.queue.buildbot.layoutTestResultsURLForIteration(iteration));
97                         new PopoverTracker(status.statusBubbleElement, this._presentPopoverForLayoutTestRegressions.bind(this), iteration);
98                     }
99                 } else if (iteration.failedTestSteps.length === 1) {
100                     var failedStep = iteration.failedTestSteps[0];
101                     if (failedStep.name === "layout-test") {
102                         var status = new StatusLineView(messageElement, StatusLineView.Status.Bad, this._testStepFailureDescription(failedStep), failedStep.tooManyFailures ? failedStep.failureCount + "\uff0b" : failedStep.failureCount, iteration.queue.buildbot.layoutTestResultsURLForIteration(iteration));
103                         new PopoverTracker(status.statusBubbleElement, this._presentPopoverForLayoutTestRegressions.bind(this), iteration);
104                     } else {
105                         var status = new StatusLineView(messageElement, StatusLineView.Status.Bad, this._testStepFailureDescription(failedStep), failedStep.failureCount ? failedStep.failureCount : undefined, failedStep.URL);
106                         new PopoverTracker(status.statusBubbleElement, this._presentPopoverForGenericTestFailures.bind(this), iteration);
107                     }
108                 } else {
109                     var url = iteration.queue.buildbot.buildPageURLForIteration(iteration);
110                     var failureDescriptions = iteration.failedTestSteps.map(function(failedStep) { return this._testStepFailureDescriptionWithCount(failedStep) }, this);
111                     var status = new StatusLineView(messageElement, StatusLineView.Status.Bad, failureDescriptions.join(", "), undefined, url);
112                     new PopoverTracker(status.statusBubbleElement, this._presentPopoverForGenericTestFailures.bind(this), iteration);
113                 }
114
115                 this.element.appendChild(status.element);
116                 appendedStatus = true;
117             }
118
119             if (!appendedStatus) {
120                 var status = new StatusLineView("unknown", StatusLineView.Status.Neutral, "last passing build");
121                 this.element.appendChild(status.element);
122             }
123         }
124
125         this.appendBuildStyle.call(this, this.releaseQueues, "Release", appendBuilderQueueStatus);
126         this.appendBuildStyle.call(this, this.debugQueues, "Debug", appendBuilderQueueStatus);
127     },
128
129     _testStepFailureDescription: function(failedStep)
130     {
131         if (!failedStep.failureCount)
132             return BuildbotIteration.TestSteps[failedStep.name] + " failed";
133         if (failedStep.failureCount === 1)
134             return BuildbotIteration.TestSteps[failedStep.name] + " failure";
135         return BuildbotIteration.TestSteps[failedStep.name] + " failures";
136     },
137
138     _testStepFailureDescriptionWithCount: function(failedStep)
139     {
140         if (!failedStep.failureCount)
141             return this._testStepFailureDescription(failedStep);
142         if (failedStep.tooManyFailures) {
143             // E.g. "50+ layout test failures", preventing line breaks around the "+".
144             return failedStep.failureCount + "\ufeff\uff0b\u00a0" + this._testStepFailureDescription(failedStep);
145         }
146         // E.g. "1 layout test failure", preventing line break after the number.
147         return failedStep.failureCount + "\u00a0" + this._testStepFailureDescription(failedStep);
148     },
149
150     _popoverContentForLayoutTestRegressions: function(iteration)
151     {
152         var hasTestHistory = typeof testHistory !== "undefined";
153
154         var content = document.createElement("div");
155         content.className = "test-results-popover";
156
157         this._addIterationHeadingToPopover(iteration, content, "layout test failures");
158         this._addDividerToPopover(content);
159
160         if (!iteration.layoutTestResults.regressions) {
161             var message = document.createElement("div");
162             message.className = "loading-failure";
163             message.textContent = "Test results couldn\u2019t be loaded";
164             content.appendChild(message);
165             return content;
166         }
167
168         function addFailureInfoLink(rowElement, className, text, url)
169         {
170             var linkElement = document.createElement("a");
171             linkElement.className = className;
172             linkElement.textContent = text;
173             linkElement.href = url;
174             linkElement.target = "_blank";
175             rowElement.appendChild(linkElement);
176         }
177
178         function addFailureInfoText(rowElement, className, text)
179         {
180             var spanElement = document.createElement("span");
181             spanElement.className = className;
182             spanElement.textContent = text;
183             rowElement.appendChild(spanElement);
184         }
185
186         var sortedRegressions = iteration.layoutTestResults.regressions.slice().sort(function(a, b) { return (a.path === b.path) ? 0 : (a.path > b.path) ? 1 : -1; });
187
188         for (var i = 0, end = sortedRegressions.length; i != end; ++i) {
189             var test = sortedRegressions[i];
190
191             var rowElement = document.createElement("div");
192
193             var testPathElement = document.createElement("span");
194             testPathElement.className = "test-path";
195             testPathElement.textContent = test.path;
196             rowElement.appendChild(testPathElement);
197
198             if (test.crash)
199                 addFailureInfoLink(rowElement, "failure-kind-indicator", "crash", iteration.queue.buildbot.layoutTestCrashLogURLForIteration(iteration, test.path));
200
201             if (test.timeout)
202                 addFailureInfoText(rowElement, "failure-kind-indicator", "timeout");
203
204             if (test.has_diff) {
205                 addFailureInfoLink(rowElement, "additional-link", "diff", iteration.queue.buildbot.layoutTestDiffURLForIteration(iteration, test.path));
206
207                 if (iteration.hasPrettyPatch)
208                     addFailureInfoLink(rowElement, "additional-link", "pretty\xa0diff", iteration.queue.buildbot.layoutTestPrettyDiffURLForIteration(iteration, test.path));
209             }
210
211             if (test.has_image_diff) {
212                 addFailureInfoLink(rowElement, "additional-link", "images", iteration.queue.buildbot.layoutTestImagesURLForIteration(iteration, test.path));
213                 addFailureInfoLink(rowElement, "additional-link", "image\xa0diff", iteration.queue.buildbot.layoutTestImageDiffURLForIteration(iteration, test.path));
214             }
215
216             if (test.has_stderr)
217                 addFailureInfoLink(rowElement, "additional-link", "stderr", iteration.queue.buildbot.layoutTestStderrURLForIteration(iteration, test.path));
218
219             if (hasTestHistory)
220                 addFailureInfoLink(rowElement, "test-history-link", "history", testHistory.historyPageURLForTest(test.path));
221
222             content.appendChild(rowElement);
223         }
224
225         // Work around bug 80159: -webkit-user-select:none not respected when copying content.
226         // We set clipboard data manually, temporarily making non-selectable content hidden
227         // to easily get accurate selection text.
228         content.oncopy = function(event) {
229             var iterator = document.createNodeIterator(
230                 event.currentTarget,
231                 NodeFilter.SHOW_ELEMENT,
232                 {
233                     acceptNode: function(element) {
234                         if (window.getComputedStyle(element).webkitUserSelect !== "none")
235                             return NodeFilter.FILTER_ACCEPT;
236                         return NodeFilter.FILTER_SKIP;
237                     }
238                 }
239             );
240
241             while ((node = iterator.nextNode()))
242                 node.style.visibility = "visible";
243
244             event.currentTarget.style.visibility = "hidden";
245             event.clipboardData.setData('text', window.getSelection());
246             event.currentTarget.style.visibility = "";
247             return false;
248         }
249
250         return content;
251     },
252
253     _presentPopoverForLayoutTestRegressions: function(element, popover, iteration)
254     {
255         if (iteration.layoutTestResults.regressions)
256             var content = this._popoverContentForLayoutTestRegressions(iteration);
257         else {
258             var content = document.createElement("div");
259             content.className = "test-results-popover";
260
261             this._addIterationHeadingToPopover(iteration, content, "layout test failures");
262             this._addDividerToPopover(content);
263
264             var loadingIndicator = document.createElement("div");
265             loadingIndicator.className = "loading-indicator";
266             loadingIndicator.textContent = "Loading\u2026";
267             content.appendChild(loadingIndicator);
268
269             iteration.loadLayoutTestResults(function() {
270                 popover.content = this._popoverContentForLayoutTestRegressions(iteration);
271             }.bind(this));
272         }
273         var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
274         popover.content = content;
275         popover.present(rect, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
276         return true;
277     },
278
279     _presentPopoverForGenericTestFailures: function(element, popover, iteration)
280     {
281         function addResultKind(message, url) {
282             var line = document.createElement("a");
283             line.className = "failing-test-kind-summary";
284             line.href = url;
285             line.textContent = message;
286             line.target = "_blank";
287             content.appendChild(line);            
288         }
289
290         var content = document.createElement("div");
291         content.className = "test-results-popover";
292
293         this._addIterationHeadingToPopover(iteration, content);
294         this._addDividerToPopover(content);
295
296         iteration.failedTestSteps.forEach(function(failedStep) {
297             if (failedStep.name === "layout-test")
298                 addResultKind(this._testStepFailureDescriptionWithCount(failedStep), iteration.queue.buildbot.layoutTestResultsURLForIteration(iteration));
299             else
300                 addResultKind(this._testStepFailureDescriptionWithCount(failedStep), failedStep.URL);
301         }, this);
302
303         var rect = Dashboard.Rect.rectFromClientRect(element.getBoundingClientRect());
304         popover.content = content;
305         popover.present(rect, [Dashboard.RectEdge.MIN_Y, Dashboard.RectEdge.MAX_Y, Dashboard.RectEdge.MAX_X, Dashboard.RectEdge.MIN_X]);
306         return true;
307     }
308 };