Merge inbound to m-c.
[gecko.git] / dom / imptests / testharnessreport.js
blob5e5c348ce882fa85c52a54a0c51db9ea9a4073bf
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 var W3CTest = {
6   /**
7    * Dictionary mapping a test URL to either the string "all", which means that
8    * all tests in this file are expected to fail, or a dictionary mapping test
9    * names to either the boolean |true|, or the string "debug". The former
10    * means that this test is expected to fail in all builds, and the latter
11    * that it is only expected to fail in debug builds.
12    */
13   "expectedFailures": {},
15   /**
16    * If set to true, we will dump the test failures to the console.
17    */
18   "dumpFailures": false,
20   /**
21    * If dumpFailures is true, this holds a structure like necessary for
22    * expectedFailures, for ease of updating the expectations.
23    */
24   "failures": {},
26   /**
27    * List of test results, needed by TestRunner to update the UI.
28    */
29   "tests": [],
31   /**
32    * Number of unlogged passes, to stop buildbot from truncating the log.
33    * We will print a message every MAX_COLLAPSED_MESSAGES passes.
34    */
35   "collapsedMessages": 0,
36   "MAX_COLLAPSED_MESSAGES": 100,
38   /**
39    * Reference to the TestRunner object in the parent frame.
40    */
41   "runner": parent === this ? null : parent.TestRunner || parent.wrappedJSObject.TestRunner,
43   /**
44    * Prefixes for the error logging. Indexed first by int(todo) and second by
45    * int(result).
46    */
47   "prefixes": [
48     ["TEST-UNEXPECTED-FAIL", "TEST-PASS"],
49     ["TEST-KNOWN-FAIL", "TEST-UNEXPECTED-PASS"]
50   ],
52   /**
53    * Prefix of the path to parent of the the failures directory.
54    */
55   "pathprefix": "/tests/dom/imptests/",
57   /**
58    * Returns the URL of the current test, relative to the root W3C tests
59    * directory. Used as a key into the expectedFailures dictionary.
60    */
61   "getPath": function() {
62     var url = this.getURL();
63     if (!url.startsWith(this.pathprefix)) {
64       return "";
65     }
66     return url.substring(this.pathprefix.length);
67   },
69   /**
70    * Returns the root-relative URL of the current test.
71    */
72   "getURL": function() {
73     return this.runner ? this.runner.currentTestURL : location.pathname;
74   },
76   /**
77    * Report the results in the tests array.
78    */
79   "reportResults": function() {
80     var element = function element(aLocalName) {
81       var xhtmlNS = "http://www.w3.org/1999/xhtml";
82       return document.createElementNS(xhtmlNS, aLocalName);
83     };
85     var stylesheet = element("link");
86     stylesheet.setAttribute("rel", "stylesheet");
87     stylesheet.setAttribute("href", "/resources/testharness.css");
88     var heads = document.getElementsByTagName("head");
89     if (heads.length) {
90       heads[0].appendChild(stylesheet);
91     }
93     var log = document.getElementById("log");
94     if (!log) {
95       return;
96     }
97     var section = log.appendChild(element("section"));
98     section.id = "summary";
99     section.appendChild(element("h2")).textContent = "Details";
101     var table = section.appendChild(element("table"));
102     table.id = "results";
104     var tr = table.appendChild(element("thead")).appendChild(element("tr"));
105     for (var header of ["Result", "Test Name", "Message"]) {
106       tr.appendChild(element("th")).textContent = header;
107     }
108     var statuses = [
109       ["Unexpected Fail", "Pass"],
110       ["Known Fail", "Unexpected Pass"]
111     ];
112     var tbody = table.appendChild(element("tbody"));
113     for (var test of this.tests) {
114       tr = tbody.appendChild(element("tr"));
115       tr.className = (test.result === !test.todo ? "pass" : "fail");
116       tr.appendChild(element("td")).textContent =
117         statuses[+test.todo][+test.result];
118       tr.appendChild(element("td")).textContent = test.name;
119       tr.appendChild(element("td")).textContent = test.message;
120     }
121   },
123   /**
124    * Returns a printable message based on aTest's 'name' and 'message'
125    * properties.
126    */
127   "formatTestMessage": function(aTest) {
128     return aTest.name + (aTest.message ? ": " + aTest.message : "");
129   },
131   /**
132    * Lets the test runner know about a test result.
133    */
134   "_log": function(test) {
135     var url = this.getURL();
136     var msg = this.prefixes[+test.todo][+test.result] + " | ";
137     if (url) {
138       msg += url;
139     }
140     msg += " | " + this.formatTestMessage(test);
141     if (this.runner) {
142       this.runner[(test.result === !test.todo) ? "log" : "error"](msg);
143     } else {
144       dump(msg + "\n");
145     }
146   },
148   /**
149    * Logs a message about collapsed messages (if any), and resets the counter.
150    */
151   "_logCollapsedMessages": function() {
152     if (this.collapsedMessages) {
153       this._log({
154         "name": document.title,
155         "result": true,
156         "todo": false,
157         "message": "Elided " + this.collapsedMessages + " passes or known failures."
158       });
159     }
160     this.collapsedMessages = 0;
161   },
163   /**
164    * Maybe logs a result, eliding up to MAX_COLLAPSED_MESSAGES consecutive
165    * passes.
166    */
167   "_maybeLog": function(test) {
168     var success = (test.result === !test.todo);
169     if (success && ++this.collapsedMessages < this.MAX_COLLAPSED_MESSAGES) {
170       return;
171     }
172     this._logCollapsedMessages();
173     this._log(test);
174   },
176   /**
177    * Reports a test result. The argument is an object with the following
178    * properties:
179    *
180    * o message (string): message to be reported
181    * o result (boolean): whether this test failed
182    * o todo (boolean): whether this test is expected to fail
183    */
184   "report": function(test) {
185     this.tests.push(test);
186     this._maybeLog(test);
187   },
189   /**
190    * Returns true if this test is expected to fail, and false otherwise.
191    */
192   "_todo": function(test) {
193     if (this.expectedFailures === "all") {
194       return true;
195     }
196     var value = this.expectedFailures[test.name];
197     return value === true || (value === "debug" && !!SpecialPowers.isDebugBuild);
198   },
200   /**
201    * Callback function for testharness.js. Called when one test in a file
202    * finishes.
203    */
204   "result": function(test) {
205     var url = this.getPath();
206     this.report({
207       "name": test.name,
208       "message": test.message || "",
209       "result": test.status === test.PASS,
210       "todo": this._todo(test)
211     });
212     if (this.dumpFailures && test.status !== test.PASS) {
213       this.failures[test.name] = true;
214     }
215   },
217   /**
218    * Callback function for testharness.js. Called when the entire test file
219    * finishes.
220    */
221   "finish": function(tests, status) {
222     var url = this.getPath();
223     this.report({
224       "name": "Finished test",
225       "message": "Status: " + status.status,
226       "result": status.status === status.OK,
227       "todo":
228         url in this.expectedFailures &&
229         this.expectedFailures[url] === "error"
230     });
232     this._logCollapsedMessages();
234     if (this.dumpFailures) {
235       dump("@@@ @@@ Failures\n");
236       dump(url + "@@@" + JSON.stringify(this.failures) + "\n");
237     }
238     if (this.runner) {
239       this.runner.testFinished(this.tests.map(function(aTest) {
240         return {
241           "message": this.formatTestMessage(aTest),
242           "result": aTest.result,
243           "todo": aTest.todo
244         };
245       }, this));
246     } else {
247       this.reportResults();
248     }
249   },
251   /**
252    * Log an unexpected failure. Intended to be used from harness code, not
253    * from tests.
254    */
255   "logFailure": function(aTestName, aMessage) {
256     this.report({
257       "name": aTestName,
258       "message": aMessage,
259       "result": false,
260       "todo": false
261     });
262   },
264   /**
265    * Timeout the current test. Intended to be used from harness code, not
266    * from tests.
267    */
268   "timeout": function() {
269     this.logFailure("Timeout", "Test runner timed us out.");
270     timeout();
271   }
273 (function() {
274   try {
275     var path = W3CTest.getPath();
276     if (path) {
277       // Get expected fails.  If there aren't any, there will be a 404, which is
278       // fine.  Anything else is unexpected.
279       var url = W3CTest.pathprefix + "failures/" + path + ".json";
280       var request = new XMLHttpRequest();
281       request.open("GET", url, false);
282       request.send();
283       if (request.status === 200) {
284         W3CTest.expectedFailures = JSON.parse(request.responseText);
285       } else if (request.status !== 404) {
286         W3CTest.logFailure("Fetching failures file", "Request status was " + request.status);
287       }
288     }
290     add_result_callback(W3CTest.result.bind(W3CTest));
291     add_completion_callback(W3CTest.finish.bind(W3CTest));
292     setup({
293       "output": false,
294       "explicit_timeout": true
295     });
296   } catch (e) {
297     W3CTest.logFailure("Harness setup", "Unexpected exception: " + e);
298   }
299 })();