testharnessreport.js (10911B)
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/. */ 4 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": {}, 14 15 /** 16 * If set to true, we will dump the test failures to the console. 17 */ 18 "dumpFailures": false, 19 20 /** 21 * If dumpFailures is true, this holds a structure like necessary for 22 * expectedFailures, for ease of updating the expectations. 23 */ 24 "failures": {}, 25 26 /** 27 * List of test results, needed by TestRunner to update the UI. 28 */ 29 "tests": [], 30 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, 37 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() { 45 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 }(), 105 106 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 ], 121 122 /** 123 * Prefix of the path to parent of the the failures directory. 124 */ 125 "pathprefix": "/tests/dom/imptests/", 126 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 }, 138 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 }, 145 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 }; 154 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 } 162 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"; 170 171 var table = section.appendChild(element("table")); 172 table.id = "results"; 173 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 }, 192 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 }, 200 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]; 208 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 }, 224 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 }, 239 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 }, 252 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 }, 265 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 }, 276 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 }, 293 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 }); 308 309 this._logCollapsedMessages(); 310 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 }, 327 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 }, 340 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 } 349 }; 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 } 366 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 })();