michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: var W3CTest = { michael@0: /** michael@0: * Dictionary mapping a test URL to either the string "all", which means that michael@0: * all tests in this file are expected to fail, or a dictionary mapping test michael@0: * names to either the boolean |true|, or the string "debug". The former michael@0: * means that this test is expected to fail in all builds, and the latter michael@0: * that it is only expected to fail in debug builds. michael@0: */ michael@0: "expectedFailures": {}, michael@0: michael@0: /** michael@0: * If set to true, we will dump the test failures to the console. michael@0: */ michael@0: "dumpFailures": false, michael@0: michael@0: /** michael@0: * If dumpFailures is true, this holds a structure like necessary for michael@0: * expectedFailures, for ease of updating the expectations. michael@0: */ michael@0: "failures": {}, michael@0: michael@0: /** michael@0: * List of test results, needed by TestRunner to update the UI. michael@0: */ michael@0: "tests": [], michael@0: michael@0: /** michael@0: * Number of unlogged passes, to stop buildbot from truncating the log. michael@0: * We will print a message every MAX_COLLAPSED_MESSAGES passes. michael@0: */ michael@0: "collapsedMessages": 0, michael@0: "MAX_COLLAPSED_MESSAGES": 100, michael@0: michael@0: /** michael@0: * Reference to the TestRunner object in the parent frame. michael@0: */ michael@0: "runner": parent === this ? null : parent.TestRunner || parent.wrappedJSObject.TestRunner, michael@0: michael@0: /** michael@0: * Prefixes for the error logging. Indexed first by int(todo) and second by michael@0: * int(result). michael@0: */ michael@0: "prefixes": [ michael@0: ["TEST-UNEXPECTED-FAIL", "TEST-PASS"], michael@0: ["TEST-KNOWN-FAIL", "TEST-UNEXPECTED-PASS"] michael@0: ], michael@0: michael@0: /** michael@0: * Prefix of the path to parent of the the failures directory. michael@0: */ michael@0: "pathprefix": "/tests/dom/imptests/", michael@0: michael@0: /** michael@0: * Returns the URL of the current test, relative to the root W3C tests michael@0: * directory. Used as a key into the expectedFailures dictionary. michael@0: */ michael@0: "getPath": function() { michael@0: var url = this.getURL(); michael@0: if (!url.startsWith(this.pathprefix)) { michael@0: return ""; michael@0: } michael@0: return url.substring(this.pathprefix.length); michael@0: }, michael@0: michael@0: /** michael@0: * Returns the root-relative URL of the current test. michael@0: */ michael@0: "getURL": function() { michael@0: return this.runner ? this.runner.currentTestURL : location.pathname; michael@0: }, michael@0: michael@0: /** michael@0: * Report the results in the tests array. michael@0: */ michael@0: "reportResults": function() { michael@0: var element = function element(aLocalName) { michael@0: var xhtmlNS = "http://www.w3.org/1999/xhtml"; michael@0: return document.createElementNS(xhtmlNS, aLocalName); michael@0: }; michael@0: michael@0: var stylesheet = element("link"); michael@0: stylesheet.setAttribute("rel", "stylesheet"); michael@0: stylesheet.setAttribute("href", "/resources/testharness.css"); michael@0: var heads = document.getElementsByTagName("head"); michael@0: if (heads.length) { michael@0: heads[0].appendChild(stylesheet); michael@0: } michael@0: michael@0: var log = document.getElementById("log"); michael@0: if (!log) { michael@0: return; michael@0: } michael@0: var section = log.appendChild(element("section")); michael@0: section.id = "summary"; michael@0: section.appendChild(element("h2")).textContent = "Details"; michael@0: michael@0: var table = section.appendChild(element("table")); michael@0: table.id = "results"; michael@0: michael@0: var tr = table.appendChild(element("thead")).appendChild(element("tr")); michael@0: for (var header of ["Result", "Test Name", "Message"]) { michael@0: tr.appendChild(element("th")).textContent = header; michael@0: } michael@0: var statuses = [ michael@0: ["Unexpected Fail", "Pass"], michael@0: ["Known Fail", "Unexpected Pass"] michael@0: ]; michael@0: var tbody = table.appendChild(element("tbody")); michael@0: for (var test of this.tests) { michael@0: tr = tbody.appendChild(element("tr")); michael@0: tr.className = (test.result === !test.todo ? "pass" : "fail"); michael@0: tr.appendChild(element("td")).textContent = michael@0: statuses[+test.todo][+test.result]; michael@0: tr.appendChild(element("td")).textContent = test.name; michael@0: tr.appendChild(element("td")).textContent = test.message; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Returns a printable message based on aTest's 'name' and 'message' michael@0: * properties. michael@0: */ michael@0: "formatTestMessage": function(aTest) { michael@0: return aTest.name + (aTest.message ? ": " + aTest.message : ""); michael@0: }, michael@0: michael@0: /** michael@0: * Lets the test runner know about a test result. michael@0: */ michael@0: "_log": function(test) { michael@0: var url = this.getURL(); michael@0: var msg = this.prefixes[+test.todo][+test.result] + " | "; michael@0: if (url) { michael@0: msg += url; michael@0: } michael@0: msg += " | " + this.formatTestMessage(test); michael@0: if (this.runner) { michael@0: this.runner[(test.result === !test.todo) ? "log" : "error"](msg); michael@0: } else { michael@0: dump(msg + "\n"); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Logs a message about collapsed messages (if any), and resets the counter. michael@0: */ michael@0: "_logCollapsedMessages": function() { michael@0: if (this.collapsedMessages) { michael@0: this._log({ michael@0: "name": document.title, michael@0: "result": true, michael@0: "todo": false, michael@0: "message": "Elided " + this.collapsedMessages + " passes or known failures." michael@0: }); michael@0: } michael@0: this.collapsedMessages = 0; michael@0: }, michael@0: michael@0: /** michael@0: * Maybe logs a result, eliding up to MAX_COLLAPSED_MESSAGES consecutive michael@0: * passes. michael@0: */ michael@0: "_maybeLog": function(test) { michael@0: var success = (test.result === !test.todo); michael@0: if (success && ++this.collapsedMessages < this.MAX_COLLAPSED_MESSAGES) { michael@0: return; michael@0: } michael@0: this._logCollapsedMessages(); michael@0: this._log(test); michael@0: }, michael@0: michael@0: /** michael@0: * Reports a test result. The argument is an object with the following michael@0: * properties: michael@0: * michael@0: * o message (string): message to be reported michael@0: * o result (boolean): whether this test failed michael@0: * o todo (boolean): whether this test is expected to fail michael@0: */ michael@0: "report": function(test) { michael@0: this.tests.push(test); michael@0: this._maybeLog(test); michael@0: }, michael@0: michael@0: /** michael@0: * Returns true if this test is expected to fail, and false otherwise. michael@0: */ michael@0: "_todo": function(test) { michael@0: if (this.expectedFailures === "all") { michael@0: return true; michael@0: } michael@0: var value = this.expectedFailures[test.name]; michael@0: return value === true || (value === "debug" && !!SpecialPowers.isDebugBuild); michael@0: }, michael@0: michael@0: /** michael@0: * Callback function for testharness.js. Called when one test in a file michael@0: * finishes. michael@0: */ michael@0: "result": function(test) { michael@0: var url = this.getPath(); michael@0: this.report({ michael@0: "name": test.name, michael@0: "message": test.message || "", michael@0: "result": test.status === test.PASS, michael@0: "todo": this._todo(test) michael@0: }); michael@0: if (this.dumpFailures && test.status !== test.PASS) { michael@0: this.failures[test.name] = true; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Callback function for testharness.js. Called when the entire test file michael@0: * finishes. michael@0: */ michael@0: "finish": function(tests, status) { michael@0: var url = this.getPath(); michael@0: this.report({ michael@0: "name": "Finished test", michael@0: "message": "Status: " + status.status, michael@0: "result": status.status === status.OK, michael@0: "todo": michael@0: url in this.expectedFailures && michael@0: this.expectedFailures[url] === "error" michael@0: }); michael@0: michael@0: this._logCollapsedMessages(); michael@0: michael@0: if (this.dumpFailures) { michael@0: dump("@@@ @@@ Failures\n"); michael@0: dump(url + "@@@" + JSON.stringify(this.failures) + "\n"); michael@0: } michael@0: if (this.runner) { michael@0: this.runner.testFinished(this.tests.map(function(aTest) { michael@0: return { michael@0: "message": this.formatTestMessage(aTest), michael@0: "result": aTest.result, michael@0: "todo": aTest.todo michael@0: }; michael@0: }, this)); michael@0: } else { michael@0: this.reportResults(); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Log an unexpected failure. Intended to be used from harness code, not michael@0: * from tests. michael@0: */ michael@0: "logFailure": function(aTestName, aMessage) { michael@0: this.report({ michael@0: "name": aTestName, michael@0: "message": aMessage, michael@0: "result": false, michael@0: "todo": false michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Timeout the current test. Intended to be used from harness code, not michael@0: * from tests. michael@0: */ michael@0: "timeout": function() { michael@0: this.logFailure("Timeout", "Test runner timed us out."); michael@0: timeout(); michael@0: } michael@0: }; michael@0: (function() { michael@0: try { michael@0: var path = W3CTest.getPath(); michael@0: if (path) { michael@0: // Get expected fails. If there aren't any, there will be a 404, which is michael@0: // fine. Anything else is unexpected. michael@0: var url = W3CTest.pathprefix + "failures/" + path + ".json"; michael@0: var request = new XMLHttpRequest(); michael@0: request.open("GET", url, false); michael@0: request.send(); michael@0: if (request.status === 200) { michael@0: W3CTest.expectedFailures = JSON.parse(request.responseText); michael@0: } else if (request.status !== 404) { michael@0: W3CTest.logFailure("Fetching failures file", "Request status was " + request.status); michael@0: } michael@0: } michael@0: michael@0: add_result_callback(W3CTest.result.bind(W3CTest)); michael@0: add_completion_callback(W3CTest.finish.bind(W3CTest)); michael@0: setup({ michael@0: "output": false, michael@0: "explicit_timeout": true michael@0: }); michael@0: } catch (e) { michael@0: W3CTest.logFailure("Harness setup", "Unexpected exception: " + e); michael@0: } michael@0: })();