Bug 1850713: remove duplicated setting of early hint preloader id in `ScriptLoader...
[gecko.git] / dom / imptests / testharnessreport.js
blob59f8dc6ab23e453dff7e1e2ecc10b8971266fb0c
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 if the
40    * test and the parent frame are same-origin. Otherwise, return
41    * a stub object that relays method calls to the parent frame's
42    * TestRunner via postMessage calls.
43    */
44   "runner": function() {
46     /**
47      * If true, these tests are running in cross-origin iframes
48      */
49     var xOrigin = function(){
50         try {
51             void parent.TestRunner;
52             return false;
53         } catch {
54             return true;
55         }
56     }();
57     if (!xOrigin) {
58       return parent === this ? null : parent.TestRunner || parent.wrappedJSObject.TestRunner;
59     }
60     let documentURL = new URL(document.URL);
61     return {
62       currentTestURL: function() {
63         return documentURL.searchParams.get("currentTestURL");
64       }(),
65       testFinished(tests) {
66         parent.postMessage(
67           {
68             harnessType: "SimpleTest",
69             command: "testFinished",
70             applyOn: "runner",
71             params: [tests],
72           },
73           "*"
74         );
75       },
76       getParameterInfo() {
77         return { closeWhenDone: documentURL.searchParams.get("closeWhenDone") };
78       },
79       structuredLogger: {
80         testStatus(url, subtest, status, expected, diagnostic, stack) {
81           parent.postMessage(
82             {
83               harnessType: "SimpleTest",
84               command: "structuredLogger.testStatus",
85               applyOn: "logger",
86               params: [url, subtest, status, expected, diagnostic, stack],
87             },
88             "*"
89           );
90         },
91       },
92       expectAssertions(min, max) {
93         parent.postMessage(
94           {
95             harnessType: "SimpleTest",
96             command: "expectAssertions",
97             applyOn: "runner",
98             params: [min, max],
99           },
100           "*"
101         );
102       },
103     };
104    }(),
107   /**
108    * Prefixes for the error logging. Indexed first by int(todo) and second by
109    * int(result). Also contains the test's status, and expected status.
110    */
111   "prefixes": [
112     [
113       {status: 'FAIL', expected: 'PASS', message: "TEST-UNEXPECTED-FAIL"},
114       {status: 'PASS', expected: 'PASS', message: "TEST-PASS"}
115     ],
116     [
117       {status: 'FAIL', expected: 'FAIL', message: "TEST-KNOWN-FAIL"},
118       {status: 'PASS', expected: 'FAIL', message: "TEST-UNEXPECTED-PASS"}
119     ]
120   ],
122   /**
123    * Prefix of the path to parent of the the failures directory.
124    */
125   "pathprefix": "/tests/dom/imptests/",
127   /**
128    * Returns the URL of the current test, relative to the root W3C tests
129    * directory. Used as a key into the expectedFailures dictionary.
130    */
131   "getPath": function() {
132     var url = this.getURL();
133     if (!url.startsWith(this.pathprefix)) {
134       return "";
135     }
136     return url.substring(this.pathprefix.length);
137   },
139   /**
140    * Returns the root-relative URL of the current test.
141    */
142   "getURL": function() {
143     return this.runner ? this.runner.currentTestURL : location.pathname;
144   },
146   /**
147    * Report the results in the tests array.
148    */
149   "reportResults": function() {
150     var element = function element(aLocalName) {
151       var xhtmlNS = "http://www.w3.org/1999/xhtml";
152       return document.createElementNS(xhtmlNS, aLocalName);
153     };
155     var stylesheet = element("link");
156     stylesheet.setAttribute("rel", "stylesheet");
157     stylesheet.setAttribute("href", "/resources/testharness.css");
158     var heads = document.getElementsByTagName("head");
159     if (heads.length) {
160       heads[0].appendChild(stylesheet);
161     }
163     var log = document.getElementById("log");
164     if (!log) {
165       return;
166     }
167     var section = log.appendChild(element("section"));
168     section.id = "summary";
169     section.appendChild(element("h2")).textContent = "Details";
171     var table = section.appendChild(element("table"));
172     table.id = "results";
174     var tr = table.appendChild(element("thead")).appendChild(element("tr"));
175     for (var header of ["Result", "Test Name", "Message"]) {
176       tr.appendChild(element("th")).textContent = header;
177     }
178     var statuses = [
179       ["Unexpected Fail", "Pass"],
180       ["Known Fail", "Unexpected Pass"]
181     ];
182     var tbody = table.appendChild(element("tbody"));
183     for (var test of this.tests) {
184       tr = tbody.appendChild(element("tr"));
185       tr.className = (test.result === !test.todo ? "pass" : "fail");
186       tr.appendChild(element("td")).textContent =
187         statuses[+test.todo][+test.result];
188       tr.appendChild(element("td")).textContent = test.name;
189       tr.appendChild(element("td")).textContent = test.message;
190     }
191   },
193   /**
194    * Returns a printable message based on aTest's 'name' and 'message'
195    * properties.
196    */
197   "formatTestMessage": function(aTest) {
198     return aTest.name + (aTest.message ? ": " + aTest.message : "");
199   },
201   /**
202    * Lets the test runner know about a test result.
203    */
204   "_log": function(test) {
205     var url = this.getURL();
206     var message = this.formatTestMessage(test);
207     var result = this.prefixes[+test.todo][+test.result];
209     if (this.runner) {
210       this.runner.structuredLogger.testStatus(url,
211                                               test.name,
212                                               result.status,
213                                               result.expected,
214                                               message);
215     } else {
216       var msg = result.message + " | ";
217       if (url) {
218         msg += url;
219       }
220       msg += " | " + this.formatTestMessage(test);
221       dump(msg + "\n");
222     }
223   },
225   /**
226    * Logs a message about collapsed messages (if any), and resets the counter.
227    */
228   "_logCollapsedMessages": function() {
229     if (this.collapsedMessages) {
230       this._log({
231         "name": document.title,
232         "result": true,
233         "todo": false,
234         "message": "Elided " + this.collapsedMessages + " passes or known failures."
235       });
236     }
237     this.collapsedMessages = 0;
238   },
240   /**
241    * Maybe logs a result, eliding up to MAX_COLLAPSED_MESSAGES consecutive
242    * passes.
243    */
244   "_maybeLog": function(test) {
245     var success = (test.result === !test.todo);
246     if (success && ++this.collapsedMessages < this.MAX_COLLAPSED_MESSAGES) {
247       return;
248     }
249     this._logCollapsedMessages();
250     this._log(test);
251   },
253   /**
254    * Reports a test result. The argument is an object with the following
255    * properties:
256    *
257    * o message (string): message to be reported
258    * o result (boolean): whether this test failed
259    * o todo (boolean): whether this test is expected to fail
260    */
261   "report": function(test) {
262     this.tests.push(test);
263     this._maybeLog(test);
264   },
266   /**
267    * Returns true if this test is expected to fail, and false otherwise.
268    */
269   "_todo": function(test) {
270     if (this.expectedFailures === "all") {
271       return true;
272     }
273     var value = this.expectedFailures[test.name];
274     return value === true || (value === "debug" && !!SpecialPowers.isDebugBuild);
275   },
277   /**
278    * Callback function for testharness.js. Called when one test in a file
279    * finishes.
280    */
281   "result": function(test) {
282     var url = this.getPath();
283     this.report({
284       "name": test.name,
285       "message": test.message || "",
286       "result": test.status === test.PASS,
287       "todo": this._todo(test)
288     });
289     if (this.dumpFailures && test.status !== test.PASS) {
290       this.failures[test.name] = true;
291     }
292   },
294   /**
295    * Callback function for testharness.js. Called when the entire test file
296    * finishes.
297    */
298   "finish": function(tests, status) {
299     var url = this.getPath();
300     this.report({
301       "name": "Finished test",
302       "message": "Status: " + status.status,
303       "result": status.status === status.OK,
304       "todo":
305         url in this.expectedFailures &&
306         this.expectedFailures[url] === "error"
307     });
309     this._logCollapsedMessages();
311     if (this.dumpFailures) {
312       dump("@@@ @@@ Failures\n");
313       dump(url + "@@@" + JSON.stringify(this.failures) + "\n");
314     }
315     if (this.runner) {
316       this.runner.testFinished(this.tests.map(function(aTest) {
317         return {
318           "message": this.formatTestMessage(aTest),
319           "result": aTest.result,
320           "todo": aTest.todo
321         };
322       }, this));
323     } else {
324       this.reportResults();
325     }
326   },
328   /**
329    * Log an unexpected failure. Intended to be used from harness code, not
330    * from tests.
331    */
332   "logFailure": function(aTestName, aMessage) {
333     this.report({
334       "name": aTestName,
335       "message": aMessage,
336       "result": false,
337       "todo": false
338     });
339   },
341   /**
342    * Timeout the current test. Intended to be used from harness code, not
343    * from tests.
344    */
345   "timeout": async function() {
346     this.logFailure("Timeout", "Test runner timed us out.");
347     timeout();
348   }
350 (function() {
351   try {
352     var path = W3CTest.getPath();
353     if (path) {
354       // Get expected fails.  If there aren't any, there will be a 404, which is
355       // fine.  Anything else is unexpected.
356       var url = W3CTest.pathprefix + "failures/" + path + ".json";
357       var request = new XMLHttpRequest();
358       request.open("GET", url, false);
359       request.send();
360       if (request.status === 200) {
361         W3CTest.expectedFailures = JSON.parse(request.responseText);
362       } else if (request.status !== 404) {
363         W3CTest.logFailure("Fetching failures file", "Request status was " + request.status);
364       }
365     }
367     add_result_callback(W3CTest.result.bind(W3CTest));
368     add_completion_callback(W3CTest.finish.bind(W3CTest));
369     setup({
370       "output": W3CTest.runner && !W3CTest.runner.getParameterInfo().closeWhenDone,
371       "explicit_timeout": true
372     });
373   } catch (e) {
374     W3CTest.logFailure("Harness setup", "Unexpected exception: " + e);
375   }
376 })();