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