Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* vim:set ts=2 sw=2 sts=2 et: */ |
michael@0 | 3 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 4 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 5 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 6 | |
michael@0 | 7 | // Note that the server script itself already defines Cc, Ci, and Cr for us, |
michael@0 | 8 | // and because they're constants it's not safe to redefine them. Scope leakage |
michael@0 | 9 | // sucks. |
michael@0 | 10 | |
michael@0 | 11 | // Disable automatic network detection, so tests work correctly when |
michael@0 | 12 | // not connected to a network. |
michael@0 | 13 | let (ios = Cc["@mozilla.org/network/io-service;1"] |
michael@0 | 14 | .getService(Ci.nsIIOService2)) { |
michael@0 | 15 | ios.manageOfflineStatus = false; |
michael@0 | 16 | ios.offline = false; |
michael@0 | 17 | } |
michael@0 | 18 | |
michael@0 | 19 | var server; // for use in the shutdown handler, if necessary |
michael@0 | 20 | |
michael@0 | 21 | // |
michael@0 | 22 | // HTML GENERATION |
michael@0 | 23 | // |
michael@0 | 24 | var tags = ['A', 'ABBR', 'ACRONYM', 'ADDRESS', 'APPLET', 'AREA', 'B', 'BASE', |
michael@0 | 25 | 'BASEFONT', 'BDO', 'BIG', 'BLOCKQUOTE', 'BODY', 'BR', 'BUTTON', |
michael@0 | 26 | 'CAPTION', 'CENTER', 'CITE', 'CODE', 'COL', 'COLGROUP', 'DD', |
michael@0 | 27 | 'DEL', 'DFN', 'DIR', 'DIV', 'DL', 'DT', 'EM', 'FIELDSET', 'FONT', |
michael@0 | 28 | 'FORM', 'FRAME', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', |
michael@0 | 29 | 'HEAD', 'HR', 'HTML', 'I', 'IFRAME', 'IMG', 'INPUT', 'INS', |
michael@0 | 30 | 'ISINDEX', 'KBD', 'LABEL', 'LEGEND', 'LI', 'LINK', 'MAP', 'MENU', |
michael@0 | 31 | 'META', 'NOFRAMES', 'NOSCRIPT', 'OBJECT', 'OL', 'OPTGROUP', |
michael@0 | 32 | 'OPTION', 'P', 'PARAM', 'PRE', 'Q', 'S', 'SAMP', 'SCRIPT', |
michael@0 | 33 | 'SELECT', 'SMALL', 'SPAN', 'STRIKE', 'STRONG', 'STYLE', 'SUB', |
michael@0 | 34 | 'SUP', 'TABLE', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD', |
michael@0 | 35 | 'TITLE', 'TR', 'TT', 'U', 'UL', 'VAR']; |
michael@0 | 36 | |
michael@0 | 37 | /** |
michael@0 | 38 | * Below, we'll use makeTagFunc to create a function for each of the |
michael@0 | 39 | * strings in 'tags'. This will allow us to use s-expression like syntax |
michael@0 | 40 | * to create HTML. |
michael@0 | 41 | */ |
michael@0 | 42 | function makeTagFunc(tagName) |
michael@0 | 43 | { |
michael@0 | 44 | return function (attrs /* rest... */) |
michael@0 | 45 | { |
michael@0 | 46 | var startChildren = 0; |
michael@0 | 47 | var response = ""; |
michael@0 | 48 | |
michael@0 | 49 | // write the start tag and attributes |
michael@0 | 50 | response += "<" + tagName; |
michael@0 | 51 | // if attr is an object, write attributes |
michael@0 | 52 | if (attrs && typeof attrs == 'object') { |
michael@0 | 53 | startChildren = 1; |
michael@0 | 54 | for (var [key,value] in attrs) { |
michael@0 | 55 | var val = "" + value; |
michael@0 | 56 | response += " " + key + '="' + val.replace('"','"') + '"'; |
michael@0 | 57 | } |
michael@0 | 58 | } |
michael@0 | 59 | response += ">"; |
michael@0 | 60 | |
michael@0 | 61 | // iterate through the rest of the args |
michael@0 | 62 | for (var i = startChildren; i < arguments.length; i++) { |
michael@0 | 63 | if (typeof arguments[i] == 'function') { |
michael@0 | 64 | response += arguments[i](); |
michael@0 | 65 | } else { |
michael@0 | 66 | response += arguments[i]; |
michael@0 | 67 | } |
michael@0 | 68 | } |
michael@0 | 69 | |
michael@0 | 70 | // write the close tag |
michael@0 | 71 | response += "</" + tagName + ">\n"; |
michael@0 | 72 | return response; |
michael@0 | 73 | } |
michael@0 | 74 | } |
michael@0 | 75 | |
michael@0 | 76 | function makeTags() { |
michael@0 | 77 | // map our global HTML generation functions |
michael@0 | 78 | for each (var tag in tags) { |
michael@0 | 79 | this[tag] = makeTagFunc(tag.toLowerCase()); |
michael@0 | 80 | } |
michael@0 | 81 | } |
michael@0 | 82 | |
michael@0 | 83 | var _quitting = false; |
michael@0 | 84 | |
michael@0 | 85 | /** Quit when all activity has completed. */ |
michael@0 | 86 | function serverStopped() |
michael@0 | 87 | { |
michael@0 | 88 | _quitting = true; |
michael@0 | 89 | } |
michael@0 | 90 | |
michael@0 | 91 | // only run the "main" section if httpd.js was loaded ahead of us |
michael@0 | 92 | if (this["nsHttpServer"]) { |
michael@0 | 93 | // |
michael@0 | 94 | // SCRIPT CODE |
michael@0 | 95 | // |
michael@0 | 96 | runServer(); |
michael@0 | 97 | |
michael@0 | 98 | // We can only have gotten here if the /server/shutdown path was requested. |
michael@0 | 99 | if (_quitting) |
michael@0 | 100 | { |
michael@0 | 101 | dumpn("HTTP server stopped, all pending requests complete"); |
michael@0 | 102 | quit(0); |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | // Impossible as the stop callback should have been called, but to be safe... |
michael@0 | 106 | dumpn("TEST-UNEXPECTED-FAIL | failure to correctly shut down HTTP server"); |
michael@0 | 107 | quit(1); |
michael@0 | 108 | } |
michael@0 | 109 | |
michael@0 | 110 | var serverBasePath; |
michael@0 | 111 | var displayResults = true; |
michael@0 | 112 | |
michael@0 | 113 | // |
michael@0 | 114 | // SERVER SETUP |
michael@0 | 115 | // |
michael@0 | 116 | function runServer() |
michael@0 | 117 | { |
michael@0 | 118 | serverBasePath = __LOCATION__.parent; |
michael@0 | 119 | server = createMochitestServer(serverBasePath); |
michael@0 | 120 | |
michael@0 | 121 | //verify server address |
michael@0 | 122 | //if a.b.c.d or 'localhost' |
michael@0 | 123 | if (typeof(_SERVER_ADDR) != "undefined") { |
michael@0 | 124 | if (_SERVER_ADDR == "localhost") { |
michael@0 | 125 | gServerAddress = _SERVER_ADDR; |
michael@0 | 126 | } else { |
michael@0 | 127 | var quads = _SERVER_ADDR.split('.'); |
michael@0 | 128 | if (quads.length == 4) { |
michael@0 | 129 | var invalid = false; |
michael@0 | 130 | for (var i=0; i < 4; i++) { |
michael@0 | 131 | if (quads[i] < 0 || quads[i] > 255) |
michael@0 | 132 | invalid = true; |
michael@0 | 133 | } |
michael@0 | 134 | if (!invalid) |
michael@0 | 135 | gServerAddress = _SERVER_ADDR; |
michael@0 | 136 | else |
michael@0 | 137 | throw "invalid _SERVER_ADDR, please specify a valid IP Address"; |
michael@0 | 138 | } |
michael@0 | 139 | } |
michael@0 | 140 | } else { |
michael@0 | 141 | throw "please defined _SERVER_ADDR (as an ip address) before running server.js"; |
michael@0 | 142 | } |
michael@0 | 143 | |
michael@0 | 144 | if (typeof(_SERVER_PORT) != "undefined") { |
michael@0 | 145 | if (parseInt(_SERVER_PORT) > 0 && parseInt(_SERVER_PORT) < 65536) |
michael@0 | 146 | SERVER_PORT = _SERVER_PORT; |
michael@0 | 147 | } else { |
michael@0 | 148 | throw "please define _SERVER_PORT (as a port number) before running server.js"; |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | // If DISPLAY_RESULTS is not specified, it defaults to true |
michael@0 | 152 | if (typeof(_DISPLAY_RESULTS) != "undefined") { |
michael@0 | 153 | displayResults = _DISPLAY_RESULTS; |
michael@0 | 154 | } |
michael@0 | 155 | |
michael@0 | 156 | server._start(SERVER_PORT, gServerAddress); |
michael@0 | 157 | |
michael@0 | 158 | // touch a file in the profile directory to indicate we're alive |
michael@0 | 159 | var foStream = Cc["@mozilla.org/network/file-output-stream;1"] |
michael@0 | 160 | .createInstance(Ci.nsIFileOutputStream); |
michael@0 | 161 | var serverAlive = Cc["@mozilla.org/file/local;1"] |
michael@0 | 162 | .createInstance(Ci.nsILocalFile); |
michael@0 | 163 | |
michael@0 | 164 | if (typeof(_PROFILE_PATH) == "undefined") { |
michael@0 | 165 | serverAlive.initWithFile(serverBasePath); |
michael@0 | 166 | serverAlive.append("mochitesttestingprofile"); |
michael@0 | 167 | } else { |
michael@0 | 168 | serverAlive.initWithPath(_PROFILE_PATH); |
michael@0 | 169 | } |
michael@0 | 170 | |
michael@0 | 171 | // If we're running outside of the test harness, there might |
michael@0 | 172 | // not be a test profile directory present |
michael@0 | 173 | if (serverAlive.exists()) { |
michael@0 | 174 | serverAlive.append("server_alive.txt"); |
michael@0 | 175 | foStream.init(serverAlive, |
michael@0 | 176 | 0x02 | 0x08 | 0x20, 436, 0); // write, create, truncate |
michael@0 | 177 | data = "It's alive!"; |
michael@0 | 178 | foStream.write(data, data.length); |
michael@0 | 179 | foStream.close(); |
michael@0 | 180 | } |
michael@0 | 181 | |
michael@0 | 182 | makeTags(); |
michael@0 | 183 | |
michael@0 | 184 | // |
michael@0 | 185 | // The following is threading magic to spin an event loop -- this has to |
michael@0 | 186 | // happen manually in xpcshell for the server to actually work. |
michael@0 | 187 | // |
michael@0 | 188 | var thread = Cc["@mozilla.org/thread-manager;1"] |
michael@0 | 189 | .getService() |
michael@0 | 190 | .currentThread; |
michael@0 | 191 | while (!server.isStopped()) |
michael@0 | 192 | thread.processNextEvent(true); |
michael@0 | 193 | |
michael@0 | 194 | // Server stopped by /server/shutdown handler -- go through pending events |
michael@0 | 195 | // and return. |
michael@0 | 196 | |
michael@0 | 197 | // get rid of any pending requests |
michael@0 | 198 | while (thread.hasPendingEvents()) |
michael@0 | 199 | thread.processNextEvent(true); |
michael@0 | 200 | } |
michael@0 | 201 | |
michael@0 | 202 | /** Creates and returns an HTTP server configured to serve Mochitests. */ |
michael@0 | 203 | function createMochitestServer(serverBasePath) |
michael@0 | 204 | { |
michael@0 | 205 | var server = new nsHttpServer(); |
michael@0 | 206 | |
michael@0 | 207 | server.registerDirectory("/", serverBasePath); |
michael@0 | 208 | server.registerPathHandler("/server/shutdown", serverShutdown); |
michael@0 | 209 | server.registerPathHandler("/server/debug", serverDebug); |
michael@0 | 210 | server.registerContentType("sjs", "sjs"); // .sjs == CGI-like functionality |
michael@0 | 211 | server.registerContentType("jar", "application/x-jar"); |
michael@0 | 212 | server.registerContentType("ogg", "application/ogg"); |
michael@0 | 213 | server.registerContentType("pdf", "application/pdf"); |
michael@0 | 214 | server.registerContentType("ogv", "video/ogg"); |
michael@0 | 215 | server.registerContentType("oga", "audio/ogg"); |
michael@0 | 216 | server.registerContentType("opus", "audio/ogg; codecs=opus"); |
michael@0 | 217 | server.registerContentType("dat", "text/plain; charset=utf-8"); |
michael@0 | 218 | server.registerContentType("frag", "text/plain"); // .frag == WebGL fragment shader |
michael@0 | 219 | server.registerContentType("vert", "text/plain"); // .vert == WebGL vertex shader |
michael@0 | 220 | server.setIndexHandler(defaultDirHandler); |
michael@0 | 221 | |
michael@0 | 222 | var serverRoot = |
michael@0 | 223 | { |
michael@0 | 224 | getFile: function getFile(path) |
michael@0 | 225 | { |
michael@0 | 226 | var file = serverBasePath.clone().QueryInterface(Ci.nsILocalFile); |
michael@0 | 227 | path.split("/").forEach(function(p) { |
michael@0 | 228 | file.appendRelativePath(p); |
michael@0 | 229 | }); |
michael@0 | 230 | return file; |
michael@0 | 231 | }, |
michael@0 | 232 | QueryInterface: function(aIID) { return this; } |
michael@0 | 233 | }; |
michael@0 | 234 | |
michael@0 | 235 | server.setObjectState("SERVER_ROOT", serverRoot); |
michael@0 | 236 | |
michael@0 | 237 | processLocations(server); |
michael@0 | 238 | |
michael@0 | 239 | return server; |
michael@0 | 240 | } |
michael@0 | 241 | |
michael@0 | 242 | /** |
michael@0 | 243 | * Notifies the HTTP server about all the locations at which it might receive |
michael@0 | 244 | * requests, so that it can properly respond to requests on any of the hosts it |
michael@0 | 245 | * serves. |
michael@0 | 246 | */ |
michael@0 | 247 | function processLocations(server) |
michael@0 | 248 | { |
michael@0 | 249 | var serverLocations = serverBasePath.clone(); |
michael@0 | 250 | serverLocations.append("server-locations.txt"); |
michael@0 | 251 | |
michael@0 | 252 | const PR_RDONLY = 0x01; |
michael@0 | 253 | var fis = new FileInputStream(serverLocations, PR_RDONLY, 292 /* 0444 */, |
michael@0 | 254 | Ci.nsIFileInputStream.CLOSE_ON_EOF); |
michael@0 | 255 | |
michael@0 | 256 | var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0); |
michael@0 | 257 | lis.QueryInterface(Ci.nsIUnicharLineInputStream); |
michael@0 | 258 | |
michael@0 | 259 | const LINE_REGEXP = |
michael@0 | 260 | new RegExp("^([a-z][-a-z0-9+.]*)" + |
michael@0 | 261 | "://" + |
michael@0 | 262 | "(" + |
michael@0 | 263 | "\\d+\\.\\d+\\.\\d+\\.\\d+" + |
michael@0 | 264 | "|" + |
michael@0 | 265 | "(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\\.)*" + |
michael@0 | 266 | "[a-z](?:[-a-z0-9]*[a-z0-9])?" + |
michael@0 | 267 | ")" + |
michael@0 | 268 | ":" + |
michael@0 | 269 | "(\\d+)" + |
michael@0 | 270 | "(?:" + |
michael@0 | 271 | "\\s+" + |
michael@0 | 272 | "(\\S+(?:,\\S+)*)" + |
michael@0 | 273 | ")?$"); |
michael@0 | 274 | |
michael@0 | 275 | var line = {}; |
michael@0 | 276 | var lineno = 0; |
michael@0 | 277 | var seenPrimary = false; |
michael@0 | 278 | do |
michael@0 | 279 | { |
michael@0 | 280 | var more = lis.readLine(line); |
michael@0 | 281 | lineno++; |
michael@0 | 282 | |
michael@0 | 283 | var lineValue = line.value; |
michael@0 | 284 | if (lineValue.charAt(0) == "#" || lineValue == "") |
michael@0 | 285 | continue; |
michael@0 | 286 | |
michael@0 | 287 | var match = LINE_REGEXP.exec(lineValue); |
michael@0 | 288 | if (!match) |
michael@0 | 289 | throw "Syntax error in server-locations.txt, line " + lineno; |
michael@0 | 290 | |
michael@0 | 291 | var [, scheme, host, port, options] = match; |
michael@0 | 292 | if (options) |
michael@0 | 293 | { |
michael@0 | 294 | if (options.split(",").indexOf("primary") >= 0) |
michael@0 | 295 | { |
michael@0 | 296 | if (seenPrimary) |
michael@0 | 297 | { |
michael@0 | 298 | throw "Multiple primary locations in server-locations.txt, " + |
michael@0 | 299 | "line " + lineno; |
michael@0 | 300 | } |
michael@0 | 301 | |
michael@0 | 302 | server.identity.setPrimary(scheme, host, port); |
michael@0 | 303 | seenPrimary = true; |
michael@0 | 304 | continue; |
michael@0 | 305 | } |
michael@0 | 306 | } |
michael@0 | 307 | |
michael@0 | 308 | server.identity.add(scheme, host, port); |
michael@0 | 309 | } |
michael@0 | 310 | while (more); |
michael@0 | 311 | } |
michael@0 | 312 | |
michael@0 | 313 | |
michael@0 | 314 | // PATH HANDLERS |
michael@0 | 315 | |
michael@0 | 316 | // /server/shutdown |
michael@0 | 317 | function serverShutdown(metadata, response) |
michael@0 | 318 | { |
michael@0 | 319 | response.setStatusLine("1.1", 200, "OK"); |
michael@0 | 320 | response.setHeader("Content-type", "text/plain", false); |
michael@0 | 321 | |
michael@0 | 322 | var body = "Server shut down."; |
michael@0 | 323 | response.bodyOutputStream.write(body, body.length); |
michael@0 | 324 | |
michael@0 | 325 | dumpn("Server shutting down now..."); |
michael@0 | 326 | server.stop(serverStopped); |
michael@0 | 327 | } |
michael@0 | 328 | |
michael@0 | 329 | // /server/debug?[012] |
michael@0 | 330 | function serverDebug(metadata, response) |
michael@0 | 331 | { |
michael@0 | 332 | response.setStatusLine(metadata.httpVersion, 400, "Bad debugging level"); |
michael@0 | 333 | if (metadata.queryString.length !== 1) |
michael@0 | 334 | return; |
michael@0 | 335 | |
michael@0 | 336 | var mode; |
michael@0 | 337 | if (metadata.queryString === "0") { |
michael@0 | 338 | // do this now so it gets logged with the old mode |
michael@0 | 339 | dumpn("Server debug logs disabled."); |
michael@0 | 340 | DEBUG = false; |
michael@0 | 341 | DEBUG_TIMESTAMP = false; |
michael@0 | 342 | mode = "disabled"; |
michael@0 | 343 | } else if (metadata.queryString === "1") { |
michael@0 | 344 | DEBUG = true; |
michael@0 | 345 | DEBUG_TIMESTAMP = false; |
michael@0 | 346 | mode = "enabled"; |
michael@0 | 347 | } else if (metadata.queryString === "2") { |
michael@0 | 348 | DEBUG = true; |
michael@0 | 349 | DEBUG_TIMESTAMP = true; |
michael@0 | 350 | mode = "enabled, with timestamps"; |
michael@0 | 351 | } else { |
michael@0 | 352 | return; |
michael@0 | 353 | } |
michael@0 | 354 | |
michael@0 | 355 | response.setStatusLine(metadata.httpVersion, 200, "OK"); |
michael@0 | 356 | response.setHeader("Content-type", "text/plain", false); |
michael@0 | 357 | var body = "Server debug logs " + mode + "."; |
michael@0 | 358 | response.bodyOutputStream.write(body, body.length); |
michael@0 | 359 | dumpn(body); |
michael@0 | 360 | } |
michael@0 | 361 | |
michael@0 | 362 | // |
michael@0 | 363 | // DIRECTORY LISTINGS |
michael@0 | 364 | // |
michael@0 | 365 | |
michael@0 | 366 | /** |
michael@0 | 367 | * Creates a generator that iterates over the contents of |
michael@0 | 368 | * an nsIFile directory. |
michael@0 | 369 | */ |
michael@0 | 370 | function dirIter(dir) |
michael@0 | 371 | { |
michael@0 | 372 | var en = dir.directoryEntries; |
michael@0 | 373 | while (en.hasMoreElements()) { |
michael@0 | 374 | var file = en.getNext(); |
michael@0 | 375 | yield file.QueryInterface(Ci.nsILocalFile); |
michael@0 | 376 | } |
michael@0 | 377 | } |
michael@0 | 378 | |
michael@0 | 379 | /** |
michael@0 | 380 | * Builds an optionally nested object containing links to the |
michael@0 | 381 | * files and directories within dir. |
michael@0 | 382 | */ |
michael@0 | 383 | function list(requestPath, directory, recurse) |
michael@0 | 384 | { |
michael@0 | 385 | var count = 0; |
michael@0 | 386 | var path = requestPath; |
michael@0 | 387 | if (path.charAt(path.length - 1) != "/") { |
michael@0 | 388 | path += "/"; |
michael@0 | 389 | } |
michael@0 | 390 | |
michael@0 | 391 | var dir = directory.QueryInterface(Ci.nsIFile); |
michael@0 | 392 | var links = {}; |
michael@0 | 393 | |
michael@0 | 394 | // The SimpleTest directory is hidden |
michael@0 | 395 | var files = [file for (file in dirIter(dir)) |
michael@0 | 396 | if (file.exists() && file.path.indexOf("SimpleTest") == -1)]; |
michael@0 | 397 | |
michael@0 | 398 | // Sort files by name, so that tests can be run in a pre-defined order inside |
michael@0 | 399 | // a given directory (see bug 384823) |
michael@0 | 400 | function leafNameComparator(first, second) { |
michael@0 | 401 | if (first.leafName < second.leafName) |
michael@0 | 402 | return -1; |
michael@0 | 403 | if (first.leafName > second.leafName) |
michael@0 | 404 | return 1; |
michael@0 | 405 | return 0; |
michael@0 | 406 | } |
michael@0 | 407 | files.sort(leafNameComparator); |
michael@0 | 408 | |
michael@0 | 409 | count = files.length; |
michael@0 | 410 | for each (var file in files) { |
michael@0 | 411 | var key = path + file.leafName; |
michael@0 | 412 | var childCount = 0; |
michael@0 | 413 | if (file.isDirectory()) { |
michael@0 | 414 | key += "/"; |
michael@0 | 415 | } |
michael@0 | 416 | if (recurse && file.isDirectory()) { |
michael@0 | 417 | [links[key], childCount] = list(key, file, recurse); |
michael@0 | 418 | count += childCount; |
michael@0 | 419 | } else { |
michael@0 | 420 | if (file.leafName.charAt(0) != '.') { |
michael@0 | 421 | links[key] = true; |
michael@0 | 422 | } |
michael@0 | 423 | } |
michael@0 | 424 | } |
michael@0 | 425 | |
michael@0 | 426 | return [links, count]; |
michael@0 | 427 | } |
michael@0 | 428 | |
michael@0 | 429 | /** |
michael@0 | 430 | * Heuristic function that determines whether a given path |
michael@0 | 431 | * is a test case to be executed in the harness, or just |
michael@0 | 432 | * a supporting file. |
michael@0 | 433 | */ |
michael@0 | 434 | function isTest(filename, pattern) |
michael@0 | 435 | { |
michael@0 | 436 | if (pattern) |
michael@0 | 437 | return pattern.test(filename); |
michael@0 | 438 | |
michael@0 | 439 | // File name is a URL style path to a test file, make sure that we check for |
michael@0 | 440 | // tests that start with the appropriate prefix. |
michael@0 | 441 | var testPrefix = typeof(_TEST_PREFIX) == "string" ? _TEST_PREFIX : "test_"; |
michael@0 | 442 | var testPattern = new RegExp("^" + testPrefix); |
michael@0 | 443 | |
michael@0 | 444 | pathPieces = filename.split('/'); |
michael@0 | 445 | |
michael@0 | 446 | return testPattern.test(pathPieces[pathPieces.length - 1]) && |
michael@0 | 447 | filename.indexOf(".js") == -1 && |
michael@0 | 448 | filename.indexOf(".css") == -1 && |
michael@0 | 449 | !/\^headers\^$/.test(filename); |
michael@0 | 450 | } |
michael@0 | 451 | |
michael@0 | 452 | /** |
michael@0 | 453 | * Transform nested hashtables of paths to nested HTML lists. |
michael@0 | 454 | */ |
michael@0 | 455 | function linksToListItems(links) |
michael@0 | 456 | { |
michael@0 | 457 | var response = ""; |
michael@0 | 458 | var children = ""; |
michael@0 | 459 | for (var [link, value] in links) { |
michael@0 | 460 | var classVal = (!isTest(link) && !(value instanceof Object)) |
michael@0 | 461 | ? "non-test invisible" |
michael@0 | 462 | : "test"; |
michael@0 | 463 | if (value instanceof Object) { |
michael@0 | 464 | children = UL({class: "testdir"}, linksToListItems(value)); |
michael@0 | 465 | } else { |
michael@0 | 466 | children = ""; |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | var bug_title = link.match(/test_bug\S+/); |
michael@0 | 470 | var bug_num = null; |
michael@0 | 471 | if (bug_title != null) { |
michael@0 | 472 | bug_num = bug_title[0].match(/\d+/); |
michael@0 | 473 | } |
michael@0 | 474 | |
michael@0 | 475 | if ((bug_title == null) || (bug_num == null)) { |
michael@0 | 476 | response += LI({class: classVal}, A({href: link}, link), children); |
michael@0 | 477 | } else { |
michael@0 | 478 | var bug_url = "https://bugzilla.mozilla.org/show_bug.cgi?id="+bug_num; |
michael@0 | 479 | response += LI({class: classVal}, A({href: link}, link), " - ", A({href: bug_url}, "Bug "+bug_num), children); |
michael@0 | 480 | } |
michael@0 | 481 | |
michael@0 | 482 | } |
michael@0 | 483 | return response; |
michael@0 | 484 | } |
michael@0 | 485 | |
michael@0 | 486 | /** |
michael@0 | 487 | * Transform nested hashtables of paths to a flat table rows. |
michael@0 | 488 | */ |
michael@0 | 489 | function linksToTableRows(links, recursionLevel) |
michael@0 | 490 | { |
michael@0 | 491 | var response = ""; |
michael@0 | 492 | for (var [link, value] in links) { |
michael@0 | 493 | var classVal = (!isTest(link) && !(value instanceof Object)) |
michael@0 | 494 | ? "non-test invisible" |
michael@0 | 495 | : ""; |
michael@0 | 496 | |
michael@0 | 497 | spacer = "padding-left: " + (10 * recursionLevel) + "px"; |
michael@0 | 498 | |
michael@0 | 499 | if (value instanceof Object) { |
michael@0 | 500 | response += TR({class: "dir", id: "tr-" + link }, |
michael@0 | 501 | TD({colspan: "3"}, " "), |
michael@0 | 502 | TD({style: spacer}, |
michael@0 | 503 | A({href: link}, link))); |
michael@0 | 504 | response += linksToTableRows(value, recursionLevel + 1); |
michael@0 | 505 | } else { |
michael@0 | 506 | var bug_title = link.match(/test_bug\S+/); |
michael@0 | 507 | var bug_num = null; |
michael@0 | 508 | if (bug_title != null) { |
michael@0 | 509 | bug_num = bug_title[0].match(/\d+/); |
michael@0 | 510 | } |
michael@0 | 511 | if ((bug_title == null) || (bug_num == null)) { |
michael@0 | 512 | response += TR({class: classVal, id: "tr-" + link }, |
michael@0 | 513 | TD("0"), |
michael@0 | 514 | TD("0"), |
michael@0 | 515 | TD("0"), |
michael@0 | 516 | TD({style: spacer}, |
michael@0 | 517 | A({href: link}, link))); |
michael@0 | 518 | } else { |
michael@0 | 519 | var bug_url = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + bug_num; |
michael@0 | 520 | response += TR({class: classVal, id: "tr-" + link }, |
michael@0 | 521 | TD("0"), |
michael@0 | 522 | TD("0"), |
michael@0 | 523 | TD("0"), |
michael@0 | 524 | TD({style: spacer}, |
michael@0 | 525 | A({href: link}, link), " - ", |
michael@0 | 526 | A({href: bug_url}, "Bug " + bug_num))); |
michael@0 | 527 | } |
michael@0 | 528 | } |
michael@0 | 529 | } |
michael@0 | 530 | return response; |
michael@0 | 531 | } |
michael@0 | 532 | |
michael@0 | 533 | function arrayOfTestFiles(linkArray, fileArray, testPattern) { |
michael@0 | 534 | for (var [link, value] in Iterator(linkArray)) { |
michael@0 | 535 | if (value instanceof Object) { |
michael@0 | 536 | arrayOfTestFiles(value, fileArray, testPattern); |
michael@0 | 537 | } else if (isTest(link, testPattern)) { |
michael@0 | 538 | fileArray.push(link) |
michael@0 | 539 | } |
michael@0 | 540 | } |
michael@0 | 541 | } |
michael@0 | 542 | /** |
michael@0 | 543 | * Produce a flat array of test file paths to be executed in the harness. |
michael@0 | 544 | */ |
michael@0 | 545 | function jsonArrayOfTestFiles(links) |
michael@0 | 546 | { |
michael@0 | 547 | var testFiles = []; |
michael@0 | 548 | arrayOfTestFiles(links, testFiles); |
michael@0 | 549 | testFiles = ['"' + file + '"' for each(file in testFiles)]; |
michael@0 | 550 | return "[" + testFiles.join(",\n") + "]"; |
michael@0 | 551 | } |
michael@0 | 552 | |
michael@0 | 553 | /** |
michael@0 | 554 | * Produce a normal directory listing. |
michael@0 | 555 | */ |
michael@0 | 556 | function regularListing(metadata, response) |
michael@0 | 557 | { |
michael@0 | 558 | var [links, count] = list(metadata.path, |
michael@0 | 559 | metadata.getProperty("directory"), |
michael@0 | 560 | false); |
michael@0 | 561 | response.write( |
michael@0 | 562 | HTML( |
michael@0 | 563 | HEAD( |
michael@0 | 564 | TITLE("mochitest index ", metadata.path) |
michael@0 | 565 | ), |
michael@0 | 566 | BODY( |
michael@0 | 567 | BR(), |
michael@0 | 568 | A({href: ".."}, "Up a level"), |
michael@0 | 569 | UL(linksToListItems(links)) |
michael@0 | 570 | ) |
michael@0 | 571 | ) |
michael@0 | 572 | ); |
michael@0 | 573 | } |
michael@0 | 574 | |
michael@0 | 575 | /** |
michael@0 | 576 | * Produce a test harness page containing all the test cases |
michael@0 | 577 | * below it, recursively. |
michael@0 | 578 | */ |
michael@0 | 579 | function testListing(metadata, response) |
michael@0 | 580 | { |
michael@0 | 581 | var links = {}; |
michael@0 | 582 | var count = 0; |
michael@0 | 583 | if (metadata.queryString.indexOf('manifestFile') == -1) { |
michael@0 | 584 | [links, count] = list(metadata.path, |
michael@0 | 585 | metadata.getProperty("directory"), |
michael@0 | 586 | true); |
michael@0 | 587 | } |
michael@0 | 588 | var table_class = metadata.queryString.indexOf("hideResultsTable=1") > -1 ? "invisible": ""; |
michael@0 | 589 | |
michael@0 | 590 | let testname = (metadata.queryString.indexOf("testname=") > -1) |
michael@0 | 591 | ? metadata.queryString.match(/testname=([^&]+)/)[1] |
michael@0 | 592 | : ""; |
michael@0 | 593 | |
michael@0 | 594 | dumpn("count: " + count); |
michael@0 | 595 | var tests = testname |
michael@0 | 596 | ? "['/" + testname + "']" |
michael@0 | 597 | : jsonArrayOfTestFiles(links); |
michael@0 | 598 | response.write( |
michael@0 | 599 | HTML( |
michael@0 | 600 | HEAD( |
michael@0 | 601 | TITLE("MochiTest | ", metadata.path), |
michael@0 | 602 | LINK({rel: "stylesheet", |
michael@0 | 603 | type: "text/css", href: "/static/harness.css"} |
michael@0 | 604 | ), |
michael@0 | 605 | SCRIPT({type: "text/javascript", |
michael@0 | 606 | src: "/tests/SimpleTest/LogController.js"}), |
michael@0 | 607 | SCRIPT({type: "text/javascript", |
michael@0 | 608 | src: "/tests/SimpleTest/MemoryStats.js"}), |
michael@0 | 609 | SCRIPT({type: "text/javascript", |
michael@0 | 610 | src: "/tests/SimpleTest/TestRunner.js"}), |
michael@0 | 611 | SCRIPT({type: "text/javascript", |
michael@0 | 612 | src: "/tests/SimpleTest/MozillaLogger.js"}), |
michael@0 | 613 | SCRIPT({type: "text/javascript", |
michael@0 | 614 | src: "/chunkifyTests.js"}), |
michael@0 | 615 | SCRIPT({type: "text/javascript", |
michael@0 | 616 | src: "/manifestLibrary.js"}), |
michael@0 | 617 | SCRIPT({type: "text/javascript", |
michael@0 | 618 | src: "/tests/SimpleTest/setup.js"}), |
michael@0 | 619 | SCRIPT({type: "text/javascript"}, |
michael@0 | 620 | "window.onload = hookup; gTestList=" + tests + ";" |
michael@0 | 621 | ) |
michael@0 | 622 | ), |
michael@0 | 623 | BODY( |
michael@0 | 624 | DIV({class: "container"}, |
michael@0 | 625 | H2("--> ", A({href: "#", id: "runtests"}, "Run Tests"), " <--"), |
michael@0 | 626 | P({style: "float: right;"}, |
michael@0 | 627 | SMALL( |
michael@0 | 628 | "Based on the ", |
michael@0 | 629 | A({href:"http://www.mochikit.com/"}, "MochiKit"), |
michael@0 | 630 | " unit tests." |
michael@0 | 631 | ) |
michael@0 | 632 | ), |
michael@0 | 633 | DIV({class: "status"}, |
michael@0 | 634 | H1({id: "indicator"}, "Status"), |
michael@0 | 635 | H2({id: "pass"}, "Passed: ", SPAN({id: "pass-count"},"0")), |
michael@0 | 636 | H2({id: "fail"}, "Failed: ", SPAN({id: "fail-count"},"0")), |
michael@0 | 637 | H2({id: "fail"}, "Todo: ", SPAN({id: "todo-count"},"0")) |
michael@0 | 638 | ), |
michael@0 | 639 | DIV({class: "clear"}), |
michael@0 | 640 | DIV({id: "current-test"}, |
michael@0 | 641 | B("Currently Executing: ", |
michael@0 | 642 | SPAN({id: "current-test-path"}, "_") |
michael@0 | 643 | ) |
michael@0 | 644 | ), |
michael@0 | 645 | DIV({class: "clear"}), |
michael@0 | 646 | DIV({class: "frameholder"}, |
michael@0 | 647 | IFRAME({scrolling: "no", id: "testframe", width: "500", height: "300"}) |
michael@0 | 648 | ), |
michael@0 | 649 | DIV({class: "clear"}), |
michael@0 | 650 | DIV({class: "toggle"}, |
michael@0 | 651 | A({href: "#", id: "toggleNonTests"}, "Show Non-Tests"), |
michael@0 | 652 | BR() |
michael@0 | 653 | ), |
michael@0 | 654 | |
michael@0 | 655 | ( |
michael@0 | 656 | displayResults ? |
michael@0 | 657 | TABLE({cellpadding: 0, cellspacing: 0, class: table_class, id: "test-table"}, |
michael@0 | 658 | TR(TD("Passed"), TD("Failed"), TD("Todo"), TD("Test Files")), |
michael@0 | 659 | linksToTableRows(links, 0) |
michael@0 | 660 | ) : "" |
michael@0 | 661 | ), |
michael@0 | 662 | |
michael@0 | 663 | BR(), |
michael@0 | 664 | TABLE({cellpadding: 0, cellspacing: 0, border: 1, bordercolor: "red", id: "fail-table"} |
michael@0 | 665 | ), |
michael@0 | 666 | |
michael@0 | 667 | DIV({class: "clear"}) |
michael@0 | 668 | ) |
michael@0 | 669 | ) |
michael@0 | 670 | ) |
michael@0 | 671 | ); |
michael@0 | 672 | } |
michael@0 | 673 | |
michael@0 | 674 | /** |
michael@0 | 675 | * Respond to requests that match a file system directory. |
michael@0 | 676 | * Under the tests/ directory, return a test harness page. |
michael@0 | 677 | */ |
michael@0 | 678 | function defaultDirHandler(metadata, response) |
michael@0 | 679 | { |
michael@0 | 680 | response.setStatusLine("1.1", 200, "OK"); |
michael@0 | 681 | response.setHeader("Content-type", "text/html;charset=utf-8", false); |
michael@0 | 682 | try { |
michael@0 | 683 | if (metadata.path.indexOf("/tests") != 0) { |
michael@0 | 684 | regularListing(metadata, response); |
michael@0 | 685 | } else { |
michael@0 | 686 | testListing(metadata, response); |
michael@0 | 687 | } |
michael@0 | 688 | } catch (ex) { |
michael@0 | 689 | response.write(ex); |
michael@0 | 690 | } |
michael@0 | 691 | } |