Update Bot Watcher's Dashboard for Buildbot 0.9
[WebKit-https.git] / Tools / BuildSlaveSupport / build.webkit.org-config / public_html / dashboard / Scripts / BuildbotTestResults.js
1 /*
2  * Copyright (C) 2013, 2016 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 BuildbotTestResults = function(testStep)
27 {
28     BaseObject.call(this);
29
30     this._parseResults(testStep);
31 };
32
33 BaseObject.addConstructorFunctions(BuildbotTestResults);
34
35 BuildbotTestResults.prototype = {
36     constructor: BuildbotTestResults,
37     __proto__: BaseObject.prototype,
38
39     _parseResults: function(testStep)
40     {
41         this.name = testStep.name;
42         try {
43             this.URL = testStep.logs[0][1];
44         } catch (ex) {
45         }
46
47         this.allPassed = false;
48         this.errorOccurred = false;
49         this.tooManyFailures = false;
50
51         this.failureCount = 0;
52         this.flakeyCount = 0;
53         this.totalLeakCount = 0;
54         this.uniqueLeakCount = 0;
55         this.newPassesCount = 0;
56         this.missingCount = 0;
57         this.crashCount = 0;
58
59         if (!testStep.complete) {
60             // The step never even ran, or hasn't finished running.
61             this.finished = false;
62             return;
63         }
64
65         this.finished = true;
66
67         if (!testStep.results || testStep.results === BuildbotIteration.SUCCESS || testStep.results === BuildbotIteration.WARNINGS) {
68             // All tests passed.
69             this.allPassed = true;
70             return;
71         }
72
73         if (/Exiting early/.test(testStep.state_string))
74             this.tooManyFailures = true;
75
76         function resultSummarizer(matchString, sum, outputLine)
77         {
78             var match = /^(\d+)\s/.exec(outputLine);
79             if (!match) {
80                 var regex = new RegExp("(\\d+)\\s" + matchString);
81                 match = regex.exec(outputLine);
82             }
83             if (!match)
84                 return sum;
85             if (!outputLine.contains(matchString))
86                 return sum;
87             if (!sum || sum === -1)
88                 sum = 0;
89             return sum + parseInt(match[1], 10);
90         }
91
92         this.failureCount = resultSummarizer('fail', null, testStep.state_string);
93         this.flakeyCount = resultSummarizer("flake", null, testStep.state_string);
94         this.totalLeakCount = resultSummarizer("total leak", null, testStep.state_string);
95         this.uniqueLeakCount = resultSummarizer("unique leak", null, testStep.state_string);
96         this.newPassesCount = resultSummarizer("new pass", null, testStep.state_string);
97         this.missingCount = resultSummarizer("missing", null, testStep.state_string);
98         this.crashCount = resultSummarizer("crash", null, testStep.state_string);
99         this.issueCount = resultSummarizer("issue", null, testStep.state_string);
100
101         if (!this.failureCount && !this.flakyCount && !this.totalLeakCount && !this.uniqueLeakCount && !this.newPassesCount && !this.missingCount) {
102             // This step exited with a non-zero exit status, but we didn't find any output about the number of failed tests.
103             // Something must have gone wrong (e.g., timed out and was killed by buildbot).
104             this.errorOccurred = true;
105         }
106     },
107
108     addFullLayoutTestResults: function(data)
109     {
110         console.assert(this.name === "layout-test");
111
112         function collectResults(subtree, predicate)
113         {
114             // Results object is a trie:
115             // directory
116             //   subdirectory
117             //     test1.html
118             //       expected:"PASS"
119             //       actual: "IMAGE"
120             //       report: "REGRESSION"
121             //     test2.html
122             //       expected:"FAIL"
123             //       actual:"TEXT"
124
125             var result = [];
126             for (var key in subtree) {
127                 var value = subtree[key];
128                 console.assert(typeof value === "object");
129                 var isIndividualTest = value.hasOwnProperty("actual") && value.hasOwnProperty("expected");
130                 if (isIndividualTest) {
131                     // Possible values for actual and expected keys: PASS, FAIL, AUDIO, IMAGE, TEXT, IMAGE+TEXT, TIMEOUT, CRASH, MISSING.
132                     // Both actual and expected can be space separated lists. Actual contains two values when retrying a failed test
133                     // gives a different result (retrying may be disabled in tester configuration).
134                     // Possible values for report key (when present): REGRESSION, MISSING, FLAKY.
135
136                     if (predicate(value)) {
137                         var item = {path: key};
138
139                         // FIXME (bug 127186): Crash log URL will be incorrect if crash only happened on retry (e.g. "TEXT CRASH").
140                         // It should point to retries subdirectory, but the information about which attempt failed gets lost here.
141                         if (value.actual.contains("CRASH"))
142                             item.crash = true;
143                         if (value.actual.contains("TIMEOUT"))
144                             item.timeout = true;
145
146                         // FIXME (bug 127186): Similarly, we don't have a good way to present results for something like "TIMEOUT TEXT",
147                         // not even UI wise. For now, only show a diff link if the first attempt has the diff.
148                         if (value.actual.split(" ")[0].contains("TEXT"))
149                             item.has_diff = true;
150
151                         // FIXME (bug 127186): It is particularly unfortunate for image diffs, because we currently only check image results
152                         // on retry (except for reftests), so many times, you will see images on buildbot page, but not on the dashboard.
153                         // FIXME: Find a way to display expected mismatch reftest failures.
154                         if (value.actual.split(" ")[0].contains("IMAGE") && value.reftest_type != "!=")
155                             item.has_image_diff = true;
156
157                         if (value.has_stderr)
158                             item.has_stderr = true;
159
160                         result.push(item);
161                     }
162
163                 } else {
164                     var nestedTests = collectResults(value, predicate);
165                     for (var i = 0, end = nestedTests.length; i < end; ++i)
166                         nestedTests[i].path = key + "/" + nestedTests[i].path;
167                     result = result.concat(nestedTests);
168                 }
169             }
170
171             return result;
172         }
173
174         this.hasPrettyPatch = data.has_pretty_patch;
175
176         this.regressions = collectResults(data.tests, function(info) { return info["report"] === "REGRESSION" });
177         console.assert(data.num_regressions === this.regressions.length);
178
179         this.flakyTests = collectResults(data.tests, function(info) { return info["report"] === "FLAKY" });
180         console.assert(data.num_flaky === this.flakyTests.length);
181
182         this.testsWithMissingResults = collectResults(data.tests, function(info) { return info["report"] === "MISSING" });
183         // data.num_missing is not always equal to the size of testsWithMissingResults array,
184         // because buildbot counts regressions that had missing pixel results on retry (e.g. "TEXT MISSING").
185         console.assert(data.num_missing >= this.testsWithMissingResults.length);
186     },
187
188     addJavaScriptCoreTestFailures: function(data)
189     {
190         this.regressions = data.stressTestFailures;
191     },
192 };