testing/mochitest/server.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/mochitest/server.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,691 @@
     1.4 +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* vim:set ts=2 sw=2 sts=2 et: */
     1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +// Note that the server script itself already defines Cc, Ci, and Cr for us,
    1.11 +// and because they're constants it's not safe to redefine them.  Scope leakage
    1.12 +// sucks.
    1.13 +
    1.14 +// Disable automatic network detection, so tests work correctly when
    1.15 +// not connected to a network.
    1.16 +let (ios = Cc["@mozilla.org/network/io-service;1"]
    1.17 +           .getService(Ci.nsIIOService2)) {
    1.18 +  ios.manageOfflineStatus = false;
    1.19 +  ios.offline = false;
    1.20 +}
    1.21 +
    1.22 +var server; // for use in the shutdown handler, if necessary
    1.23 +
    1.24 +//
    1.25 +// HTML GENERATION
    1.26 +//
    1.27 +var tags = ['A', 'ABBR', 'ACRONYM', 'ADDRESS', 'APPLET', 'AREA', 'B', 'BASE',
    1.28 +            'BASEFONT', 'BDO', 'BIG', 'BLOCKQUOTE', 'BODY', 'BR', 'BUTTON',
    1.29 +            'CAPTION', 'CENTER', 'CITE', 'CODE', 'COL', 'COLGROUP', 'DD',
    1.30 +            'DEL', 'DFN', 'DIR', 'DIV', 'DL', 'DT', 'EM', 'FIELDSET', 'FONT',
    1.31 +            'FORM', 'FRAME', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
    1.32 +            'HEAD', 'HR', 'HTML', 'I', 'IFRAME', 'IMG', 'INPUT', 'INS',
    1.33 +            'ISINDEX', 'KBD', 'LABEL', 'LEGEND', 'LI', 'LINK', 'MAP', 'MENU',
    1.34 +            'META', 'NOFRAMES', 'NOSCRIPT', 'OBJECT', 'OL', 'OPTGROUP',
    1.35 +            'OPTION', 'P', 'PARAM', 'PRE', 'Q', 'S', 'SAMP', 'SCRIPT',
    1.36 +            'SELECT', 'SMALL', 'SPAN', 'STRIKE', 'STRONG', 'STYLE', 'SUB',
    1.37 +            'SUP', 'TABLE', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD',
    1.38 +            'TITLE', 'TR', 'TT', 'U', 'UL', 'VAR'];
    1.39 +
    1.40 +/**
    1.41 + * Below, we'll use makeTagFunc to create a function for each of the
    1.42 + * strings in 'tags'. This will allow us to use s-expression like syntax
    1.43 + * to create HTML.
    1.44 + */
    1.45 +function makeTagFunc(tagName)
    1.46 +{
    1.47 +  return function (attrs /* rest... */)
    1.48 +  {
    1.49 +    var startChildren = 0;
    1.50 +    var response = "";
    1.51 +    
    1.52 +    // write the start tag and attributes
    1.53 +    response += "<" + tagName;
    1.54 +    // if attr is an object, write attributes
    1.55 +    if (attrs && typeof attrs == 'object') {
    1.56 +      startChildren = 1;
    1.57 +      for (var [key,value] in attrs) {
    1.58 +        var val = "" + value;
    1.59 +        response += " " + key + '="' + val.replace('"','&quot;') + '"';
    1.60 +      }
    1.61 +    }
    1.62 +    response += ">";
    1.63 +    
    1.64 +    // iterate through the rest of the args
    1.65 +    for (var i = startChildren; i < arguments.length; i++) {
    1.66 +      if (typeof arguments[i] == 'function') {
    1.67 +        response += arguments[i]();
    1.68 +      } else {
    1.69 +        response += arguments[i];
    1.70 +      }
    1.71 +    }
    1.72 +
    1.73 +    // write the close tag
    1.74 +    response += "</" + tagName + ">\n";
    1.75 +    return response;
    1.76 +  }
    1.77 +}
    1.78 +
    1.79 +function makeTags() {
    1.80 +  // map our global HTML generation functions
    1.81 +  for each (var tag in tags) {
    1.82 +      this[tag] = makeTagFunc(tag.toLowerCase());
    1.83 +  }
    1.84 +}
    1.85 +
    1.86 +var _quitting = false;
    1.87 +
    1.88 +/** Quit when all activity has completed. */
    1.89 +function serverStopped()
    1.90 +{
    1.91 +  _quitting = true;
    1.92 +}
    1.93 +
    1.94 +// only run the "main" section if httpd.js was loaded ahead of us
    1.95 +if (this["nsHttpServer"]) {
    1.96 +  //
    1.97 +  // SCRIPT CODE
    1.98 +  //
    1.99 +  runServer();
   1.100 +
   1.101 +  // We can only have gotten here if the /server/shutdown path was requested.
   1.102 +  if (_quitting)
   1.103 +  {
   1.104 +    dumpn("HTTP server stopped, all pending requests complete");
   1.105 +    quit(0);
   1.106 +  }
   1.107 +
   1.108 +  // Impossible as the stop callback should have been called, but to be safe...
   1.109 +  dumpn("TEST-UNEXPECTED-FAIL | failure to correctly shut down HTTP server");
   1.110 +  quit(1);
   1.111 +}
   1.112 +
   1.113 +var serverBasePath;
   1.114 +var displayResults = true;
   1.115 +
   1.116 +//
   1.117 +// SERVER SETUP
   1.118 +//
   1.119 +function runServer()
   1.120 +{
   1.121 +  serverBasePath = __LOCATION__.parent;
   1.122 +  server = createMochitestServer(serverBasePath);
   1.123 +
   1.124 +  //verify server address
   1.125 +  //if a.b.c.d or 'localhost'
   1.126 +  if (typeof(_SERVER_ADDR) != "undefined") {
   1.127 +    if (_SERVER_ADDR == "localhost") {
   1.128 +      gServerAddress = _SERVER_ADDR;      
   1.129 +    } else {
   1.130 +      var quads = _SERVER_ADDR.split('.');
   1.131 +      if (quads.length == 4) {
   1.132 +        var invalid = false;
   1.133 +        for (var i=0; i < 4; i++) {
   1.134 +          if (quads[i] < 0 || quads[i] > 255)
   1.135 +            invalid = true;
   1.136 +        }
   1.137 +        if (!invalid)
   1.138 +          gServerAddress = _SERVER_ADDR;
   1.139 +        else
   1.140 +          throw "invalid _SERVER_ADDR, please specify a valid IP Address";
   1.141 +      }
   1.142 +    }
   1.143 +  } else {
   1.144 +    throw "please defined _SERVER_ADDR (as an ip address) before running server.js";
   1.145 +  }
   1.146 +
   1.147 +  if (typeof(_SERVER_PORT) != "undefined") {
   1.148 +    if (parseInt(_SERVER_PORT) > 0 && parseInt(_SERVER_PORT) < 65536)
   1.149 +      SERVER_PORT = _SERVER_PORT;
   1.150 +  } else {
   1.151 +    throw "please define _SERVER_PORT (as a port number) before running server.js";
   1.152 +  }
   1.153 +
   1.154 +  // If DISPLAY_RESULTS is not specified, it defaults to true
   1.155 +  if (typeof(_DISPLAY_RESULTS) != "undefined") {
   1.156 +    displayResults = _DISPLAY_RESULTS;
   1.157 +  }
   1.158 +
   1.159 +  server._start(SERVER_PORT, gServerAddress);
   1.160 +
   1.161 +  // touch a file in the profile directory to indicate we're alive
   1.162 +  var foStream = Cc["@mozilla.org/network/file-output-stream;1"]
   1.163 +                   .createInstance(Ci.nsIFileOutputStream);
   1.164 +  var serverAlive = Cc["@mozilla.org/file/local;1"]
   1.165 +                      .createInstance(Ci.nsILocalFile);
   1.166 +
   1.167 +  if (typeof(_PROFILE_PATH) == "undefined") {
   1.168 +    serverAlive.initWithFile(serverBasePath);
   1.169 +    serverAlive.append("mochitesttestingprofile");
   1.170 +  } else {
   1.171 +    serverAlive.initWithPath(_PROFILE_PATH);
   1.172 +  }
   1.173 +
   1.174 +  // If we're running outside of the test harness, there might
   1.175 +  // not be a test profile directory present
   1.176 +  if (serverAlive.exists()) {
   1.177 +    serverAlive.append("server_alive.txt");
   1.178 +    foStream.init(serverAlive,
   1.179 +                  0x02 | 0x08 | 0x20, 436, 0); // write, create, truncate
   1.180 +    data = "It's alive!";
   1.181 +    foStream.write(data, data.length);
   1.182 +    foStream.close();
   1.183 +  }
   1.184 +
   1.185 +  makeTags();
   1.186 +
   1.187 +  //
   1.188 +  // The following is threading magic to spin an event loop -- this has to
   1.189 +  // happen manually in xpcshell for the server to actually work.
   1.190 +  //
   1.191 +  var thread = Cc["@mozilla.org/thread-manager;1"]
   1.192 +                 .getService()
   1.193 +                 .currentThread;
   1.194 +  while (!server.isStopped())
   1.195 +    thread.processNextEvent(true);
   1.196 +
   1.197 +  // Server stopped by /server/shutdown handler -- go through pending events
   1.198 +  // and return.
   1.199 +
   1.200 +  // get rid of any pending requests
   1.201 +  while (thread.hasPendingEvents())
   1.202 +    thread.processNextEvent(true);
   1.203 +}
   1.204 +
   1.205 +/** Creates and returns an HTTP server configured to serve Mochitests. */
   1.206 +function createMochitestServer(serverBasePath)
   1.207 +{
   1.208 +  var server = new nsHttpServer();
   1.209 +
   1.210 +  server.registerDirectory("/", serverBasePath);
   1.211 +  server.registerPathHandler("/server/shutdown", serverShutdown);
   1.212 +  server.registerPathHandler("/server/debug", serverDebug);
   1.213 +  server.registerContentType("sjs", "sjs"); // .sjs == CGI-like functionality
   1.214 +  server.registerContentType("jar", "application/x-jar");
   1.215 +  server.registerContentType("ogg", "application/ogg");
   1.216 +  server.registerContentType("pdf", "application/pdf");
   1.217 +  server.registerContentType("ogv", "video/ogg");
   1.218 +  server.registerContentType("oga", "audio/ogg");
   1.219 +  server.registerContentType("opus", "audio/ogg; codecs=opus");
   1.220 +  server.registerContentType("dat", "text/plain; charset=utf-8");
   1.221 +  server.registerContentType("frag", "text/plain"); // .frag == WebGL fragment shader
   1.222 +  server.registerContentType("vert", "text/plain"); // .vert == WebGL vertex shader
   1.223 +  server.setIndexHandler(defaultDirHandler);
   1.224 +
   1.225 +  var serverRoot =
   1.226 +    {
   1.227 +      getFile: function getFile(path)
   1.228 +      {
   1.229 +        var file = serverBasePath.clone().QueryInterface(Ci.nsILocalFile);
   1.230 +        path.split("/").forEach(function(p) {
   1.231 +          file.appendRelativePath(p);
   1.232 +        });
   1.233 +        return file;
   1.234 +      },
   1.235 +      QueryInterface: function(aIID) { return this; }
   1.236 +    };
   1.237 +
   1.238 +  server.setObjectState("SERVER_ROOT", serverRoot);
   1.239 +
   1.240 +  processLocations(server);
   1.241 +
   1.242 +  return server;
   1.243 +}
   1.244 +
   1.245 +/**
   1.246 + * Notifies the HTTP server about all the locations at which it might receive
   1.247 + * requests, so that it can properly respond to requests on any of the hosts it
   1.248 + * serves.
   1.249 + */
   1.250 +function processLocations(server)
   1.251 +{
   1.252 +  var serverLocations = serverBasePath.clone();
   1.253 +  serverLocations.append("server-locations.txt");
   1.254 +
   1.255 +  const PR_RDONLY = 0x01;
   1.256 +  var fis = new FileInputStream(serverLocations, PR_RDONLY, 292 /* 0444 */,
   1.257 +                                Ci.nsIFileInputStream.CLOSE_ON_EOF);
   1.258 +
   1.259 +  var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
   1.260 +  lis.QueryInterface(Ci.nsIUnicharLineInputStream);
   1.261 +
   1.262 +  const LINE_REGEXP =
   1.263 +    new RegExp("^([a-z][-a-z0-9+.]*)" +
   1.264 +               "://" +
   1.265 +               "(" +
   1.266 +                 "\\d+\\.\\d+\\.\\d+\\.\\d+" +
   1.267 +                 "|" +
   1.268 +                 "(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\\.)*" +
   1.269 +                 "[a-z](?:[-a-z0-9]*[a-z0-9])?" +
   1.270 +               ")" +
   1.271 +               ":" +
   1.272 +               "(\\d+)" +
   1.273 +               "(?:" +
   1.274 +               "\\s+" +
   1.275 +               "(\\S+(?:,\\S+)*)" +
   1.276 +               ")?$");
   1.277 +
   1.278 +  var line = {};
   1.279 +  var lineno = 0;
   1.280 +  var seenPrimary = false;
   1.281 +  do
   1.282 +  {
   1.283 +    var more = lis.readLine(line);
   1.284 +    lineno++;
   1.285 +
   1.286 +    var lineValue = line.value;
   1.287 +    if (lineValue.charAt(0) == "#" || lineValue == "")
   1.288 +      continue;
   1.289 +
   1.290 +    var match = LINE_REGEXP.exec(lineValue);
   1.291 +    if (!match)
   1.292 +      throw "Syntax error in server-locations.txt, line " + lineno;
   1.293 +
   1.294 +    var [, scheme, host, port, options] = match;
   1.295 +    if (options)
   1.296 +    {
   1.297 +      if (options.split(",").indexOf("primary") >= 0)
   1.298 +      {
   1.299 +        if (seenPrimary)
   1.300 +        {
   1.301 +          throw "Multiple primary locations in server-locations.txt, " +
   1.302 +                "line " + lineno;
   1.303 +        }
   1.304 +  
   1.305 +        server.identity.setPrimary(scheme, host, port);
   1.306 +        seenPrimary = true;
   1.307 +        continue;
   1.308 +      }
   1.309 +    }
   1.310 +
   1.311 +    server.identity.add(scheme, host, port);
   1.312 +  }
   1.313 +  while (more);
   1.314 +}
   1.315 +
   1.316 +
   1.317 +// PATH HANDLERS
   1.318 +
   1.319 +// /server/shutdown
   1.320 +function serverShutdown(metadata, response)
   1.321 +{
   1.322 +  response.setStatusLine("1.1", 200, "OK");
   1.323 +  response.setHeader("Content-type", "text/plain", false);
   1.324 +
   1.325 +  var body = "Server shut down.";
   1.326 +  response.bodyOutputStream.write(body, body.length);
   1.327 +
   1.328 +  dumpn("Server shutting down now...");
   1.329 +  server.stop(serverStopped);
   1.330 +}
   1.331 +
   1.332 +// /server/debug?[012]
   1.333 +function serverDebug(metadata, response)
   1.334 +{
   1.335 +  response.setStatusLine(metadata.httpVersion, 400, "Bad debugging level");
   1.336 +  if (metadata.queryString.length !== 1)
   1.337 +    return;
   1.338 +
   1.339 +  var mode;
   1.340 +  if (metadata.queryString === "0") {
   1.341 +    // do this now so it gets logged with the old mode
   1.342 +    dumpn("Server debug logs disabled.");
   1.343 +    DEBUG = false;
   1.344 +    DEBUG_TIMESTAMP = false;
   1.345 +    mode = "disabled";
   1.346 +  } else if (metadata.queryString === "1") {
   1.347 +    DEBUG = true;
   1.348 +    DEBUG_TIMESTAMP = false;
   1.349 +    mode = "enabled";
   1.350 +  } else if (metadata.queryString === "2") {
   1.351 +    DEBUG = true;
   1.352 +    DEBUG_TIMESTAMP = true;
   1.353 +    mode = "enabled, with timestamps";
   1.354 +  } else {
   1.355 +    return;
   1.356 +  }
   1.357 +
   1.358 +  response.setStatusLine(metadata.httpVersion, 200, "OK");
   1.359 +  response.setHeader("Content-type", "text/plain", false);
   1.360 +  var body = "Server debug logs " + mode + ".";
   1.361 +  response.bodyOutputStream.write(body, body.length);
   1.362 +  dumpn(body);
   1.363 +}
   1.364 +
   1.365 +//
   1.366 +// DIRECTORY LISTINGS
   1.367 +//
   1.368 +
   1.369 +/**
   1.370 + * Creates a generator that iterates over the contents of
   1.371 + * an nsIFile directory.
   1.372 + */
   1.373 +function dirIter(dir)
   1.374 +{
   1.375 +  var en = dir.directoryEntries;
   1.376 +  while (en.hasMoreElements()) {
   1.377 +    var file = en.getNext();
   1.378 +    yield file.QueryInterface(Ci.nsILocalFile);
   1.379 +  }
   1.380 +}
   1.381 +
   1.382 +/**
   1.383 + * Builds an optionally nested object containing links to the
   1.384 + * files and directories within dir.
   1.385 + */
   1.386 +function list(requestPath, directory, recurse)
   1.387 +{
   1.388 +  var count = 0;
   1.389 +  var path = requestPath;
   1.390 +  if (path.charAt(path.length - 1) != "/") {
   1.391 +    path += "/";
   1.392 +  }
   1.393 +
   1.394 +  var dir = directory.QueryInterface(Ci.nsIFile);
   1.395 +  var links = {};
   1.396 +  
   1.397 +  // The SimpleTest directory is hidden
   1.398 +  var files = [file for (file in dirIter(dir))
   1.399 +               if (file.exists() && file.path.indexOf("SimpleTest") == -1)];
   1.400 +  
   1.401 +  // Sort files by name, so that tests can be run in a pre-defined order inside
   1.402 +  // a given directory (see bug 384823)
   1.403 +  function leafNameComparator(first, second) {
   1.404 +    if (first.leafName < second.leafName)
   1.405 +      return -1;
   1.406 +    if (first.leafName > second.leafName)
   1.407 +      return 1;
   1.408 +    return 0;
   1.409 +  }
   1.410 +  files.sort(leafNameComparator);
   1.411 +  
   1.412 +  count = files.length;
   1.413 +  for each (var file in files) {
   1.414 +    var key = path + file.leafName;
   1.415 +    var childCount = 0;
   1.416 +    if (file.isDirectory()) {
   1.417 +      key += "/";
   1.418 +    }
   1.419 +    if (recurse && file.isDirectory()) {
   1.420 +      [links[key], childCount] = list(key, file, recurse);
   1.421 +      count += childCount;
   1.422 +    } else {
   1.423 +      if (file.leafName.charAt(0) != '.') {
   1.424 +        links[key] = true;
   1.425 +      }
   1.426 +    }
   1.427 +  }
   1.428 +
   1.429 +  return [links, count];
   1.430 +}
   1.431 +
   1.432 +/**
   1.433 + * Heuristic function that determines whether a given path
   1.434 + * is a test case to be executed in the harness, or just
   1.435 + * a supporting file.
   1.436 + */
   1.437 +function isTest(filename, pattern)
   1.438 +{
   1.439 +  if (pattern)
   1.440 +    return pattern.test(filename);
   1.441 +
   1.442 +  // File name is a URL style path to a test file, make sure that we check for
   1.443 +  // tests that start with the appropriate prefix.
   1.444 +  var testPrefix = typeof(_TEST_PREFIX) == "string" ? _TEST_PREFIX : "test_";
   1.445 +  var testPattern = new RegExp("^" + testPrefix);
   1.446 +
   1.447 +  pathPieces = filename.split('/');
   1.448 +    
   1.449 +  return testPattern.test(pathPieces[pathPieces.length - 1]) &&
   1.450 +         filename.indexOf(".js") == -1 &&
   1.451 +         filename.indexOf(".css") == -1 &&
   1.452 +         !/\^headers\^$/.test(filename);
   1.453 +}
   1.454 +
   1.455 +/**
   1.456 + * Transform nested hashtables of paths to nested HTML lists.
   1.457 + */
   1.458 +function linksToListItems(links)
   1.459 +{
   1.460 +  var response = "";
   1.461 +  var children = "";
   1.462 +  for (var [link, value] in links) {
   1.463 +    var classVal = (!isTest(link) && !(value instanceof Object))
   1.464 +      ? "non-test invisible"
   1.465 +      : "test";
   1.466 +    if (value instanceof Object) {
   1.467 +      children = UL({class: "testdir"}, linksToListItems(value)); 
   1.468 +    } else {
   1.469 +      children = "";
   1.470 +    }
   1.471 +
   1.472 +    var bug_title = link.match(/test_bug\S+/);
   1.473 +    var bug_num = null;
   1.474 +    if (bug_title != null) {
   1.475 +        bug_num = bug_title[0].match(/\d+/);
   1.476 +    }
   1.477 +
   1.478 +    if ((bug_title == null) || (bug_num == null)) {
   1.479 +      response += LI({class: classVal}, A({href: link}, link), children);
   1.480 +    } else {
   1.481 +      var bug_url = "https://bugzilla.mozilla.org/show_bug.cgi?id="+bug_num;
   1.482 +      response += LI({class: classVal}, A({href: link}, link), " - ", A({href: bug_url}, "Bug "+bug_num), children);
   1.483 +    }
   1.484 +
   1.485 +  }
   1.486 +  return response;
   1.487 +}
   1.488 +
   1.489 +/**
   1.490 + * Transform nested hashtables of paths to a flat table rows.
   1.491 + */
   1.492 +function linksToTableRows(links, recursionLevel)
   1.493 +{
   1.494 +  var response = "";
   1.495 +  for (var [link, value] in links) {
   1.496 +    var classVal = (!isTest(link) && !(value instanceof Object))
   1.497 +      ? "non-test invisible"
   1.498 +      : "";
   1.499 +
   1.500 +    spacer = "padding-left: " + (10 * recursionLevel) + "px";
   1.501 +
   1.502 +    if (value instanceof Object) {
   1.503 +      response += TR({class: "dir", id: "tr-" + link },
   1.504 +                     TD({colspan: "3"}, "&#160;"),
   1.505 +                     TD({style: spacer},
   1.506 +                        A({href: link}, link)));
   1.507 +      response += linksToTableRows(value, recursionLevel + 1);
   1.508 +    } else {
   1.509 +      var bug_title = link.match(/test_bug\S+/);
   1.510 +      var bug_num = null;
   1.511 +      if (bug_title != null) {
   1.512 +          bug_num = bug_title[0].match(/\d+/);
   1.513 +      }
   1.514 +      if ((bug_title == null) || (bug_num == null)) {
   1.515 +        response += TR({class: classVal, id: "tr-" + link },
   1.516 +                       TD("0"),
   1.517 +                       TD("0"),
   1.518 +                       TD("0"),
   1.519 +                       TD({style: spacer},
   1.520 +                          A({href: link}, link)));
   1.521 +      } else {
   1.522 +        var bug_url = "https://bugzilla.mozilla.org/show_bug.cgi?id=" + bug_num;
   1.523 +        response += TR({class: classVal, id: "tr-" + link },
   1.524 +                       TD("0"),
   1.525 +                       TD("0"),
   1.526 +                       TD("0"),
   1.527 +                       TD({style: spacer},
   1.528 +                          A({href: link}, link), " - ",
   1.529 +                          A({href: bug_url}, "Bug " + bug_num)));
   1.530 +      }
   1.531 +    }
   1.532 +  }
   1.533 +  return response;
   1.534 +}
   1.535 +
   1.536 +function arrayOfTestFiles(linkArray, fileArray, testPattern) {
   1.537 +  for (var [link, value] in Iterator(linkArray)) {
   1.538 +    if (value instanceof Object) {
   1.539 +      arrayOfTestFiles(value, fileArray, testPattern);
   1.540 +    } else if (isTest(link, testPattern)) {
   1.541 +      fileArray.push(link)
   1.542 +    }
   1.543 +  }
   1.544 +}
   1.545 +/**
   1.546 + * Produce a flat array of test file paths to be executed in the harness.
   1.547 + */
   1.548 +function jsonArrayOfTestFiles(links)
   1.549 +{
   1.550 +  var testFiles = [];
   1.551 +  arrayOfTestFiles(links, testFiles);
   1.552 +  testFiles = ['"' + file + '"' for each(file in testFiles)];
   1.553 +  return "[" + testFiles.join(",\n") + "]";
   1.554 +}
   1.555 +
   1.556 +/**
   1.557 + * Produce a normal directory listing.
   1.558 + */
   1.559 +function regularListing(metadata, response)
   1.560 +{
   1.561 +  var [links, count] = list(metadata.path,
   1.562 +                            metadata.getProperty("directory"),
   1.563 +                            false);
   1.564 +  response.write(
   1.565 +    HTML(
   1.566 +      HEAD(
   1.567 +        TITLE("mochitest index ", metadata.path)
   1.568 +      ),
   1.569 +      BODY(
   1.570 +        BR(),
   1.571 +        A({href: ".."}, "Up a level"),
   1.572 +        UL(linksToListItems(links))
   1.573 +      )
   1.574 +    )
   1.575 +  );
   1.576 +}
   1.577 +
   1.578 +/**
   1.579 + * Produce a test harness page containing all the test cases
   1.580 + * below it, recursively.
   1.581 + */
   1.582 +function testListing(metadata, response)
   1.583 +{
   1.584 +  var links = {};
   1.585 +  var count = 0;
   1.586 +  if (metadata.queryString.indexOf('manifestFile') == -1) {
   1.587 +    [links, count] = list(metadata.path,
   1.588 +                          metadata.getProperty("directory"),
   1.589 +                          true);
   1.590 +  }
   1.591 +  var table_class = metadata.queryString.indexOf("hideResultsTable=1") > -1 ? "invisible": "";
   1.592 +
   1.593 +  let testname = (metadata.queryString.indexOf("testname=") > -1)
   1.594 +                 ? metadata.queryString.match(/testname=([^&]+)/)[1]
   1.595 +                 : "";
   1.596 +
   1.597 +  dumpn("count: " + count);
   1.598 +  var tests = testname
   1.599 +              ? "['/" + testname + "']"
   1.600 +              : jsonArrayOfTestFiles(links);
   1.601 +  response.write(
   1.602 +    HTML(
   1.603 +      HEAD(
   1.604 +        TITLE("MochiTest | ", metadata.path),
   1.605 +        LINK({rel: "stylesheet",
   1.606 +              type: "text/css", href: "/static/harness.css"}
   1.607 +        ),
   1.608 +        SCRIPT({type: "text/javascript",
   1.609 +                 src: "/tests/SimpleTest/LogController.js"}),
   1.610 +        SCRIPT({type: "text/javascript",
   1.611 +                 src: "/tests/SimpleTest/MemoryStats.js"}),
   1.612 +        SCRIPT({type: "text/javascript",
   1.613 +                 src: "/tests/SimpleTest/TestRunner.js"}),
   1.614 +        SCRIPT({type: "text/javascript",
   1.615 +                 src: "/tests/SimpleTest/MozillaLogger.js"}),
   1.616 +        SCRIPT({type: "text/javascript",
   1.617 +                 src: "/chunkifyTests.js"}),
   1.618 +        SCRIPT({type: "text/javascript",
   1.619 +                 src: "/manifestLibrary.js"}),
   1.620 +        SCRIPT({type: "text/javascript",
   1.621 +                 src: "/tests/SimpleTest/setup.js"}),
   1.622 +        SCRIPT({type: "text/javascript"},
   1.623 +               "window.onload =  hookup; gTestList=" + tests + ";"
   1.624 +        )
   1.625 +      ),
   1.626 +      BODY(
   1.627 +        DIV({class: "container"},
   1.628 +          H2("--> ", A({href: "#", id: "runtests"}, "Run Tests"), " <--"),
   1.629 +            P({style: "float: right;"},
   1.630 +            SMALL(
   1.631 +              "Based on the ",
   1.632 +              A({href:"http://www.mochikit.com/"}, "MochiKit"),
   1.633 +              " unit tests."
   1.634 +            )
   1.635 +          ),
   1.636 +          DIV({class: "status"},
   1.637 +            H1({id: "indicator"}, "Status"),
   1.638 +            H2({id: "pass"}, "Passed: ", SPAN({id: "pass-count"},"0")),
   1.639 +            H2({id: "fail"}, "Failed: ", SPAN({id: "fail-count"},"0")),
   1.640 +            H2({id: "fail"}, "Todo: ", SPAN({id: "todo-count"},"0"))
   1.641 +          ),
   1.642 +          DIV({class: "clear"}),
   1.643 +          DIV({id: "current-test"},
   1.644 +            B("Currently Executing: ",
   1.645 +              SPAN({id: "current-test-path"}, "_")
   1.646 +            )
   1.647 +          ),
   1.648 +          DIV({class: "clear"}),
   1.649 +          DIV({class: "frameholder"},
   1.650 +            IFRAME({scrolling: "no", id: "testframe", width: "500", height: "300"})
   1.651 +          ),
   1.652 +          DIV({class: "clear"}),
   1.653 +          DIV({class: "toggle"},
   1.654 +            A({href: "#", id: "toggleNonTests"}, "Show Non-Tests"),
   1.655 +            BR()
   1.656 +          ),
   1.657 +
   1.658 +          (
   1.659 +           displayResults ?
   1.660 +            TABLE({cellpadding: 0, cellspacing: 0, class: table_class, id: "test-table"},
   1.661 +              TR(TD("Passed"), TD("Failed"), TD("Todo"), TD("Test Files")),
   1.662 +              linksToTableRows(links, 0)
   1.663 +            ) : ""
   1.664 +          ),
   1.665 +
   1.666 +          BR(),
   1.667 +          TABLE({cellpadding: 0, cellspacing: 0, border: 1, bordercolor: "red", id: "fail-table"}
   1.668 +          ),
   1.669 +
   1.670 +          DIV({class: "clear"})
   1.671 +        )
   1.672 +      )
   1.673 +    )
   1.674 +  );
   1.675 +}
   1.676 +
   1.677 +/**
   1.678 + * Respond to requests that match a file system directory.
   1.679 + * Under the tests/ directory, return a test harness page.
   1.680 + */
   1.681 +function defaultDirHandler(metadata, response)
   1.682 +{
   1.683 +  response.setStatusLine("1.1", 200, "OK");
   1.684 +  response.setHeader("Content-type", "text/html;charset=utf-8", false);
   1.685 +  try {
   1.686 +    if (metadata.path.indexOf("/tests") != 0) {
   1.687 +      regularListing(metadata, response);
   1.688 +    } else {
   1.689 +      testListing(metadata, response);
   1.690 +    }
   1.691 +  } catch (ex) {
   1.692 +    response.write(ex);
   1.693 +  }  
   1.694 +}

mercurial