server.js 22 KB

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