1 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 // We expect these to be defined in the global scope by runtest.py.
8 /* global __LOCATION__, _PROFILE_PATH, _SERVER_PORT, _SERVER_ADDR, _DISPLAY_RESULTS,
9 _TEST_PREFIX, _HTTPD_PATH */
10 // Defined by xpcshell
13 /* eslint-disable mozilla/use-chromeutils-generateqi */
15 // Set up a protocol substituion so that we can load the httpd.js file.
16 let protocolHandler = Services.io
17 .getProtocolHandler("resource")
18 .QueryInterface(Ci.nsIResProtocolHandler);
19 let httpdJSPath = PathUtils.toFileURI(_HTTPD_PATH);
21 protocolHandler.setSubstitution(
23 Services.io.newURI(httpdJSPath)
25 const { HttpServer, dumpn, setDebuggingStatus } = ChromeUtils.importESModule(
26 "resource://httpd-server/httpd.sys.mjs"
29 protocolHandler.setSubstitution(
31 Services.io.newFileURI(__LOCATION__.parent)
33 /* import-globals-from mochitestListingsUtils.js */
34 Services.scriptloader.loadSubScript(
35 "resource://mochitest-server/mochitestListingsUtils.js",
39 const CC = Components.Constructor;
41 const FileInputStream = CC(
42 "@mozilla.org/network/file-input-stream;1",
46 const ConverterInputStream = CC(
47 "@mozilla.org/intl/converter-input-stream;1",
48 "nsIConverterInputStream",
52 // Disable automatic network detection, so tests work correctly when
53 // not connected to a network.
54 // eslint-disable-next-line mozilla/use-services
55 var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
56 ios.manageOfflineStatus = false;
59 var server; // for use in the shutdown handler, if necessary
61 var _quitting = false;
63 /** Quit when all activity has completed. */
64 function serverStopped() {
73 // We can only have gotten here if the /server/shutdown path was requested.
75 dumpn("HTTP server stopped, all pending requests complete");
79 // Impossible as the stop callback should have been called, but to be safe...
80 dumpn("TEST-UNEXPECTED-FAIL | failure to correctly shut down HTTP server");
84 var displayResults = true;
92 function runServer() {
93 serverBasePath = __LOCATION__.parent;
94 server = createMochitestServer(serverBasePath);
96 // verify server address
97 // if a.b.c.d or 'localhost'
98 if (typeof _SERVER_ADDR != "undefined") {
99 if (_SERVER_ADDR == "localhost") {
100 gServerAddress = _SERVER_ADDR;
102 var quads = _SERVER_ADDR.split(".");
103 if (quads.length == 4) {
105 for (var i = 0; i < 4; i++) {
106 if (quads[i] < 0 || quads[i] > 255) {
111 gServerAddress = _SERVER_ADDR;
114 "invalid _SERVER_ADDR, please specify a valid IP Address"
121 "please define _SERVER_ADDR (as an ip address) before running server.js"
125 if (typeof _SERVER_PORT != "undefined") {
126 if (parseInt(_SERVER_PORT) > 0 && parseInt(_SERVER_PORT) < 65536) {
127 SERVER_PORT = _SERVER_PORT;
131 "please define _SERVER_PORT (as a port number) before running server.js"
135 // If DISPLAY_RESULTS is not specified, it defaults to true
136 if (typeof _DISPLAY_RESULTS != "undefined") {
137 displayResults = _DISPLAY_RESULTS;
140 server._start(SERVER_PORT, gServerAddress);
142 // touch a file in the profile directory to indicate we're alive
143 var foStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
144 Ci.nsIFileOutputStream
146 var serverAlive = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
148 if (typeof _PROFILE_PATH == "undefined") {
149 serverAlive.initWithFile(serverBasePath);
150 serverAlive.append("mochitesttestingprofile");
152 serverAlive.initWithPath(_PROFILE_PATH);
155 // Create a file to inform the harness that the server is ready
156 if (serverAlive.exists()) {
157 serverAlive.append("server_alive.txt");
158 foStream.init(serverAlive, 0x02 | 0x08 | 0x20, 436, 0); // write, create, truncate
159 var data = "It's alive!";
160 foStream.write(data, data.length);
164 "Failed to create server_alive.txt because " +
166 " could not be found."
173 // The following is threading magic to spin an event loop -- this has to
174 // happen manually in xpcshell for the server to actually work.
176 var thread = Cc["@mozilla.org/thread-manager;1"].getService().currentThread;
177 while (!server.isStopped()) {
178 thread.processNextEvent(true);
181 // Server stopped by /server/shutdown handler -- go through pending events
184 // get rid of any pending requests
185 while (thread.hasPendingEvents()) {
186 thread.processNextEvent(true);
190 /** Creates and returns an HTTP server configured to serve Mochitests. */
191 function createMochitestServer(serverBasePath) {
192 var server = new HttpServer();
194 server.registerDirectory("/", serverBasePath);
195 server.registerPathHandler("/server/shutdown", serverShutdown);
196 server.registerPathHandler("/server/debug", serverDebug);
197 server.registerContentType("sjs", "sjs"); // .sjs == CGI-like functionality
198 server.registerContentType("jar", "application/x-jar");
199 server.registerContentType("ogg", "application/ogg");
200 server.registerContentType("pdf", "application/pdf");
201 server.registerContentType("ogv", "video/ogg");
202 server.registerContentType("oga", "audio/ogg");
203 server.registerContentType("opus", "audio/ogg; codecs=opus");
204 server.registerContentType("dat", "text/plain; charset=utf-8");
205 server.registerContentType("frag", "text/plain"); // .frag == WebGL fragment shader
206 server.registerContentType("vert", "text/plain"); // .vert == WebGL vertex shader
207 server.registerContentType("wasm", "application/wasm");
208 server.setIndexHandler(defaultDirHandler);
211 getFile: function getFile(path) {
212 var file = serverBasePath.clone().QueryInterface(Ci.nsIFile);
213 path.split("/").forEach(function (p) {
214 file.appendRelativePath(p);
223 server.setObjectState("SERVER_ROOT", serverRoot);
225 processLocations(server);
231 * Notifies the HTTP server about all the locations at which it might receive
232 * requests, so that it can properly respond to requests on any of the hosts it
235 function processLocations(server) {
236 var serverLocations = serverBasePath.clone();
237 serverLocations.append("server-locations.txt");
239 const PR_RDONLY = 0x01;
240 var fis = new FileInputStream(
244 Ci.nsIFileInputStream.CLOSE_ON_EOF
247 var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
248 lis.QueryInterface(Ci.nsIUnicharLineInputStream);
250 const LINE_REGEXP = new RegExp(
251 "^([a-z][-a-z0-9+.]*)" +
254 "\\d+\\.\\d+\\.\\d+\\.\\d+" +
256 "(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\\.)*" +
257 "[a-z](?:[-a-z0-9]*[a-z0-9])?" +
269 var seenPrimary = false;
271 var more = lis.readLine(line);
274 var lineValue = line.value;
275 if (lineValue.charAt(0) == "#" || lineValue == "") {
279 var match = LINE_REGEXP.exec(lineValue);
281 throw new Error("Syntax error in server-locations.txt, line " + lineno);
284 var [, scheme, host, port, options] = match;
286 if (options.split(",").includes("primary")) {
289 "Multiple primary locations in server-locations.txt, " +
295 server.identity.setPrimary(scheme, host, port);
301 server.identity.add(scheme, host, port);
308 function serverShutdown(metadata, response) {
309 response.setStatusLine("1.1", 200, "OK");
310 response.setHeader("Content-type", "text/plain", false);
312 var body = "Server shut down.";
313 response.bodyOutputStream.write(body, body.length);
315 dumpn("Server shutting down now...");
316 server.stop(serverStopped);
319 // /server/debug?[012]
320 function serverDebug(metadata, response) {
321 response.setStatusLine(metadata.httpVersion, 400, "Bad debugging level");
322 if (metadata.queryString.length !== 1) {
327 if (metadata.queryString === "0") {
328 // do this now so it gets logged with the old mode
329 dumpn("Server debug logs disabled.");
330 setDebuggingStatus(false, false);
332 } else if (metadata.queryString === "1") {
333 setDebuggingStatus(true, false);
335 } else if (metadata.queryString === "2") {
336 setDebuggingStatus(true, true);
337 mode = "enabled, with timestamps";
342 response.setStatusLine(metadata.httpVersion, 200, "OK");
343 response.setHeader("Content-type", "text/plain", false);
344 var body = "Server debug logs " + mode + ".";
345 response.bodyOutputStream.write(body, body.length);
350 * Produce a normal directory listing.
352 function regularListing(metadata, response) {
353 var [links] = list(metadata.path, metadata.getProperty("directory"), false);
355 "<!DOCTYPE html>\n" +
357 HEAD(TITLE("mochitest index ", metadata.path)),
358 BODY(BR(), A({ href: ".." }, "Up a level"), UL(linksToListItems(links)))
364 * Read a manifestFile located at the root of the server's directory and turn
365 * it into an object for creating a table of clickable links for each test.
367 function convertManifestToTestLinks(root, manifest) {
368 const { NetUtil } = ChromeUtils.importESModule(
369 "resource://gre/modules/NetUtil.sys.mjs"
372 var manifestFile = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
373 manifestFile.initWithFile(serverBasePath);
374 manifestFile.append(manifest);
376 var manifestStream = Cc[
377 "@mozilla.org/network/file-input-stream;1"
378 ].createInstance(Ci.nsIFileInputStream);
379 manifestStream.init(manifestFile, -1, 0, 0);
381 var manifestObj = JSON.parse(
382 NetUtil.readInputStreamToString(manifestStream, manifestStream.available())
384 var paths = manifestObj.tests;
385 var pathPrefix = "/" + root + "/";
387 paths.reduce(function (t, p) {
388 t[pathPrefix + p.path] = true;
396 * Produce a test harness page containing all the test cases
397 * below it, recursively.
399 function testListing(metadata, response) {
402 if (!metadata.queryString.includes("manifestFile")) {
403 [links, count] = list(
405 metadata.getProperty("directory"),
408 } else if (typeof Components != "undefined") {
409 var manifest = metadata.queryString.match(/manifestFile=([^&]+)/)[1];
411 [links, count] = convertManifestToTestLinks(
412 metadata.path.split("/")[1],
418 metadata.queryString.indexOf("hideResultsTable=1") > -1 ? "invisible" : "";
421 metadata.queryString.indexOf("testname=") > -1
422 ? metadata.queryString.match(/testname=([^&]+)/)[1]
425 dumpn("count: " + count);
426 var tests = testname ? "['/" + testname + "']" : jsonArrayOfTestFiles(links);
430 TITLE("MochiTest | ", metadata.path),
434 href: "/static/harness.css",
437 type: "text/javascript",
438 src: "/tests/SimpleTest/LogController.js",
441 type: "text/javascript",
442 src: "/tests/SimpleTest/MemoryStats.js",
445 type: "text/javascript",
446 src: "/tests/SimpleTest/TestRunner.js",
449 type: "text/javascript",
450 src: "/tests/SimpleTest/MozillaLogger.js",
452 SCRIPT({ type: "text/javascript", src: "/chunkifyTests.js" }),
453 SCRIPT({ type: "text/javascript", src: "/manifestLibrary.js" }),
454 SCRIPT({ type: "text/javascript", src: "/tests/SimpleTest/setup.js" }),
456 { type: "text/javascript" },
457 "window.onload = hookup; gTestList=" + tests + ";"
462 { class: "container" },
463 H2("--> ", A({ href: "#", id: "runtests" }, "Run Tests"), " <--"),
465 { style: "float: right;" },
468 A({ href: "http://www.mochikit.com/" }, "MochiKit"),
474 H1({ id: "indicator" }, "Status"),
475 H2({ id: "pass" }, "Passed: ", SPAN({ id: "pass-count" }, "0")),
476 H2({ id: "fail" }, "Failed: ", SPAN({ id: "fail-count" }, "0")),
477 H2({ id: "fail" }, "Todo: ", SPAN({ id: "todo-count" }, "0"))
479 DIV({ class: "clear" }),
481 { id: "current-test" },
482 B("Currently Executing: ", SPAN({ id: "current-test-path" }, "_"))
484 DIV({ class: "clear" }),
486 { class: "frameholder" },
490 allow: "geolocation 'src'",
491 allowfullscreen: true,
494 DIV({ class: "clear" }),
497 A({ href: "#", id: "toggleNonTests" }, "Show Non-Tests"),
509 TR(TD("Passed"), TD("Failed"), TD("Todo"), TD("Test Files")),
510 linksToTableRows(links, 0)
522 DIV({ class: "clear" })
530 * Respond to requests that match a file system directory.
531 * Under the tests/ directory, return a test harness page.
533 function defaultDirHandler(metadata, response) {
534 response.setStatusLine("1.1", 200, "OK");
535 response.setHeader("Content-type", "text/html;charset=utf-8", false);
537 if (metadata.path.indexOf("/tests") != 0) {
538 regularListing(metadata, response);
540 testListing(metadata, response);