michael@0: /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et: */ 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: // Note that the server script itself already defines Cc, Ci, and Cr for us, michael@0: // and because they're constants it's not safe to redefine them. Scope leakage michael@0: // sucks. michael@0: michael@0: // Disable automatic network detection, so tests work correctly when michael@0: // not connected to a network. michael@0: let (ios = Cc["@mozilla.org/network/io-service;1"] michael@0: .getService(Ci.nsIIOService2)) { michael@0: ios.manageOfflineStatus = false; michael@0: ios.offline = false; michael@0: } michael@0: michael@0: var server; // for use in the shutdown handler, if necessary michael@0: michael@0: // michael@0: // HTML GENERATION michael@0: // michael@0: var tags = ['A', 'ABBR', 'ACRONYM', 'ADDRESS', 'APPLET', 'AREA', 'B', 'BASE', michael@0: 'BASEFONT', 'BDO', 'BIG', 'BLOCKQUOTE', 'BODY', 'BR', 'BUTTON', michael@0: 'CAPTION', 'CENTER', 'CITE', 'CODE', 'COL', 'COLGROUP', 'DD', michael@0: 'DEL', 'DFN', 'DIR', 'DIV', 'DL', 'DT', 'EM', 'FIELDSET', 'FONT', michael@0: 'FORM', 'FRAME', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', michael@0: 'HEAD', 'HR', 'HTML', 'I', 'IFRAME', 'IMG', 'INPUT', 'INS', michael@0: 'ISINDEX', 'KBD', 'LABEL', 'LEGEND', 'LI', 'LINK', 'MAP', 'MENU', michael@0: 'META', 'NOFRAMES', 'NOSCRIPT', 'OBJECT', 'OL', 'OPTGROUP', michael@0: 'OPTION', 'P', 'PARAM', 'PRE', 'Q', 'S', 'SAMP', 'SCRIPT', michael@0: 'SELECT', 'SMALL', 'SPAN', 'STRIKE', 'STRONG', 'STYLE', 'SUB', michael@0: 'SUP', 'TABLE', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', michael@0: 'TITLE', 'TR', 'TT', 'U', 'UL', 'VAR']; michael@0: michael@0: /** michael@0: * Below, we'll use makeTagFunc to create a function for each of the michael@0: * strings in 'tags'. This will allow us to use s-expression like syntax michael@0: * to create HTML. michael@0: */ michael@0: function makeTagFunc(tagName) michael@0: { michael@0: return function (attrs /* rest... */) michael@0: { michael@0: var startChildren = 0; michael@0: var response = ""; michael@0: michael@0: // write the start tag and attributes michael@0: response += "<" + tagName; michael@0: // if attr is an object, write attributes michael@0: if (attrs && typeof attrs == 'object') { michael@0: startChildren = 1; michael@0: for (var [key,value] in attrs) { michael@0: var val = "" + value; michael@0: response += " " + key + '="' + val.replace('"','"') + '"'; michael@0: } michael@0: } michael@0: response += ">"; michael@0: michael@0: // iterate through the rest of the args michael@0: for (var i = startChildren; i < arguments.length; i++) { michael@0: if (typeof arguments[i] == 'function') { michael@0: response += arguments[i](); michael@0: } else { michael@0: response += arguments[i]; michael@0: } michael@0: } michael@0: michael@0: // write the close tag michael@0: response += "\n"; michael@0: return response; michael@0: } michael@0: } michael@0: michael@0: function makeTags() { michael@0: // map our global HTML generation functions michael@0: for each (var tag in tags) { michael@0: this[tag] = makeTagFunc(tag.toLowerCase()); michael@0: } michael@0: } michael@0: michael@0: var _quitting = false; michael@0: michael@0: /** Quit when all activity has completed. */ michael@0: function serverStopped() michael@0: { michael@0: _quitting = true; michael@0: } michael@0: michael@0: // only run the "main" section if httpd.js was loaded ahead of us michael@0: if (this["nsHttpServer"]) { michael@0: // michael@0: // SCRIPT CODE michael@0: // michael@0: runServer(); michael@0: michael@0: // We can only have gotten here if the /server/shutdown path was requested. michael@0: if (_quitting) michael@0: { michael@0: dumpn("HTTP server stopped, all pending requests complete"); michael@0: quit(0); michael@0: } michael@0: michael@0: // Impossible as the stop callback should have been called, but to be safe... michael@0: dumpn("TEST-UNEXPECTED-FAIL | failure to correctly shut down HTTP server"); michael@0: quit(1); michael@0: } michael@0: michael@0: var serverBasePath; michael@0: var displayResults = true; michael@0: michael@0: // michael@0: // SERVER SETUP michael@0: // michael@0: function runServer() michael@0: { michael@0: serverBasePath = __LOCATION__.parent; michael@0: server = createMochitestServer(serverBasePath); michael@0: michael@0: //verify server address michael@0: //if a.b.c.d or 'localhost' michael@0: if (typeof(_SERVER_ADDR) != "undefined") { michael@0: if (_SERVER_ADDR == "localhost") { michael@0: gServerAddress = _SERVER_ADDR; michael@0: } else { michael@0: var quads = _SERVER_ADDR.split('.'); michael@0: if (quads.length == 4) { michael@0: var invalid = false; michael@0: for (var i=0; i < 4; i++) { michael@0: if (quads[i] < 0 || quads[i] > 255) michael@0: invalid = true; michael@0: } michael@0: if (!invalid) michael@0: gServerAddress = _SERVER_ADDR; michael@0: else michael@0: throw "invalid _SERVER_ADDR, please specify a valid IP Address"; michael@0: } michael@0: } michael@0: } else { michael@0: throw "please defined _SERVER_ADDR (as an ip address) before running server.js"; michael@0: } michael@0: michael@0: if (typeof(_SERVER_PORT) != "undefined") { michael@0: if (parseInt(_SERVER_PORT) > 0 && parseInt(_SERVER_PORT) < 65536) michael@0: SERVER_PORT = _SERVER_PORT; michael@0: } else { michael@0: throw "please define _SERVER_PORT (as a port number) before running server.js"; michael@0: } michael@0: michael@0: // If DISPLAY_RESULTS is not specified, it defaults to true michael@0: if (typeof(_DISPLAY_RESULTS) != "undefined") { michael@0: displayResults = _DISPLAY_RESULTS; michael@0: } michael@0: michael@0: server._start(SERVER_PORT, gServerAddress); michael@0: michael@0: // touch a file in the profile directory to indicate we're alive michael@0: var foStream = Cc["@mozilla.org/network/file-output-stream;1"] michael@0: .createInstance(Ci.nsIFileOutputStream); michael@0: var serverAlive = Cc["@mozilla.org/file/local;1"] michael@0: .createInstance(Ci.nsILocalFile); michael@0: michael@0: if (typeof(_PROFILE_PATH) == "undefined") { michael@0: serverAlive.initWithFile(serverBasePath); michael@0: serverAlive.append("mochitesttestingprofile"); michael@0: } else { michael@0: serverAlive.initWithPath(_PROFILE_PATH); michael@0: } michael@0: michael@0: // If we're running outside of the test harness, there might michael@0: // not be a test profile directory present michael@0: if (serverAlive.exists()) { michael@0: serverAlive.append("server_alive.txt"); michael@0: foStream.init(serverAlive, michael@0: 0x02 | 0x08 | 0x20, 436, 0); // write, create, truncate michael@0: data = "It's alive!"; michael@0: foStream.write(data, data.length); michael@0: foStream.close(); michael@0: } michael@0: michael@0: makeTags(); michael@0: michael@0: // michael@0: // The following is threading magic to spin an event loop -- this has to michael@0: // happen manually in xpcshell for the server to actually work. michael@0: // michael@0: var thread = Cc["@mozilla.org/thread-manager;1"] michael@0: .getService() michael@0: .currentThread; michael@0: while (!server.isStopped()) michael@0: thread.processNextEvent(true); michael@0: michael@0: // Server stopped by /server/shutdown handler -- go through pending events michael@0: // and return. michael@0: michael@0: // get rid of any pending requests michael@0: while (thread.hasPendingEvents()) michael@0: thread.processNextEvent(true); michael@0: } michael@0: michael@0: /** Creates and returns an HTTP server configured to serve Mochitests. */ michael@0: function createMochitestServer(serverBasePath) michael@0: { michael@0: var server = new nsHttpServer(); michael@0: michael@0: server.registerDirectory("/", serverBasePath); michael@0: server.registerPathHandler("/server/shutdown", serverShutdown); michael@0: server.registerPathHandler("/server/debug", serverDebug); michael@0: server.registerContentType("sjs", "sjs"); // .sjs == CGI-like functionality michael@0: server.registerContentType("jar", "application/x-jar"); michael@0: server.registerContentType("ogg", "application/ogg"); michael@0: server.registerContentType("pdf", "application/pdf"); michael@0: server.registerContentType("ogv", "video/ogg"); michael@0: server.registerContentType("oga", "audio/ogg"); michael@0: server.registerContentType("opus", "audio/ogg; codecs=opus"); michael@0: server.registerContentType("dat", "text/plain; charset=utf-8"); michael@0: server.registerContentType("frag", "text/plain"); // .frag == WebGL fragment shader michael@0: server.registerContentType("vert", "text/plain"); // .vert == WebGL vertex shader michael@0: server.setIndexHandler(defaultDirHandler); michael@0: michael@0: var serverRoot = michael@0: { michael@0: getFile: function getFile(path) michael@0: { michael@0: var file = serverBasePath.clone().QueryInterface(Ci.nsILocalFile); michael@0: path.split("/").forEach(function(p) { michael@0: file.appendRelativePath(p); michael@0: }); michael@0: return file; michael@0: }, michael@0: QueryInterface: function(aIID) { return this; } michael@0: }; michael@0: michael@0: server.setObjectState("SERVER_ROOT", serverRoot); michael@0: michael@0: processLocations(server); michael@0: michael@0: return server; michael@0: } michael@0: michael@0: /** michael@0: * Notifies the HTTP server about all the locations at which it might receive michael@0: * requests, so that it can properly respond to requests on any of the hosts it michael@0: * serves. michael@0: */ michael@0: function processLocations(server) michael@0: { michael@0: var serverLocations = serverBasePath.clone(); michael@0: serverLocations.append("server-locations.txt"); michael@0: michael@0: const PR_RDONLY = 0x01; michael@0: var fis = new FileInputStream(serverLocations, PR_RDONLY, 292 /* 0444 */, michael@0: Ci.nsIFileInputStream.CLOSE_ON_EOF); michael@0: michael@0: var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0); michael@0: lis.QueryInterface(Ci.nsIUnicharLineInputStream); michael@0: michael@0: const LINE_REGEXP = michael@0: new RegExp("^([a-z][-a-z0-9+.]*)" + michael@0: "://" + michael@0: "(" + michael@0: "\\d+\\.\\d+\\.\\d+\\.\\d+" + michael@0: "|" + michael@0: "(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\\.)*" + michael@0: "[a-z](?:[-a-z0-9]*[a-z0-9])?" + michael@0: ")" + michael@0: ":" + michael@0: "(\\d+)" + michael@0: "(?:" + michael@0: "\\s+" + michael@0: "(\\S+(?:,\\S+)*)" + michael@0: ")?$"); michael@0: michael@0: var line = {}; michael@0: var lineno = 0; michael@0: var seenPrimary = false; michael@0: do michael@0: { michael@0: var more = lis.readLine(line); michael@0: lineno++; michael@0: michael@0: var lineValue = line.value; michael@0: if (lineValue.charAt(0) == "#" || lineValue == "") michael@0: continue; michael@0: michael@0: var match = LINE_REGEXP.exec(lineValue); michael@0: if (!match) michael@0: throw "Syntax error in server-locations.txt, line " + lineno; michael@0: michael@0: var [, scheme, host, port, options] = match; michael@0: if (options) michael@0: { michael@0: if (options.split(",").indexOf("primary") >= 0) michael@0: { michael@0: if (seenPrimary) michael@0: { michael@0: throw "Multiple primary locations in server-locations.txt, " + michael@0: "line " + lineno; michael@0: } michael@0: michael@0: server.identity.setPrimary(scheme, host, port); michael@0: seenPrimary = true; michael@0: continue; michael@0: } michael@0: } michael@0: michael@0: server.identity.add(scheme, host, port); michael@0: } michael@0: while (more); michael@0: } michael@0: michael@0: michael@0: // PATH HANDLERS michael@0: michael@0: // /server/shutdown michael@0: function serverShutdown(metadata, response) michael@0: { michael@0: response.setStatusLine("1.1", 200, "OK"); michael@0: response.setHeader("Content-type", "text/plain", false); michael@0: michael@0: var body = "Server shut down."; michael@0: response.bodyOutputStream.write(body, body.length); michael@0: michael@0: dumpn("Server shutting down now..."); michael@0: server.stop(serverStopped); michael@0: } michael@0: michael@0: // /server/debug?[012] michael@0: function serverDebug(metadata, response) michael@0: { michael@0: response.setStatusLine(metadata.httpVersion, 400, "Bad debugging level"); michael@0: if (metadata.queryString.length !== 1) michael@0: return; michael@0: michael@0: var mode; michael@0: if (metadata.queryString === "0") { michael@0: // do this now so it gets logged with the old mode michael@0: dumpn("Server debug logs disabled."); michael@0: DEBUG = false; michael@0: DEBUG_TIMESTAMP = false; michael@0: mode = "disabled"; michael@0: } else if (metadata.queryString === "1") { michael@0: DEBUG = true; michael@0: DEBUG_TIMESTAMP = false; michael@0: mode = "enabled"; michael@0: } else if (metadata.queryString === "2") { michael@0: DEBUG = true; michael@0: DEBUG_TIMESTAMP = true; michael@0: mode = "enabled, with timestamps"; michael@0: } else { michael@0: return; michael@0: } michael@0: michael@0: response.setStatusLine(metadata.httpVersion, 200, "OK"); michael@0: response.setHeader("Content-type", "text/plain", false); michael@0: var body = "Server debug logs " + mode + "."; michael@0: response.bodyOutputStream.write(body, body.length); michael@0: dumpn(body); michael@0: } michael@0: michael@0: // michael@0: // DIRECTORY LISTINGS michael@0: // michael@0: michael@0: /** michael@0: * Creates a generator that iterates over the contents of michael@0: * an nsIFile directory. michael@0: */ michael@0: function dirIter(dir) michael@0: { michael@0: var en = dir.directoryEntries; michael@0: while (en.hasMoreElements()) { michael@0: var file = en.getNext(); michael@0: yield file.QueryInterface(Ci.nsILocalFile); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Builds an optionally nested object containing links to the michael@0: * files and directories within dir. michael@0: */ michael@0: function list(requestPath, directory, recurse) michael@0: { michael@0: var count = 0; michael@0: var path = requestPath; michael@0: if (path.charAt(path.length - 1) != "/") { michael@0: path += "/"; michael@0: } michael@0: michael@0: var dir = directory.QueryInterface(Ci.nsIFile); michael@0: var links = {}; michael@0: michael@0: // The SimpleTest directory is hidden michael@0: var files = [file for (file in dirIter(dir)) michael@0: if (file.exists() && file.path.indexOf("SimpleTest") == -1)]; michael@0: michael@0: // Sort files by name, so that tests can be run in a pre-defined order inside michael@0: // a given directory (see bug 384823) michael@0: function leafNameComparator(first, second) { michael@0: if (first.leafName < second.leafName) michael@0: return -1; michael@0: if (first.leafName > second.leafName) michael@0: return 1; michael@0: return 0; michael@0: } michael@0: files.sort(leafNameComparator); michael@0: michael@0: count = files.length; michael@0: for each (var file in files) { michael@0: var key = path + file.leafName; michael@0: var childCount = 0; michael@0: if (file.isDirectory()) { michael@0: key += "/"; michael@0: } michael@0: if (recurse && file.isDirectory()) { michael@0: [links[key], childCount] = list(key, file, recurse); michael@0: count += childCount; michael@0: } else { michael@0: if (file.leafName.charAt(0) != '.') { michael@0: links[key] = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return [links, count]; michael@0: } michael@0: michael@0: /** michael@0: * Heuristic function that determines whether a given path michael@0: * is a test case to be executed in the harness, or just michael@0: * a supporting file. michael@0: */ michael@0: function isTest(filename, pattern) michael@0: { michael@0: if (pattern) michael@0: return pattern.test(filename); michael@0: michael@0: // File name is a URL style path to a test file, make sure that we check for michael@0: // tests that start with the appropriate prefix. michael@0: var testPrefix = typeof(_TEST_PREFIX) == "string" ? _TEST_PREFIX : "test_"; michael@0: var testPattern = new RegExp("^" + testPrefix); michael@0: michael@0: pathPieces = filename.split('/'); michael@0: michael@0: return testPattern.test(pathPieces[pathPieces.length - 1]) && michael@0: filename.indexOf(".js") == -1 && michael@0: filename.indexOf(".css") == -1 && michael@0: !/\^headers\^$/.test(filename); michael@0: } michael@0: michael@0: /** michael@0: * Transform nested hashtables of paths to nested HTML lists. michael@0: */ michael@0: function linksToListItems(links) michael@0: { michael@0: var response = ""; michael@0: var children = ""; michael@0: for (var [link, value] in links) { michael@0: var classVal = (!isTest(link) && !(value instanceof Object)) michael@0: ? "non-test invisible" michael@0: : "test"; michael@0: if (value instanceof Object) { michael@0: children = UL({class: "testdir"}, linksToListItems(value)); michael@0: } else { michael@0: children = ""; michael@0: } michael@0: michael@0: var bug_title = link.match(/test_bug\S+/); michael@0: var bug_num = null; michael@0: if (bug_title != null) { michael@0: bug_num = bug_title[0].match(/\d+/); michael@0: } michael@0: michael@0: if ((bug_title == null) || (bug_num == null)) { michael@0: response += LI({class: classVal}, A({href: link}, link), children); michael@0: } else { michael@0: var bug_url = "https://bugzilla.mozilla.org/show_bug.cgi?id="+bug_num; michael@0: response += LI({class: classVal}, A({href: link}, link), " - ", A({href: bug_url}, "Bug "+bug_num), children); michael@0: } michael@0: michael@0: } michael@0: return response; michael@0: } michael@0: michael@0: /** michael@0: * Transform nested hashtables of paths to a flat table rows. michael@0: */ michael@0: function linksToTableRows(links, recursionLevel) michael@0: { michael@0: var response = ""; michael@0: for (var [link, value] in links) { michael@0: var classVal = (!isTest(link) && !(value instanceof Object)) michael@0: ? "non-test invisible" michael@0: : ""; michael@0: michael@0: spacer = "padding-left: " + (10 * recursionLevel) + "px"; michael@0: michael@0: if (value instanceof Object) { michael@0: response += TR({class: "dir", id: "tr-" + link }, michael@0: TD({colspan: "3"}, " "), michael@0: TD({style: spacer}, michael@0: A({href: link}, link))); michael@0: response += linksToTableRows(value, recursionLevel + 1); michael@0: } else { michael@0: var bug_title = link.match(/test_bug\S+/); michael@0: var bug_num = null; michael@0: if (bug_title != null) { michael@0: bug_num = bug_title[0].match(/\d+/); michael@0: } michael@0: if ((bug_title == null) || (bug_num == null)) { michael@0: response += TR({class: classVal, id: "tr-" + link }, michael@0: TD("0"), michael@0: TD("0"), michael@0: TD("0"), michael@0: TD({style: spacer}, michael@0: A({href: link}, link))); michael@0: } else { michael@0: var bug_url = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + bug_num; michael@0: response += TR({class: classVal, id: "tr-" + link }, michael@0: TD("0"), michael@0: TD("0"), michael@0: TD("0"), michael@0: TD({style: spacer}, michael@0: A({href: link}, link), " - ", michael@0: A({href: bug_url}, "Bug " + bug_num))); michael@0: } michael@0: } michael@0: } michael@0: return response; michael@0: } michael@0: michael@0: function arrayOfTestFiles(linkArray, fileArray, testPattern) { michael@0: for (var [link, value] in Iterator(linkArray)) { michael@0: if (value instanceof Object) { michael@0: arrayOfTestFiles(value, fileArray, testPattern); michael@0: } else if (isTest(link, testPattern)) { michael@0: fileArray.push(link) michael@0: } michael@0: } michael@0: } michael@0: /** michael@0: * Produce a flat array of test file paths to be executed in the harness. michael@0: */ michael@0: function jsonArrayOfTestFiles(links) michael@0: { michael@0: var testFiles = []; michael@0: arrayOfTestFiles(links, testFiles); michael@0: testFiles = ['"' + file + '"' for each(file in testFiles)]; michael@0: return "[" + testFiles.join(",\n") + "]"; michael@0: } michael@0: michael@0: /** michael@0: * Produce a normal directory listing. michael@0: */ michael@0: function regularListing(metadata, response) michael@0: { michael@0: var [links, count] = list(metadata.path, michael@0: metadata.getProperty("directory"), michael@0: false); michael@0: response.write( michael@0: HTML( michael@0: HEAD( michael@0: TITLE("mochitest index ", metadata.path) michael@0: ), michael@0: BODY( michael@0: BR(), michael@0: A({href: ".."}, "Up a level"), michael@0: UL(linksToListItems(links)) michael@0: ) michael@0: ) michael@0: ); michael@0: } michael@0: michael@0: /** michael@0: * Produce a test harness page containing all the test cases michael@0: * below it, recursively. michael@0: */ michael@0: function testListing(metadata, response) michael@0: { michael@0: var links = {}; michael@0: var count = 0; michael@0: if (metadata.queryString.indexOf('manifestFile') == -1) { michael@0: [links, count] = list(metadata.path, michael@0: metadata.getProperty("directory"), michael@0: true); michael@0: } michael@0: var table_class = metadata.queryString.indexOf("hideResultsTable=1") > -1 ? "invisible": ""; michael@0: michael@0: let testname = (metadata.queryString.indexOf("testname=") > -1) michael@0: ? metadata.queryString.match(/testname=([^&]+)/)[1] michael@0: : ""; michael@0: michael@0: dumpn("count: " + count); michael@0: var tests = testname michael@0: ? "['/" + testname + "']" michael@0: : jsonArrayOfTestFiles(links); michael@0: response.write( michael@0: HTML( michael@0: HEAD( michael@0: TITLE("MochiTest | ", metadata.path), michael@0: LINK({rel: "stylesheet", michael@0: type: "text/css", href: "/static/harness.css"} michael@0: ), michael@0: SCRIPT({type: "text/javascript", michael@0: src: "/tests/SimpleTest/LogController.js"}), michael@0: SCRIPT({type: "text/javascript", michael@0: src: "/tests/SimpleTest/MemoryStats.js"}), michael@0: SCRIPT({type: "text/javascript", michael@0: src: "/tests/SimpleTest/TestRunner.js"}), michael@0: SCRIPT({type: "text/javascript", michael@0: src: "/tests/SimpleTest/MozillaLogger.js"}), michael@0: SCRIPT({type: "text/javascript", michael@0: src: "/chunkifyTests.js"}), michael@0: SCRIPT({type: "text/javascript", michael@0: src: "/manifestLibrary.js"}), michael@0: SCRIPT({type: "text/javascript", michael@0: src: "/tests/SimpleTest/setup.js"}), michael@0: SCRIPT({type: "text/javascript"}, michael@0: "window.onload = hookup; gTestList=" + tests + ";" michael@0: ) michael@0: ), michael@0: BODY( michael@0: DIV({class: "container"}, michael@0: H2("--> ", A({href: "#", id: "runtests"}, "Run Tests"), " <--"), michael@0: P({style: "float: right;"}, michael@0: SMALL( michael@0: "Based on the ", michael@0: A({href:"http://www.mochikit.com/"}, "MochiKit"), michael@0: " unit tests." michael@0: ) michael@0: ), michael@0: DIV({class: "status"}, michael@0: H1({id: "indicator"}, "Status"), michael@0: H2({id: "pass"}, "Passed: ", SPAN({id: "pass-count"},"0")), michael@0: H2({id: "fail"}, "Failed: ", SPAN({id: "fail-count"},"0")), michael@0: H2({id: "fail"}, "Todo: ", SPAN({id: "todo-count"},"0")) michael@0: ), michael@0: DIV({class: "clear"}), michael@0: DIV({id: "current-test"}, michael@0: B("Currently Executing: ", michael@0: SPAN({id: "current-test-path"}, "_") michael@0: ) michael@0: ), michael@0: DIV({class: "clear"}), michael@0: DIV({class: "frameholder"}, michael@0: IFRAME({scrolling: "no", id: "testframe", width: "500", height: "300"}) michael@0: ), michael@0: DIV({class: "clear"}), michael@0: DIV({class: "toggle"}, michael@0: A({href: "#", id: "toggleNonTests"}, "Show Non-Tests"), michael@0: BR() michael@0: ), michael@0: michael@0: ( michael@0: displayResults ? michael@0: TABLE({cellpadding: 0, cellspacing: 0, class: table_class, id: "test-table"}, michael@0: TR(TD("Passed"), TD("Failed"), TD("Todo"), TD("Test Files")), michael@0: linksToTableRows(links, 0) michael@0: ) : "" michael@0: ), michael@0: michael@0: BR(), michael@0: TABLE({cellpadding: 0, cellspacing: 0, border: 1, bordercolor: "red", id: "fail-table"} michael@0: ), michael@0: michael@0: DIV({class: "clear"}) michael@0: ) michael@0: ) michael@0: ) michael@0: ); michael@0: } michael@0: michael@0: /** michael@0: * Respond to requests that match a file system directory. michael@0: * Under the tests/ directory, return a test harness page. michael@0: */ michael@0: function defaultDirHandler(metadata, response) michael@0: { michael@0: response.setStatusLine("1.1", 200, "OK"); michael@0: response.setHeader("Content-type", "text/html;charset=utf-8", false); michael@0: try { michael@0: if (metadata.path.indexOf("/tests") != 0) { michael@0: regularListing(metadata, response); michael@0: } else { michael@0: testListing(metadata, response); michael@0: } michael@0: } catch (ex) { michael@0: response.write(ex); michael@0: } michael@0: }