build.webkit.org/dashboard: Don't list test steps in BuildbotIteration twice
[WebKit-https.git] / Tools / BuildSlaveSupport / build.webkit.org-config / public_html / dashboard / Scripts / BuildbotIteration.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 BuildbotIteration = function(queue, dataOrID, finished)
27 {
28     BaseObject.call(this);
29
30     console.assert(queue);
31
32     this.queue = queue;
33
34     if (typeof dataOrID === "object") {
35         this._parseData(dataOrID);
36         return;
37     }
38
39     console.assert(typeof dataOrID === "number");
40     this.id = dataOrID;
41
42     this.loaded = false;
43     this.isLoading = false;
44     this._productive = false;
45
46     this.openSourceRevision = null;
47     this.internalRevision = null;
48
49     this.layoutTestResults = null; // Layout test results can be needed even if all tests passed, e.g. for the leaks bot.
50
51     this.failedTestSteps = [];
52
53     this._finished = finished;
54 };
55
56 BaseObject.addConstructorFunctions(BuildbotIteration);
57
58 // JSON result values for both individual steps and the whole iteration.
59 BuildbotIteration.SUCCESS = 0;
60 BuildbotIteration.WARNINGS = 1;
61 BuildbotIteration.FAILURE = 2;
62 BuildbotIteration.SKIPPED = 3;
63 BuildbotIteration.EXCEPTION = 4;
64 BuildbotIteration.RETRY = 5;
65
66 // If none of these steps ran, then we didn't get any real results, and the iteration was not productive.
67 // All test steps are considered productive too.
68 BuildbotIteration.ProductiveSteps = {
69     "Build" : 1,
70     "build ASan archive": 1,
71     "compile-webkit": 1,
72     "scan build": 1,
73 };
74
75 // These have a special meaning for test queue views.
76 BuildbotIteration.TestSteps = {
77     "API tests": "platform api test",
78     "bindings-generation-tests": "bindings tests",
79     "jscore-test": "javascript test",
80     "layout-test": "layout test",
81     "perf-test": "performance test",
82     "run-api-tests": "api test",
83     "webkit-32bit-jsc-test": "javascript test",
84     "webkit-jsc-cloop-test": "javascript cloop test",
85     "webkitperl-test": "webkitperl test",
86     "webkitpy-test": "webkitpy test",
87 };
88
89 BuildbotIteration.Event = {
90     Updated: "updated",
91     UnauthorizedAccess: "unauthorized-access"
92 };
93
94 // See <http://docs.buildbot.net/0.8.8/manual/cfg-properties.html>.
95 function isMultiCodebaseGotRevisionProperty(property)
96 {
97     return property[0] === "got_revision" && typeof property[1] === "object";
98 }
99
100 function parseRevisionProperty(property, key, fallbackKey)
101 {
102     if (!property)
103         return null;
104     var value = property[1];
105     if (isMultiCodebaseGotRevisionProperty(property))
106         value = (key in value) ? value[key] : value[fallbackKey];
107     return parseInt(value);
108 }
109
110 BuildbotIteration.prototype = {
111     constructor: BuildbotIteration,
112     __proto__: BaseObject.prototype,
113
114     get finished()
115     {
116         return this._finished;
117     },
118
119     set finished(x)
120     {
121         this._finished = x;
122     },
123
124     get successful()
125     {
126         return this._result === BuildbotIteration.SUCCESS;
127     },
128
129     get productive()
130     {
131         return this._productive;
132     },
133
134     // It is not a real failure if Buildbot itself failed with codes like EXCEPTION or RETRY.
135     get failed()
136     {
137         return this._result === BuildbotIteration.FAILURE;
138     },
139
140     get firstFailedStepName()
141     {
142         if (!this._firstFailedStep)
143             return undefined;
144         return this._firstFailedStep.name;
145     },
146
147     failureLogURL: function(kind)
148     {
149         if (!this.failed)
150             return undefined;
151
152         console.assert(this._firstFailedStep);
153
154         for (var i = 0; i < this._firstFailedStep.logs.length; ++i) {
155             if (this._firstFailedStep.logs[i][0] == kind)
156                 return this._firstFailedStep.logs[i][1];
157         }
158
159         return undefined;
160     },
161
162     get failureLogs()
163     {
164         if (!this.failed)
165             return undefined;
166
167         console.assert(this._firstFailedStep);
168         return this._firstFailedStep.logs;
169     },
170
171     get previousProductiveIteration()
172     {
173         for (var i = 0; i < this.queue.iterations.length - 1; ++i) {
174             if (this.queue.iterations[i] === this) {
175                 while (++i < this.queue.iterations.length) {
176                     var iteration = this.queue.iterations[i];
177                     if (iteration.productive)
178                         return iteration;
179                 }
180                 break;
181             }
182         }
183         return null;
184     },
185
186     _parseData: function(data)
187     {
188         console.assert(!this.id || this.id === data.number);
189         this.id = data.number;
190
191         // The property got_revision may have the following forms:
192         //
193         // ["got_revision",{"Internal":"1357","WebKitOpenSource":"2468"},"Source"]
194         // OR
195         // ["got_revision","2468","Source"]
196         //
197         // When extracting the OpenSource revision from property got_revision we don't need to check whether the
198         // value of got_revision is a dictionary (represents multiple codebases) or a string literal because we
199         // assume that got_revision contains the OpenSource revision. However, it may not have the Internal
200         // revision. Therefore, we only look at got_revision to extract the Internal revision when it's
201         // a dictionary.
202
203         var openSourceRevisionProperty = data.properties.findFirst(function(property) { return property[0] === "got_revision"; });
204         this.openSourceRevision = parseRevisionProperty(openSourceRevisionProperty, "WebKit", "opensource");
205
206         var internalRevisionProperty = data.properties.findFirst(function(property) { return isMultiCodebaseGotRevisionProperty(property); });
207         this.internalRevision = parseRevisionProperty(internalRevisionProperty, "Internal", "internal");
208
209         function sourceStampChanges(sourceStamp) {
210             var result = [];
211             var changes = sourceStamp.changes;
212             for (var i = 0; i < changes.length; ++i) {
213                 var change = { revisionNumber: parseInt(changes[i].revision, 10) }
214                 if (changes[i].repository)
215                     change.repository = changes[i].repository;
216                 if (changes[i].branch)
217                     change.branch = changes[i].branch;
218                 // There is also a timestamp, but it's not accurate.
219                 result.push(change);
220             }
221             return result;
222         }
223
224         // The changes array is generally meaningful for svn triggered queues (such as builders),
225         // but not for internally triggered ones (such as testers), due to coalescing.
226         this.changes = [];
227         if (data.sourceStamp)
228             this.changes = sourceStampChanges(data.sourceStamp);
229         else for (var i = 0; i < data.sourceStamps.length; ++i)
230             this.changes = this.changes.concat(sourceStampChanges(data.sourceStamps[i]));
231
232         this.startTime = new Date(data.times[0] * 1000);
233         this.endTime = new Date(data.times[1] * 1000);
234
235         this.failedTestSteps = [];
236         data.steps.forEach(function(step) {
237             if (!step.isFinished || !(step.name in BuildbotIteration.TestSteps))
238                 return;
239             var results = new BuildbotTestResults(step);
240             if (step.name === "layout-test")
241                 this.layoutTestResults = results;
242             if (results.allPassed)
243                 return;
244             this.failedTestSteps.push(results);
245         }, this);
246
247         var masterShellCommandStep = data.steps.findFirst(function(step) { return step.name === "MasterShellCommand"; });
248         this.resultURLs = masterShellCommandStep ? masterShellCommandStep.urls : null;
249         for (var linkName in this.resultURLs) {
250             var url = this.resultURLs[linkName];
251             if (!url.startsWith("http"))
252                 this.resultURLs[linkName] = this.queue.buildbot.baseURL + url;
253         }
254
255         this.loaded = true;
256
257         this._firstFailedStep = data.steps.findFirst(function(step) { return step.results[0] === BuildbotIteration.FAILURE; });
258
259         console.assert(data.results === null || typeof data.results === "number");
260         this._result = data.results;
261
262         this.text = data.text.join(" ");
263
264         if (!data.currentStep)
265             this.finished = true;
266
267         this._productive = this._finished && this._result !== BuildbotIteration.EXCEPTION && this._result !== BuildbotIteration.RETRY;
268         if (this._productive) {
269             var finishedAnyProductiveStep = false;
270             for (var i = 0; i < data.steps.length; ++i) {
271                 var step = data.steps[i];
272                 if (!step.isFinished)
273                     break;
274                 if (step.name in BuildbotIteration.ProductiveSteps || step.name in BuildbotIteration.TestSteps) {
275                     finishedAnyProductiveStep = true;
276                     break;
277                 }
278             }
279             this._productive = finishedAnyProductiveStep;
280         }
281     },
282
283     _updateWithData: function(data)
284     {
285         if (this.loaded && this._finished)
286             return;
287
288         this._parseData(data);
289
290         // Update the sorting since it is based on the revision numbers that just became known.
291         this.queue.sortIterations();
292
293         this.dispatchEventToListeners(BuildbotIteration.Event.Updated);
294     },
295
296     update: function()
297     {
298         if (this.loaded && this._finished)
299             return;
300
301         if (this.queue.buildbot.needsAuthentication && this.queue.buildbot.authenticationStatus === Buildbot.AuthenticationStatus.InvalidCredentials)
302             return;
303
304         if (this.isLoading)
305             return;
306
307         this.isLoading = true;
308
309         JSON.load(this.queue.baseURL + "/builds/" + this.id, function(data) {
310             this.isLoading = false;
311             this.queue.buildbot.isAuthenticated = true;
312             if (!data || !data.properties)
313                 return;
314
315             this._updateWithData(data);
316         }.bind(this),
317         function(data) {
318             this.isLoading = false;
319             if (data.errorType === JSON.LoadError && data.errorHTTPCode === 401) {
320                 this.queue.buildbot.isAuthenticated = false;
321                 this.dispatchEventToListeners(BuildbotIteration.Event.UnauthorizedAccess);
322             }
323         }.bind(this), {withCredentials: this.queue.buildbot.needsAuthentication});
324     },
325
326     loadLayoutTestResults: function(callback)
327     {
328         if (this.queue.buildbot.needsAuthentication && this.queue.buildbot.authenticationStatus === Buildbot.AuthenticationStatus.InvalidCredentials)
329             return;
330
331         JSON.load(this.queue.buildbot.layoutTestFullResultsURLForIteration(this), function(data) {
332             this.queue.buildbot.isAuthenticated = true;
333
334             this.layoutTestResults.addFullLayoutTestResults(data);
335             callback();
336         }.bind(this),
337         function(data) {
338             if (data.errorType === JSON.LoadError && data.errorHTTPCode === 401) {
339                 this.queue.buildbot.isAuthenticated = false;
340                 this.dispatchEventToListeners(BuildbotIteration.Event.UnauthorizedAccess);
341             }
342             console.log(data.error);
343             callback();
344         }.bind(this), {jsonpCallbackName: "ADD_RESULTS", withCredentials: this.queue.buildbot.needsAuthentication});
345     }
346 };