Bug 458113. Fix syntax error that broke OS/2 build. r+wuno
[wine-gecko.git] / testing / mochitest / server.js
blob47e4537025a994641ec73cdf11e0ebc100053271
1 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et: */
3 /* ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is MozJSHTTP code.
18 * The Initial Developer of the Original Code is
19 * Jeff Walden <jwalden+code@mit.edu>.
20 * Portions created by the Initial Developer are Copyright (C) 2006
21 * the Initial Developer. All Rights Reserved.
23 * Contributor(s):
24 * Robert Sayre <sayrer@gmail.com>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
40 // Note that the server script itself already defines Cc, Ci, and Cr for us,
41 // and because they're constants it's not safe to redefine them. Scope leakage
42 // sucks.
44 const SERVER_PORT = 8888;
45 var server; // for use in the shutdown handler, if necessary
48 // HTML GENERATION
50 var tags = ['A', 'ABBR', 'ACRONYM', 'ADDRESS', 'APPLET', 'AREA', 'B', 'BASE',
51 'BASEFONT', 'BDO', 'BIG', 'BLOCKQUOTE', 'BODY', 'BR', 'BUTTON',
52 'CAPTION', 'CENTER', 'CITE', 'CODE', 'COL', 'COLGROUP', 'DD',
53 'DEL', 'DFN', 'DIR', 'DIV', 'DL', 'DT', 'EM', 'FIELDSET', 'FONT',
54 'FORM', 'FRAME', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6',
55 'HEAD', 'HR', 'HTML', 'I', 'IFRAME', 'IMG', 'INPUT', 'INS',
56 'ISINDEX', 'KBD', 'LABEL', 'LEGEND', 'LI', 'LINK', 'MAP', 'MENU',
57 'META', 'NOFRAMES', 'NOSCRIPT', 'OBJECT', 'OL', 'OPTGROUP',
58 'OPTION', 'P', 'PARAM', 'PRE', 'Q', 'S', 'SAMP', 'SCRIPT',
59 'SELECT', 'SMALL', 'SPAN', 'STRIKE', 'STRONG', 'STYLE', 'SUB',
60 'SUP', 'TABLE', 'TBODY', 'TD', 'TEXTAREA', 'TFOOT', 'TH', 'THEAD',
61 'TITLE', 'TR', 'TT', 'U', 'UL', 'VAR'];
63 /**
64 * Below, we'll use makeTagFunc to create a function for each of the
65 * strings in 'tags'. This will allow us to use s-expression like syntax
66 * to create HTML.
68 function makeTagFunc(tagName)
70 return function (attrs /* rest... */)
72 var startChildren = 0;
73 var response = "";
75 // write the start tag and attributes
76 response += "<" + tagName;
77 // if attr is an object, write attributes
78 if (attrs && typeof attrs == 'object') {
79 startChildren = 1;
80 for (var [key,value] in attrs) {
81 var val = "" + value;
82 response += " " + key + '="' + val.replace('"','&quot;') + '"';
85 response += ">";
87 // iterate through the rest of the args
88 for (var i = startChildren; i < arguments.length; i++) {
89 if (typeof arguments[i] == 'function') {
90 response += arguments[i]();
91 } else {
92 response += arguments[i];
96 // write the close tag
97 response += "</" + tagName + ">\n";
98 return response;
102 function makeTags() {
103 // map our global HTML generation functions
104 for each (var tag in tags) {
105 this[tag] = makeTagFunc(tag.toLowerCase());
109 // only run the "main" section if httpd.js was loaded ahead of us
110 if (this["nsHttpServer"]) {
112 // SCRIPT CODE
114 runServer();
116 // We can only have gotten here if the /server/shutdown path was requested,
117 // and we can shut down the xpcshell now that all testing requests have been
118 // served.
119 quit(0);
122 var serverBasePath;
125 // SERVER SETUP
127 function runServer()
129 serverBasePath = Cc["@mozilla.org/file/local;1"]
130 .createInstance(Ci.nsILocalFile);
131 var procDir = Cc["@mozilla.org/file/directory_service;1"]
132 .getService(Ci.nsIProperties).get("CurProcD", Ci.nsIFile);
133 serverBasePath.initWithPath(procDir.parent.parent.path);
134 serverBasePath.append("_tests");
135 serverBasePath.append("testing");
136 serverBasePath.append("mochitest");
138 server = createMochitestServer(serverBasePath);
139 server.start(SERVER_PORT);
141 // touch a file in the profile directory to indicate we're alive
142 var foStream = Cc["@mozilla.org/network/file-output-stream;1"]
143 .createInstance(Ci.nsIFileOutputStream);
144 var serverAlive = Cc["@mozilla.org/file/local;1"]
145 .createInstance(Ci.nsILocalFile);
146 serverAlive.initWithFile(serverBasePath);
147 serverAlive.append("mochitesttestingprofile");
149 // If we're running outside of the test harness, there might
150 // not be a test profile directory present
151 if (serverAlive.exists()) {
152 serverAlive.append("server_alive.txt");
153 foStream.init(serverAlive,
154 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
155 data = "It's alive!";
156 foStream.write(data, data.length);
157 foStream.close();
160 makeTags();
163 // The following is threading magic to spin an event loop -- this has to
164 // happen manually in xpcshell for the server to actually work.
166 var thread = Cc["@mozilla.org/thread-manager;1"]
167 .getService()
168 .currentThread;
169 while (!server.isStopped())
170 thread.processNextEvent(true);
172 // Server stopped by /server/shutdown handler -- go through pending events
173 // and return.
175 // get rid of any pending requests
176 while (thread.hasPendingEvents())
177 thread.processNextEvent(true);
180 /** Creates and returns an HTTP server configured to serve Mochitests. */
181 function createMochitestServer(serverBasePath)
183 var server = new nsHttpServer();
185 server.registerDirectory("/", serverBasePath);
186 server.registerPathHandler("/server/shutdown", serverShutdown);
187 server.registerContentType("sjs", "sjs"); // .sjs == CGI-like functionality
188 server.setIndexHandler(defaultDirHandler);
190 processLocations(server);
192 return server;
196 * Notifies the HTTP server about all the locations at which it might receive
197 * requests, so that it can properly respond to requests on any of the hosts it
198 * serves.
200 function processLocations(server)
202 var serverLocations = serverBasePath.clone();
203 serverLocations.append("server-locations.txt");
205 const PR_RDONLY = 0x01;
206 var fis = new FileInputStream(serverLocations, PR_RDONLY, 0444,
207 Ci.nsIFileInputStream.CLOSE_ON_EOF);
209 var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
210 lis.QueryInterface(Ci.nsIUnicharLineInputStream);
212 const LINE_REGEXP =
213 new RegExp("^([a-z][-a-z0-9+.]*)" +
214 "://" +
215 "(" +
216 "\\d+\\.\\d+\\.\\d+\\.\\d+" +
217 "|" +
218 "(?:[a-z0-9](?:[-a-z0-9]*[a-z0-9])?\\.)*" +
219 "[a-z](?:[-a-z0-9]*[a-z0-9])?" +
220 ")" +
221 ":" +
222 "(\\d+)" +
223 "(?:" +
224 "\\s+" +
225 "(\\S+(?:,\\S+)*)" +
226 ")?$");
228 var line = {};
229 var lineno = 0;
230 var seenPrimary = false;
233 var more = lis.readLine(line);
234 lineno++;
236 var lineValue = line.value;
237 if (lineValue.charAt(0) == "#" || lineValue == "")
238 continue;
240 var match = LINE_REGEXP.exec(lineValue);
241 if (!match)
242 throw "Syntax error in server-locations.txt, line " + lineno;
244 var [, scheme, host, port, options] = match;
245 if (options)
247 if (options.split(",").indexOf("primary") >= 0)
249 if (seenPrimary)
251 throw "Multiple primary locations in server-locations.txt, " +
252 "line " + lineno;
255 server.identity.setPrimary(scheme, host, port);
256 seenPrimary = true;
257 continue;
261 server.identity.add(scheme, host, port);
263 while (more);
267 // PATH HANDLERS
269 // /server/shutdown
270 function serverShutdown(metadata, response)
272 response.setStatusLine("1.1", 200, "OK");
273 response.setHeader("Content-type", "text/plain", false);
275 var body = "Server shut down.";
276 response.bodyOutputStream.write(body, body.length);
278 // Note: this doesn't disrupt the current request.
279 server.stop();
283 // DIRECTORY LISTINGS
287 * Creates a generator that iterates over the contents of
288 * an nsIFile directory.
290 function dirIter(dir)
292 var en = dir.directoryEntries;
293 while (en.hasMoreElements()) {
294 var file = en.getNext();
295 yield file.QueryInterface(Ci.nsILocalFile);
300 * Builds an optionally nested object containing links to the
301 * files and directories within dir.
303 function list(requestPath, directory, recurse)
305 var count = 0;
306 var path = requestPath;
307 if (path.charAt(path.length - 1) != "/") {
308 path += "/";
311 var dir = directory.QueryInterface(Ci.nsIFile);
312 var links = {};
314 // The SimpleTest directory is hidden
315 var files = [file for (file in dirIter(dir))
316 if (file.exists() && file.path.indexOf("SimpleTest") == -1)];
318 // Sort files by name, so that tests can be run in a pre-defined order inside
319 // a given directory (see bug 384823)
320 function leafNameComparator(first, second) {
321 if (first.leafName < second.leafName)
322 return -1;
323 if (first.leafName > second.leafName)
324 return 1;
325 return 0;
327 files.sort(leafNameComparator);
329 count = files.length;
330 for each (var file in files) {
331 var key = path + file.leafName;
332 var childCount = 0;
333 if (file.isDirectory()) {
334 key += "/";
336 if (recurse && file.isDirectory()) {
337 [links[key], childCount] = list(key, file, recurse);
338 count += childCount;
339 } else {
340 if (file.leafName.charAt(0) != '.') {
341 links[key] = true;
346 return [links, count];
350 * Heuristic function that determines whether a given path
351 * is a test case to be executed in the harness, or just
352 * a supporting file.
354 function isTest(filename, pattern)
356 if (pattern)
357 return pattern.test(filename);
359 return filename.indexOf("test_") > -1 &&
360 filename.indexOf(".js") == -1 &&
361 filename.indexOf(".css") == -1 &&
362 !/\^headers\^$/.test(filename);
366 * Transform nested hashtables of paths to nested HTML lists.
368 function linksToListItems(links)
370 var response = "";
371 var children = "";
372 for (var [link, value] in links) {
373 var classVal = (!isTest(link) && !(value instanceof Object))
374 ? "non-test invisible"
375 : "test";
376 if (value instanceof Object) {
377 children = UL({class: "testdir"}, linksToListItems(value));
378 } else {
379 children = "";
382 var bug_title = link.match(/test_bug\S+/);
383 var bug_num = null;
384 if (bug_title != null) {
385 bug_num = bug_title[0].match(/\d+/);
388 if ((bug_title == null) || (bug_num == null)) {
389 response += LI({class: classVal}, A({href: link}, link), children);
390 } else {
391 var bug_url = "https://bugzilla.mozilla.org/show_bug.cgi?id="+bug_num;
392 response += LI({class: classVal}, A({href: link}, link), " - ", A({href: bug_url}, "Bug "+bug_num), children);
396 return response;
400 * Transform nested hashtables of paths to a flat table rows.
402 function linksToTableRows(links)
404 var response = "";
405 for (var [link, value] in links) {
406 var classVal = (!isTest(link) && !(value instanceof Object))
407 ? "non-test invisible"
408 : "";
409 if (value instanceof Object) {
410 response += TR({class: "dir", id: "tr-" + link },
411 TD({colspan: "3"},"&#160;"));
412 response += linksToTableRows(value);
413 } else {
414 response += TR({class: classVal, id: "tr-" + link},
415 TD("0"), TD("0"), TD("0"));
418 return response;
421 function arrayOfTestFiles(linkArray, fileArray, testPattern) {
422 for (var [link, value] in linkArray) {
423 if (value instanceof Object) {
424 arrayOfTestFiles(value, fileArray, testPattern);
425 } else if (isTest(link, testPattern)) {
426 fileArray.push(link)
431 * Produce a flat array of test file paths to be executed in the harness.
433 function jsonArrayOfTestFiles(links)
435 var testFiles = [];
436 arrayOfTestFiles(links, testFiles);
437 testFiles = ['"' + file + '"' for each(file in testFiles)];
438 return "[" + testFiles.join(",\n") + "]";
442 * Produce a normal directory listing.
444 function regularListing(metadata, response)
446 var [links, count] = list(metadata.path,
447 metadata.getProperty("directory"),
448 false);
449 response.write(
450 HTML(
451 HEAD(
452 TITLE("mochitest index ", metadata.path)
454 BODY(
455 BR(),
456 A({href: ".."}, "Up a level"),
457 UL(linksToListItems(links))
464 * Produce a test harness page containing all the test cases
465 * below it, recursively.
467 function testListing(metadata, response)
469 var [links, count] = list(metadata.path,
470 metadata.getProperty("directory"),
471 true);
472 dumpn("count: " + count);
473 var tests = jsonArrayOfTestFiles(links);
474 response.write(
475 HTML(
476 HEAD(
477 TITLE("MochiTest | ", metadata.path),
478 LINK({rel: "stylesheet",
479 type: "text/css", href: "/static/harness.css"}
481 SCRIPT({type: "text/javascript", src: "/MochiKit/packed.js"}),
482 SCRIPT({type: "text/javascript",
483 src: "/tests/SimpleTest/TestRunner.js"}),
484 SCRIPT({type: "text/javascript",
485 src: "/tests/SimpleTest/MozillaFileLogger.js"}),
486 SCRIPT({type: "text/javascript",
487 src: "/tests/SimpleTest/quit.js"}),
488 SCRIPT({type: "text/javascript",
489 src: "/tests/SimpleTest/setup.js"}),
490 SCRIPT({type: "text/javascript"},
491 "connect(window, 'onload', hookup); gTestList=" + tests + ";"
494 BODY(
495 DIV({class: "container"},
496 H2("--> ", A({href: "#", id: "runtests"}, "Run Tests"), " <--"),
497 P({style: "float: right;"},
498 SMALL(
499 "Based on the ",
500 A({href:"http://www.mochikit.com/"}, "MochiKit"),
501 " unit tests."
504 DIV({class: "status"},
505 H1({id: "indicator"}, "Status"),
506 H2({id: "pass"}, "Passed: ", SPAN({id: "pass-count"},"0")),
507 H2({id: "fail"}, "Failed: ", SPAN({id: "fail-count"},"0")),
508 H2({id: "fail"}, "Todo: ", SPAN({id: "todo-count"},"0"))
510 DIV({class: "clear"}),
511 DIV({id: "current-test"},
512 B("Currently Executing: ",
513 SPAN({id: "current-test-path"}, "_")
516 DIV({class: "clear"}),
517 DIV({class: "frameholder"},
518 IFRAME({scrolling: "no", id: "testframe", width: "500", height: "300"})
520 DIV({class: "clear"}),
521 DIV({class: "toggle"},
522 A({href: "#", id: "toggleNonTests"}, "Show Non-Tests"),
523 BR()
526 TABLE({cellpadding: 0, cellspacing: 0, id: "test-table"},
527 TR(TD("Passed"), TD("Failed"), TD("Todo"),
528 TD({rowspan: count+1},
529 UL({class: "top"},
530 LI(B("Test Files")),
531 linksToListItems(links)
535 linksToTableRows(links)
537 DIV({class: "clear"})
545 * Respond to requests that match a file system directory.
546 * Under the tests/ directory, return a test harness page.
548 function defaultDirHandler(metadata, response)
550 response.setStatusLine("1.1", 200, "OK");
551 response.setHeader("Content-type", "text/html", false);
552 try {
553 if (metadata.path.indexOf("/tests") != 0) {
554 regularListing(metadata, response);
555 } else {
556 testListing(metadata, response);
558 } catch (ex) {
559 response.write(ex);