|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 var EXPORTED_SYMBOLS = ['Collector','Runner','events', 'runTestFile', 'log', |
|
6 'timers', 'persisted', 'shutdownApplication']; |
|
7 |
|
8 const Cc = Components.classes; |
|
9 const Ci = Components.interfaces; |
|
10 const Cu = Components.utils; |
|
11 |
|
12 const TIMEOUT_SHUTDOWN_HTTPD = 15000; |
|
13 |
|
14 Cu.import("resource://gre/modules/Services.jsm"); |
|
15 |
|
16 Cu.import('resource://mozmill/stdlib/httpd.js'); |
|
17 |
|
18 var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker); |
|
19 var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions); |
|
20 var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors); |
|
21 var os = {}; Cu.import('resource://mozmill/stdlib/os.js', os); |
|
22 var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings); |
|
23 var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays); |
|
24 var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs); |
|
25 var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils); |
|
26 |
|
27 var securableModule = {}; |
|
28 Cu.import('resource://mozmill/stdlib/securable-module.js', securableModule); |
|
29 |
|
30 var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); |
|
31 |
|
32 var httpd = null; |
|
33 var persisted = {}; |
|
34 |
|
35 var assert = new assertions.Assert(); |
|
36 |
|
37 var mozmill = undefined; |
|
38 var mozelement = undefined; |
|
39 var modules = undefined; |
|
40 |
|
41 var timers = []; |
|
42 |
|
43 |
|
44 /** |
|
45 * Shutdown or restart the application |
|
46 * |
|
47 * @param {boolean} [aFlags=undefined] |
|
48 * Additional flags how to handle the shutdown or restart. The attributes |
|
49 * eRestarti386 and eRestartx86_64 have not been documented yet. |
|
50 * @see https://developer.mozilla.org/nsIAppStartup#Attributes |
|
51 */ |
|
52 function shutdownApplication(aFlags) { |
|
53 var flags = Ci.nsIAppStartup.eForceQuit; |
|
54 |
|
55 if (aFlags) { |
|
56 flags |= aFlags; |
|
57 } |
|
58 |
|
59 // Send a request to shutdown the application. That will allow us and other |
|
60 // components to finish up with any shutdown code. Please note that we don't |
|
61 // care if other components or add-ons want to prevent this via cancelQuit, |
|
62 // we really force the shutdown. |
|
63 let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]. |
|
64 createInstance(Components.interfaces.nsISupportsPRBool); |
|
65 Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null); |
|
66 |
|
67 // Use a timer to trigger the application restart, which will allow us to |
|
68 // send an ACK packet via jsbridge if the method has been called via Python. |
|
69 var event = { |
|
70 notify: function(timer) { |
|
71 Services.startup.quit(flags); |
|
72 } |
|
73 } |
|
74 |
|
75 var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
|
76 timer.initWithCallback(event, 100, Ci.nsITimer.TYPE_ONE_SHOT); |
|
77 } |
|
78 |
|
79 function stateChangeBase(possibilties, restrictions, target, cmeta, v) { |
|
80 if (possibilties) { |
|
81 if (!arrays.inArray(possibilties, v)) { |
|
82 // TODO Error value not in this.poss |
|
83 return; |
|
84 } |
|
85 } |
|
86 |
|
87 if (restrictions) { |
|
88 for (var i in restrictions) { |
|
89 var r = restrictions[i]; |
|
90 if (!r(v)) { |
|
91 // TODO error value did not pass restriction |
|
92 return; |
|
93 } |
|
94 } |
|
95 } |
|
96 |
|
97 // Fire jsbridge notification, logging notification, listener notifications |
|
98 events[target] = v; |
|
99 events.fireEvent(cmeta, target); |
|
100 } |
|
101 |
|
102 |
|
103 var events = { |
|
104 appQuit : false, |
|
105 currentModule : null, |
|
106 currentState : null, |
|
107 currentTest : null, |
|
108 shutdownRequested : false, |
|
109 userShutdown : null, |
|
110 userShutdownTimer : null, |
|
111 |
|
112 listeners : {}, |
|
113 globalListeners : [] |
|
114 } |
|
115 |
|
116 events.setState = function (v) { |
|
117 return stateChangeBase(['dependencies', 'setupModule', 'teardownModule', |
|
118 'test', 'setupTest', 'teardownTest', 'collection'], |
|
119 null, 'currentState', 'setState', v); |
|
120 } |
|
121 |
|
122 events.toggleUserShutdown = function (obj){ |
|
123 if (!this.userShutdown) { |
|
124 this.userShutdown = obj; |
|
125 |
|
126 var event = { |
|
127 notify: function(timer) { |
|
128 events.toggleUserShutdown(obj); |
|
129 } |
|
130 } |
|
131 |
|
132 this.userShutdownTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
|
133 this.userShutdownTimer.initWithCallback(event, obj.timeout, Ci.nsITimer.TYPE_ONE_SHOT); |
|
134 |
|
135 } else { |
|
136 this.userShutdownTimer.cancel(); |
|
137 |
|
138 // If the application is not going to shutdown, the user shutdown failed and |
|
139 // we have to force a shutdown. |
|
140 if (!events.appQuit) { |
|
141 this.fail({'function':'events.toggleUserShutdown', |
|
142 'message':'Shutdown expected but none detected before timeout', |
|
143 'userShutdown': obj}); |
|
144 |
|
145 var flags = Ci.nsIAppStartup.eAttemptQuit; |
|
146 if (events.isRestartShutdown()) { |
|
147 flags |= Ci.nsIAppStartup.eRestart; |
|
148 } |
|
149 |
|
150 shutdownApplication(flags); |
|
151 } |
|
152 } |
|
153 } |
|
154 |
|
155 events.isUserShutdown = function () { |
|
156 return this.userShutdown ? this.userShutdown["user"] : false; |
|
157 } |
|
158 |
|
159 events.isRestartShutdown = function () { |
|
160 return this.userShutdown.restart; |
|
161 } |
|
162 |
|
163 events.startShutdown = function (obj) { |
|
164 events.fireEvent('shutdown', obj); |
|
165 |
|
166 if (obj["user"]) { |
|
167 events.toggleUserShutdown(obj); |
|
168 } else { |
|
169 shutdownApplication(obj.flags); |
|
170 } |
|
171 } |
|
172 |
|
173 events.setTest = function (test) { |
|
174 test.__start__ = Date.now(); |
|
175 test.__passes__ = []; |
|
176 test.__fails__ = []; |
|
177 |
|
178 events.currentTest = test; |
|
179 |
|
180 var obj = {'filename': events.currentModule.__file__, |
|
181 'name': test.__name__} |
|
182 events.fireEvent('setTest', obj); |
|
183 } |
|
184 |
|
185 events.endTest = function (test) { |
|
186 // use the current test unless specified |
|
187 if (test === undefined) { |
|
188 test = events.currentTest; |
|
189 } |
|
190 |
|
191 // If no test is set it has already been reported. Beside that we don't want |
|
192 // to report it a second time. |
|
193 if (!test || test.status === 'done') |
|
194 return; |
|
195 |
|
196 // report the end of a test |
|
197 test.__end__ = Date.now(); |
|
198 test.status = 'done'; |
|
199 |
|
200 var obj = {'filename': events.currentModule.__file__, |
|
201 'passed': test.__passes__.length, |
|
202 'failed': test.__fails__.length, |
|
203 'passes': test.__passes__, |
|
204 'fails' : test.__fails__, |
|
205 'name' : test.__name__, |
|
206 'time_start': test.__start__, |
|
207 'time_end': test.__end__} |
|
208 |
|
209 if (test.skipped) { |
|
210 obj['skipped'] = true; |
|
211 obj.skipped_reason = test.skipped_reason; |
|
212 } |
|
213 |
|
214 if (test.meta) { |
|
215 obj.meta = test.meta; |
|
216 } |
|
217 |
|
218 // Report the test result only if the test is a true test or if it is failing |
|
219 if (withs.startsWith(test.__name__, "test") || test.__fails__.length > 0) { |
|
220 events.fireEvent('endTest', obj); |
|
221 } |
|
222 } |
|
223 |
|
224 events.setModule = function (aModule) { |
|
225 aModule.__start__ = Date.now(); |
|
226 aModule.__status__ = 'running'; |
|
227 |
|
228 var result = stateChangeBase(null, |
|
229 [function (aModule) {return (aModule.__file__ != undefined)}], |
|
230 'currentModule', 'setModule', aModule); |
|
231 |
|
232 return result; |
|
233 } |
|
234 |
|
235 events.endModule = function (aModule) { |
|
236 // It should only reported once, so check if it already has been done |
|
237 if (aModule.__status__ === 'done') |
|
238 return; |
|
239 |
|
240 aModule.__end__ = Date.now(); |
|
241 aModule.__status__ = 'done'; |
|
242 |
|
243 var obj = { |
|
244 'filename': aModule.__file__, |
|
245 'time_start': aModule.__start__, |
|
246 'time_end': aModule.__end__ |
|
247 } |
|
248 |
|
249 events.fireEvent('endModule', obj); |
|
250 } |
|
251 |
|
252 events.pass = function (obj) { |
|
253 // a low level event, such as a keystroke, succeeds |
|
254 if (events.currentTest) { |
|
255 events.currentTest.__passes__.push(obj); |
|
256 } |
|
257 |
|
258 for each (var timer in timers) { |
|
259 timer.actions.push( |
|
260 {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__, |
|
261 "obj": obj, |
|
262 "result": "pass"} |
|
263 ); |
|
264 } |
|
265 |
|
266 events.fireEvent('pass', obj); |
|
267 } |
|
268 |
|
269 events.fail = function (obj) { |
|
270 var error = obj.exception; |
|
271 |
|
272 if (error) { |
|
273 // Error objects aren't enumerable https://bugzilla.mozilla.org/show_bug.cgi?id=637207 |
|
274 obj.exception = { |
|
275 name: error.name, |
|
276 message: error.message, |
|
277 lineNumber: error.lineNumber, |
|
278 fileName: error.fileName, |
|
279 stack: error.stack |
|
280 }; |
|
281 } |
|
282 |
|
283 // a low level event, such as a keystroke, fails |
|
284 if (events.currentTest) { |
|
285 events.currentTest.__fails__.push(obj); |
|
286 } |
|
287 |
|
288 for each (var time in timers) { |
|
289 timer.actions.push( |
|
290 {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__, |
|
291 "obj": obj, |
|
292 "result": "fail"} |
|
293 ); |
|
294 } |
|
295 |
|
296 events.fireEvent('fail', obj); |
|
297 } |
|
298 |
|
299 events.skip = function (reason) { |
|
300 // this is used to report skips associated with setupModule and nothing else |
|
301 events.currentTest.skipped = true; |
|
302 events.currentTest.skipped_reason = reason; |
|
303 |
|
304 for (var timer of timers) { |
|
305 timer.actions.push( |
|
306 {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__, |
|
307 "obj": reason, |
|
308 "result": "skip"} |
|
309 ); |
|
310 } |
|
311 |
|
312 events.fireEvent('skip', reason); |
|
313 } |
|
314 |
|
315 events.fireEvent = function (name, obj) { |
|
316 if (events.appQuit) { |
|
317 // dump('* Event discarded: ' + name + ' ' + JSON.stringify(obj) + '\n'); |
|
318 return; |
|
319 } |
|
320 |
|
321 if (this.listeners[name]) { |
|
322 for (var i in this.listeners[name]) { |
|
323 this.listeners[name][i](obj); |
|
324 } |
|
325 } |
|
326 |
|
327 for each(var listener in this.globalListeners) { |
|
328 listener(name, obj); |
|
329 } |
|
330 } |
|
331 |
|
332 events.addListener = function (name, listener) { |
|
333 if (this.listeners[name]) { |
|
334 this.listeners[name].push(listener); |
|
335 } else if (name == '') { |
|
336 this.globalListeners.push(listener) |
|
337 } else { |
|
338 this.listeners[name] = [listener]; |
|
339 } |
|
340 } |
|
341 |
|
342 events.removeListener = function (listener) { |
|
343 for (var listenerIndex in this.listeners) { |
|
344 var e = this.listeners[listenerIndex]; |
|
345 |
|
346 for (var i in e){ |
|
347 if (e[i] == listener) { |
|
348 this.listeners[listenerIndex] = arrays.remove(e, i); |
|
349 } |
|
350 } |
|
351 } |
|
352 |
|
353 for (var i in this.globalListeners) { |
|
354 if (this.globalListeners[i] == listener) { |
|
355 this.globalListeners = arrays.remove(this.globalListeners, i); |
|
356 } |
|
357 } |
|
358 } |
|
359 |
|
360 events.persist = function () { |
|
361 try { |
|
362 events.fireEvent('persist', persisted); |
|
363 } catch (e) { |
|
364 events.fireEvent('error', "persist serialization failed.") |
|
365 } |
|
366 } |
|
367 |
|
368 events.firePythonCallback = function (obj) { |
|
369 obj['test'] = events.currentModule.__file__; |
|
370 events.fireEvent('firePythonCallback', obj); |
|
371 } |
|
372 |
|
373 events.screenshot = function (obj) { |
|
374 // Find the name of the test function |
|
375 for (var attr in events.currentModule) { |
|
376 if (events.currentModule[attr] == events.currentTest) { |
|
377 var testName = attr; |
|
378 break; |
|
379 } |
|
380 } |
|
381 |
|
382 obj['test_file'] = events.currentModule.__file__; |
|
383 obj['test_name'] = testName; |
|
384 events.fireEvent('screenshot', obj); |
|
385 } |
|
386 |
|
387 var log = function (obj) { |
|
388 events.fireEvent('log', obj); |
|
389 } |
|
390 |
|
391 // Register the listeners |
|
392 broker.addObject({'endTest': events.endTest, |
|
393 'fail': events.fail, |
|
394 'firePythonCallback': events.firePythonCallback, |
|
395 'log': log, |
|
396 'pass': events.pass, |
|
397 'persist': events.persist, |
|
398 'screenshot': events.screenshot, |
|
399 'shutdown': events.startShutdown, |
|
400 }); |
|
401 |
|
402 try { |
|
403 Cu.import('resource://jsbridge/modules/Events.jsm'); |
|
404 |
|
405 events.addListener('', function (name, obj) { |
|
406 Events.fireEvent('mozmill.' + name, obj); |
|
407 }); |
|
408 } catch (e) { |
|
409 Services.console.logStringMessage("Event module of JSBridge not available."); |
|
410 } |
|
411 |
|
412 |
|
413 /** |
|
414 * Observer for notifications when the application is going to shutdown |
|
415 */ |
|
416 function AppQuitObserver() { |
|
417 this.runner = null; |
|
418 |
|
419 Services.obs.addObserver(this, "quit-application-requested", false); |
|
420 } |
|
421 |
|
422 AppQuitObserver.prototype = { |
|
423 observe: function (aSubject, aTopic, aData) { |
|
424 switch (aTopic) { |
|
425 case "quit-application-requested": |
|
426 Services.obs.removeObserver(this, "quit-application-requested"); |
|
427 |
|
428 // If we observe a quit notification make sure to send the |
|
429 // results of the current test. In those cases we don't reach |
|
430 // the equivalent code in runTestModule() |
|
431 events.pass({'message': 'AppQuitObserver: ' + JSON.stringify(aData), |
|
432 'userShutdown': events.userShutdown}); |
|
433 |
|
434 if (this.runner) { |
|
435 this.runner.end(); |
|
436 } |
|
437 |
|
438 if (httpd) { |
|
439 httpd.stop(); |
|
440 } |
|
441 |
|
442 events.appQuit = true; |
|
443 |
|
444 break; |
|
445 } |
|
446 } |
|
447 } |
|
448 |
|
449 var appQuitObserver = new AppQuitObserver(); |
|
450 |
|
451 /** |
|
452 * The collector handles HTTPd.js and initilizing the module |
|
453 */ |
|
454 function Collector() { |
|
455 this.test_modules_by_filename = {}; |
|
456 this.testing = []; |
|
457 } |
|
458 |
|
459 Collector.prototype.addHttpResource = function (aDirectory, aPath) { |
|
460 var fp = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); |
|
461 fp.initWithPath(os.abspath(aDirectory, this.current_file)); |
|
462 |
|
463 return httpd.addHttpResource(fp, aPath); |
|
464 } |
|
465 |
|
466 Collector.prototype.initTestModule = function (filename, testname) { |
|
467 var test_module = this.loadFile(filename, this); |
|
468 var has_restarted = !(testname == null); |
|
469 test_module.__tests__ = []; |
|
470 |
|
471 for (var i in test_module) { |
|
472 if (typeof(test_module[i]) == "function") { |
|
473 test_module[i].__name__ = i; |
|
474 |
|
475 // Only run setupModule if we are a single test OR if we are the first |
|
476 // test of a restart chain (don't run it prior to members in a restart |
|
477 // chain) |
|
478 if (i == "setupModule" && !has_restarted) { |
|
479 test_module.__setupModule__ = test_module[i]; |
|
480 } else if (i == "setupTest") { |
|
481 test_module.__setupTest__ = test_module[i]; |
|
482 } else if (i == "teardownTest") { |
|
483 test_module.__teardownTest__ = test_module[i]; |
|
484 } else if (i == "teardownModule") { |
|
485 test_module.__teardownModule__ = test_module[i]; |
|
486 } else if (withs.startsWith(i, "test")) { |
|
487 if (testname && (i != testname)) { |
|
488 continue; |
|
489 } |
|
490 |
|
491 testname = null; |
|
492 test_module.__tests__.push(test_module[i]); |
|
493 } |
|
494 } |
|
495 } |
|
496 |
|
497 test_module.collector = this; |
|
498 test_module.status = 'loaded'; |
|
499 |
|
500 this.test_modules_by_filename[filename] = test_module; |
|
501 |
|
502 return test_module; |
|
503 } |
|
504 |
|
505 Collector.prototype.loadFile = function (path, collector) { |
|
506 var moduleLoader = new securableModule.Loader({ |
|
507 rootPaths: ["resource://mozmill/modules/"], |
|
508 defaultPrincipal: "system", |
|
509 globals : { Cc: Cc, |
|
510 Ci: Ci, |
|
511 Cu: Cu, |
|
512 Cr: Components.results} |
|
513 }); |
|
514 |
|
515 // load a test module from a file and add some candy |
|
516 var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); |
|
517 file.initWithPath(path); |
|
518 var uri = Services.io.newFileURI(file).spec; |
|
519 |
|
520 this.loadTestResources(); |
|
521 |
|
522 var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); |
|
523 var module = new Components.utils.Sandbox(systemPrincipal); |
|
524 module.assert = new assertions.Assert(); |
|
525 module.Cc = Cc; |
|
526 module.Ci = Ci; |
|
527 module.Cr = Components.results; |
|
528 module.Cu = Cu; |
|
529 module.collector = collector; |
|
530 module.driver = moduleLoader.require("driver"); |
|
531 module.elementslib = mozelement; |
|
532 module.errors = errors; |
|
533 module.expect = new assertions.Expect(); |
|
534 module.findElement = mozelement; |
|
535 module.log = log; |
|
536 module.mozmill = mozmill; |
|
537 module.persisted = persisted; |
|
538 |
|
539 module.require = function (mod) { |
|
540 var loader = new securableModule.Loader({ |
|
541 rootPaths: [Services.io.newFileURI(file.parent).spec, |
|
542 "resource://mozmill/modules/"], |
|
543 defaultPrincipal: "system", |
|
544 globals : { mozmill: mozmill, |
|
545 elementslib: mozelement, // This a quick hack to maintain backwards compatibility with 1.5.x |
|
546 findElement: mozelement, |
|
547 persisted: persisted, |
|
548 Cc: Cc, |
|
549 Ci: Ci, |
|
550 Cu: Cu, |
|
551 log: log } |
|
552 }); |
|
553 |
|
554 if (modules != undefined) { |
|
555 loader.modules = modules; |
|
556 } |
|
557 |
|
558 var retval = loader.require(mod); |
|
559 modules = loader.modules; |
|
560 |
|
561 return retval; |
|
562 } |
|
563 |
|
564 if (collector != undefined) { |
|
565 collector.current_file = file; |
|
566 collector.current_path = path; |
|
567 } |
|
568 |
|
569 try { |
|
570 Services.scriptloader.loadSubScript(uri, module, "UTF-8"); |
|
571 } catch (e) { |
|
572 var obj = { |
|
573 'filename': path, |
|
574 'passed': 0, |
|
575 'failed': 1, |
|
576 'passes': [], |
|
577 'fails' : [{'exception' : { |
|
578 message: e.message, |
|
579 filename: e.filename, |
|
580 lineNumber: e.lineNumber}}], |
|
581 'name' :'<TOP_LEVEL>' |
|
582 }; |
|
583 |
|
584 events.fail({'exception': e}); |
|
585 events.fireEvent('endTest', obj); |
|
586 } |
|
587 |
|
588 module.__file__ = path; |
|
589 module.__uri__ = uri; |
|
590 |
|
591 return module; |
|
592 } |
|
593 |
|
594 Collector.prototype.loadTestResources = function () { |
|
595 // load resources we want in our tests |
|
596 if (mozmill === undefined) { |
|
597 mozmill = {}; |
|
598 Cu.import("resource://mozmill/driver/mozmill.js", mozmill); |
|
599 } |
|
600 if (mozelement === undefined) { |
|
601 mozelement = {}; |
|
602 Cu.import("resource://mozmill/driver/mozelement.js", mozelement); |
|
603 } |
|
604 } |
|
605 |
|
606 |
|
607 /** |
|
608 * |
|
609 */ |
|
610 function Httpd(aPort) { |
|
611 this.http_port = aPort; |
|
612 |
|
613 while (true) { |
|
614 try { |
|
615 var srv = new HttpServer(); |
|
616 srv.registerContentType("sjs", "sjs"); |
|
617 srv.identity.setPrimary("http", "localhost", this.http_port); |
|
618 srv.start(this.http_port); |
|
619 |
|
620 this._httpd = srv; |
|
621 break; |
|
622 } |
|
623 catch (e) { |
|
624 // Failure most likely due to port conflict |
|
625 this.http_port++; |
|
626 } |
|
627 } |
|
628 } |
|
629 |
|
630 Httpd.prototype.addHttpResource = function (aDir, aPath) { |
|
631 var path = aPath ? ("/" + aPath + "/") : "/"; |
|
632 |
|
633 try { |
|
634 this._httpd.registerDirectory(path, aDir); |
|
635 return 'http://localhost:' + this.http_port + path; |
|
636 } |
|
637 catch (e) { |
|
638 throw Error("Failure to register directory: " + aDir.path); |
|
639 } |
|
640 }; |
|
641 |
|
642 Httpd.prototype.stop = function () { |
|
643 if (!this._httpd) { |
|
644 return; |
|
645 } |
|
646 |
|
647 var shutdown = false; |
|
648 this._httpd.stop(function () { shutdown = true; }); |
|
649 |
|
650 assert.waitFor(function () { |
|
651 return shutdown; |
|
652 }, "Local HTTP server has been stopped", TIMEOUT_SHUTDOWN_HTTPD); |
|
653 |
|
654 this._httpd = null; |
|
655 }; |
|
656 |
|
657 function startHTTPd() { |
|
658 if (!httpd) { |
|
659 // Ensure that we start the HTTP server only once during a session |
|
660 httpd = new Httpd(43336); |
|
661 } |
|
662 } |
|
663 |
|
664 |
|
665 function Runner() { |
|
666 this.collector = new Collector(); |
|
667 this.ended = false; |
|
668 |
|
669 var m = {}; Cu.import('resource://mozmill/driver/mozmill.js', m); |
|
670 this.platform = m.platform; |
|
671 |
|
672 events.fireEvent('startRunner', true); |
|
673 } |
|
674 |
|
675 Runner.prototype.end = function () { |
|
676 if (!this.ended) { |
|
677 this.ended = true; |
|
678 |
|
679 appQuitObserver.runner = null; |
|
680 |
|
681 events.endTest(); |
|
682 events.endModule(events.currentModule); |
|
683 events.fireEvent('endRunner', true); |
|
684 events.persist(); |
|
685 } |
|
686 }; |
|
687 |
|
688 Runner.prototype.runTestFile = function (filename, name) { |
|
689 var module = this.collector.initTestModule(filename, name); |
|
690 this.runTestModule(module); |
|
691 }; |
|
692 |
|
693 Runner.prototype.runTestModule = function (module) { |
|
694 appQuitObserver.runner = this; |
|
695 events.setModule(module); |
|
696 |
|
697 // If setupModule passes, run all the tests. Otherwise mark them as skipped. |
|
698 if (this.execFunction(module.__setupModule__, module)) { |
|
699 for (var test of module.__tests__) { |
|
700 if (events.shutdownRequested) { |
|
701 break; |
|
702 } |
|
703 |
|
704 // If setupTest passes, run the test. Otherwise mark it as skipped. |
|
705 if (this.execFunction(module.__setupTest__, module)) { |
|
706 this.execFunction(test); |
|
707 } else { |
|
708 this.skipFunction(test, module.__setupTest__.__name__ + " failed"); |
|
709 } |
|
710 |
|
711 this.execFunction(module.__teardownTest__, module); |
|
712 } |
|
713 |
|
714 } else { |
|
715 for (var test of module.__tests__) { |
|
716 this.skipFunction(test, module.__setupModule__.__name__ + " failed"); |
|
717 } |
|
718 } |
|
719 |
|
720 this.execFunction(module.__teardownModule__, module); |
|
721 events.endModule(module); |
|
722 }; |
|
723 |
|
724 Runner.prototype.execFunction = function (func, arg) { |
|
725 if (typeof func !== "function" || events.shutdownRequested) { |
|
726 return true; |
|
727 } |
|
728 |
|
729 var isTest = withs.startsWith(func.__name__, "test"); |
|
730 |
|
731 events.setState(isTest ? "test" : func.__name); |
|
732 events.setTest(func); |
|
733 |
|
734 // skip excluded platforms |
|
735 if (func.EXCLUDED_PLATFORMS != undefined) { |
|
736 if (arrays.inArray(func.EXCLUDED_PLATFORMS, this.platform)) { |
|
737 events.skip("Platform exclusion"); |
|
738 events.endTest(func); |
|
739 return false; |
|
740 } |
|
741 } |
|
742 |
|
743 // skip function if requested |
|
744 if (func.__force_skip__ != undefined) { |
|
745 events.skip(func.__force_skip__); |
|
746 events.endTest(func); |
|
747 return false; |
|
748 } |
|
749 |
|
750 // execute the test function |
|
751 try { |
|
752 func(arg); |
|
753 } catch (e) { |
|
754 if (e instanceof errors.ApplicationQuitError) { |
|
755 events.shutdownRequested = true; |
|
756 } else { |
|
757 events.fail({'exception': e, 'test': func}) |
|
758 } |
|
759 } |
|
760 |
|
761 // If a user shutdown has been requested and the function already returned, |
|
762 // we can assume that a shutdown will not happen anymore. We should force a |
|
763 // shutdown then, to prevent the next test from being executed. |
|
764 if (events.isUserShutdown()) { |
|
765 events.shutdownRequested = true; |
|
766 events.toggleUserShutdown(events.userShutdown); |
|
767 } |
|
768 |
|
769 events.endTest(func); |
|
770 return events.currentTest.__fails__.length == 0; |
|
771 }; |
|
772 |
|
773 function runTestFile(filename, name) { |
|
774 var runner = new Runner(); |
|
775 runner.runTestFile(filename, name); |
|
776 runner.end(); |
|
777 |
|
778 return true; |
|
779 } |
|
780 |
|
781 Runner.prototype.skipFunction = function (func, message) { |
|
782 events.setTest(func); |
|
783 events.skip(message); |
|
784 events.endTest(func); |
|
785 }; |