michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, you can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: var EXPORTED_SYMBOLS = ['Collector','Runner','events', 'runTestFile', 'log', michael@0: 'timers', 'persisted', 'shutdownApplication']; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: michael@0: const TIMEOUT_SHUTDOWN_HTTPD = 15000; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: Cu.import('resource://mozmill/stdlib/httpd.js'); michael@0: michael@0: var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker); michael@0: var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions); michael@0: var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors); michael@0: var os = {}; Cu.import('resource://mozmill/stdlib/os.js', os); michael@0: var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings); michael@0: var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays); michael@0: var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs); michael@0: var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils); michael@0: michael@0: var securableModule = {}; michael@0: Cu.import('resource://mozmill/stdlib/securable-module.js', securableModule); michael@0: michael@0: var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); michael@0: michael@0: var httpd = null; michael@0: var persisted = {}; michael@0: michael@0: var assert = new assertions.Assert(); michael@0: michael@0: var mozmill = undefined; michael@0: var mozelement = undefined; michael@0: var modules = undefined; michael@0: michael@0: var timers = []; michael@0: michael@0: michael@0: /** michael@0: * Shutdown or restart the application michael@0: * michael@0: * @param {boolean} [aFlags=undefined] michael@0: * Additional flags how to handle the shutdown or restart. The attributes michael@0: * eRestarti386 and eRestartx86_64 have not been documented yet. michael@0: * @see https://developer.mozilla.org/nsIAppStartup#Attributes michael@0: */ michael@0: function shutdownApplication(aFlags) { michael@0: var flags = Ci.nsIAppStartup.eForceQuit; michael@0: michael@0: if (aFlags) { michael@0: flags |= aFlags; michael@0: } michael@0: michael@0: // Send a request to shutdown the application. That will allow us and other michael@0: // components to finish up with any shutdown code. Please note that we don't michael@0: // care if other components or add-ons want to prevent this via cancelQuit, michael@0: // we really force the shutdown. michael@0: let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]. michael@0: createInstance(Components.interfaces.nsISupportsPRBool); michael@0: Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null); michael@0: michael@0: // Use a timer to trigger the application restart, which will allow us to michael@0: // send an ACK packet via jsbridge if the method has been called via Python. michael@0: var event = { michael@0: notify: function(timer) { michael@0: Services.startup.quit(flags); michael@0: } michael@0: } michael@0: michael@0: var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: timer.initWithCallback(event, 100, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: } michael@0: michael@0: function stateChangeBase(possibilties, restrictions, target, cmeta, v) { michael@0: if (possibilties) { michael@0: if (!arrays.inArray(possibilties, v)) { michael@0: // TODO Error value not in this.poss michael@0: return; michael@0: } michael@0: } michael@0: michael@0: if (restrictions) { michael@0: for (var i in restrictions) { michael@0: var r = restrictions[i]; michael@0: if (!r(v)) { michael@0: // TODO error value did not pass restriction michael@0: return; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Fire jsbridge notification, logging notification, listener notifications michael@0: events[target] = v; michael@0: events.fireEvent(cmeta, target); michael@0: } michael@0: michael@0: michael@0: var events = { michael@0: appQuit : false, michael@0: currentModule : null, michael@0: currentState : null, michael@0: currentTest : null, michael@0: shutdownRequested : false, michael@0: userShutdown : null, michael@0: userShutdownTimer : null, michael@0: michael@0: listeners : {}, michael@0: globalListeners : [] michael@0: } michael@0: michael@0: events.setState = function (v) { michael@0: return stateChangeBase(['dependencies', 'setupModule', 'teardownModule', michael@0: 'test', 'setupTest', 'teardownTest', 'collection'], michael@0: null, 'currentState', 'setState', v); michael@0: } michael@0: michael@0: events.toggleUserShutdown = function (obj){ michael@0: if (!this.userShutdown) { michael@0: this.userShutdown = obj; michael@0: michael@0: var event = { michael@0: notify: function(timer) { michael@0: events.toggleUserShutdown(obj); michael@0: } michael@0: } michael@0: michael@0: this.userShutdownTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); michael@0: this.userShutdownTimer.initWithCallback(event, obj.timeout, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: michael@0: } else { michael@0: this.userShutdownTimer.cancel(); michael@0: michael@0: // If the application is not going to shutdown, the user shutdown failed and michael@0: // we have to force a shutdown. michael@0: if (!events.appQuit) { michael@0: this.fail({'function':'events.toggleUserShutdown', michael@0: 'message':'Shutdown expected but none detected before timeout', michael@0: 'userShutdown': obj}); michael@0: michael@0: var flags = Ci.nsIAppStartup.eAttemptQuit; michael@0: if (events.isRestartShutdown()) { michael@0: flags |= Ci.nsIAppStartup.eRestart; michael@0: } michael@0: michael@0: shutdownApplication(flags); michael@0: } michael@0: } michael@0: } michael@0: michael@0: events.isUserShutdown = function () { michael@0: return this.userShutdown ? this.userShutdown["user"] : false; michael@0: } michael@0: michael@0: events.isRestartShutdown = function () { michael@0: return this.userShutdown.restart; michael@0: } michael@0: michael@0: events.startShutdown = function (obj) { michael@0: events.fireEvent('shutdown', obj); michael@0: michael@0: if (obj["user"]) { michael@0: events.toggleUserShutdown(obj); michael@0: } else { michael@0: shutdownApplication(obj.flags); michael@0: } michael@0: } michael@0: michael@0: events.setTest = function (test) { michael@0: test.__start__ = Date.now(); michael@0: test.__passes__ = []; michael@0: test.__fails__ = []; michael@0: michael@0: events.currentTest = test; michael@0: michael@0: var obj = {'filename': events.currentModule.__file__, michael@0: 'name': test.__name__} michael@0: events.fireEvent('setTest', obj); michael@0: } michael@0: michael@0: events.endTest = function (test) { michael@0: // use the current test unless specified michael@0: if (test === undefined) { michael@0: test = events.currentTest; michael@0: } michael@0: michael@0: // If no test is set it has already been reported. Beside that we don't want michael@0: // to report it a second time. michael@0: if (!test || test.status === 'done') michael@0: return; michael@0: michael@0: // report the end of a test michael@0: test.__end__ = Date.now(); michael@0: test.status = 'done'; michael@0: michael@0: var obj = {'filename': events.currentModule.__file__, michael@0: 'passed': test.__passes__.length, michael@0: 'failed': test.__fails__.length, michael@0: 'passes': test.__passes__, michael@0: 'fails' : test.__fails__, michael@0: 'name' : test.__name__, michael@0: 'time_start': test.__start__, michael@0: 'time_end': test.__end__} michael@0: michael@0: if (test.skipped) { michael@0: obj['skipped'] = true; michael@0: obj.skipped_reason = test.skipped_reason; michael@0: } michael@0: michael@0: if (test.meta) { michael@0: obj.meta = test.meta; michael@0: } michael@0: michael@0: // Report the test result only if the test is a true test or if it is failing michael@0: if (withs.startsWith(test.__name__, "test") || test.__fails__.length > 0) { michael@0: events.fireEvent('endTest', obj); michael@0: } michael@0: } michael@0: michael@0: events.setModule = function (aModule) { michael@0: aModule.__start__ = Date.now(); michael@0: aModule.__status__ = 'running'; michael@0: michael@0: var result = stateChangeBase(null, michael@0: [function (aModule) {return (aModule.__file__ != undefined)}], michael@0: 'currentModule', 'setModule', aModule); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: events.endModule = function (aModule) { michael@0: // It should only reported once, so check if it already has been done michael@0: if (aModule.__status__ === 'done') michael@0: return; michael@0: michael@0: aModule.__end__ = Date.now(); michael@0: aModule.__status__ = 'done'; michael@0: michael@0: var obj = { michael@0: 'filename': aModule.__file__, michael@0: 'time_start': aModule.__start__, michael@0: 'time_end': aModule.__end__ michael@0: } michael@0: michael@0: events.fireEvent('endModule', obj); michael@0: } michael@0: michael@0: events.pass = function (obj) { michael@0: // a low level event, such as a keystroke, succeeds michael@0: if (events.currentTest) { michael@0: events.currentTest.__passes__.push(obj); michael@0: } michael@0: michael@0: for each (var timer in timers) { michael@0: timer.actions.push( michael@0: {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__, michael@0: "obj": obj, michael@0: "result": "pass"} michael@0: ); michael@0: } michael@0: michael@0: events.fireEvent('pass', obj); michael@0: } michael@0: michael@0: events.fail = function (obj) { michael@0: var error = obj.exception; michael@0: michael@0: if (error) { michael@0: // Error objects aren't enumerable https://bugzilla.mozilla.org/show_bug.cgi?id=637207 michael@0: obj.exception = { michael@0: name: error.name, michael@0: message: error.message, michael@0: lineNumber: error.lineNumber, michael@0: fileName: error.fileName, michael@0: stack: error.stack michael@0: }; michael@0: } michael@0: michael@0: // a low level event, such as a keystroke, fails michael@0: if (events.currentTest) { michael@0: events.currentTest.__fails__.push(obj); michael@0: } michael@0: michael@0: for each (var time in timers) { michael@0: timer.actions.push( michael@0: {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__, michael@0: "obj": obj, michael@0: "result": "fail"} michael@0: ); michael@0: } michael@0: michael@0: events.fireEvent('fail', obj); michael@0: } michael@0: michael@0: events.skip = function (reason) { michael@0: // this is used to report skips associated with setupModule and nothing else michael@0: events.currentTest.skipped = true; michael@0: events.currentTest.skipped_reason = reason; michael@0: michael@0: for (var timer of timers) { michael@0: timer.actions.push( michael@0: {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__, michael@0: "obj": reason, michael@0: "result": "skip"} michael@0: ); michael@0: } michael@0: michael@0: events.fireEvent('skip', reason); michael@0: } michael@0: michael@0: events.fireEvent = function (name, obj) { michael@0: if (events.appQuit) { michael@0: // dump('* Event discarded: ' + name + ' ' + JSON.stringify(obj) + '\n'); michael@0: return; michael@0: } michael@0: michael@0: if (this.listeners[name]) { michael@0: for (var i in this.listeners[name]) { michael@0: this.listeners[name][i](obj); michael@0: } michael@0: } michael@0: michael@0: for each(var listener in this.globalListeners) { michael@0: listener(name, obj); michael@0: } michael@0: } michael@0: michael@0: events.addListener = function (name, listener) { michael@0: if (this.listeners[name]) { michael@0: this.listeners[name].push(listener); michael@0: } else if (name == '') { michael@0: this.globalListeners.push(listener) michael@0: } else { michael@0: this.listeners[name] = [listener]; michael@0: } michael@0: } michael@0: michael@0: events.removeListener = function (listener) { michael@0: for (var listenerIndex in this.listeners) { michael@0: var e = this.listeners[listenerIndex]; michael@0: michael@0: for (var i in e){ michael@0: if (e[i] == listener) { michael@0: this.listeners[listenerIndex] = arrays.remove(e, i); michael@0: } michael@0: } michael@0: } michael@0: michael@0: for (var i in this.globalListeners) { michael@0: if (this.globalListeners[i] == listener) { michael@0: this.globalListeners = arrays.remove(this.globalListeners, i); michael@0: } michael@0: } michael@0: } michael@0: michael@0: events.persist = function () { michael@0: try { michael@0: events.fireEvent('persist', persisted); michael@0: } catch (e) { michael@0: events.fireEvent('error', "persist serialization failed.") michael@0: } michael@0: } michael@0: michael@0: events.firePythonCallback = function (obj) { michael@0: obj['test'] = events.currentModule.__file__; michael@0: events.fireEvent('firePythonCallback', obj); michael@0: } michael@0: michael@0: events.screenshot = function (obj) { michael@0: // Find the name of the test function michael@0: for (var attr in events.currentModule) { michael@0: if (events.currentModule[attr] == events.currentTest) { michael@0: var testName = attr; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: obj['test_file'] = events.currentModule.__file__; michael@0: obj['test_name'] = testName; michael@0: events.fireEvent('screenshot', obj); michael@0: } michael@0: michael@0: var log = function (obj) { michael@0: events.fireEvent('log', obj); michael@0: } michael@0: michael@0: // Register the listeners michael@0: broker.addObject({'endTest': events.endTest, michael@0: 'fail': events.fail, michael@0: 'firePythonCallback': events.firePythonCallback, michael@0: 'log': log, michael@0: 'pass': events.pass, michael@0: 'persist': events.persist, michael@0: 'screenshot': events.screenshot, michael@0: 'shutdown': events.startShutdown, michael@0: }); michael@0: michael@0: try { michael@0: Cu.import('resource://jsbridge/modules/Events.jsm'); michael@0: michael@0: events.addListener('', function (name, obj) { michael@0: Events.fireEvent('mozmill.' + name, obj); michael@0: }); michael@0: } catch (e) { michael@0: Services.console.logStringMessage("Event module of JSBridge not available."); michael@0: } michael@0: michael@0: michael@0: /** michael@0: * Observer for notifications when the application is going to shutdown michael@0: */ michael@0: function AppQuitObserver() { michael@0: this.runner = null; michael@0: michael@0: Services.obs.addObserver(this, "quit-application-requested", false); michael@0: } michael@0: michael@0: AppQuitObserver.prototype = { michael@0: observe: function (aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case "quit-application-requested": michael@0: Services.obs.removeObserver(this, "quit-application-requested"); michael@0: michael@0: // If we observe a quit notification make sure to send the michael@0: // results of the current test. In those cases we don't reach michael@0: // the equivalent code in runTestModule() michael@0: events.pass({'message': 'AppQuitObserver: ' + JSON.stringify(aData), michael@0: 'userShutdown': events.userShutdown}); michael@0: michael@0: if (this.runner) { michael@0: this.runner.end(); michael@0: } michael@0: michael@0: if (httpd) { michael@0: httpd.stop(); michael@0: } michael@0: michael@0: events.appQuit = true; michael@0: michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: var appQuitObserver = new AppQuitObserver(); michael@0: michael@0: /** michael@0: * The collector handles HTTPd.js and initilizing the module michael@0: */ michael@0: function Collector() { michael@0: this.test_modules_by_filename = {}; michael@0: this.testing = []; michael@0: } michael@0: michael@0: Collector.prototype.addHttpResource = function (aDirectory, aPath) { michael@0: var fp = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); michael@0: fp.initWithPath(os.abspath(aDirectory, this.current_file)); michael@0: michael@0: return httpd.addHttpResource(fp, aPath); michael@0: } michael@0: michael@0: Collector.prototype.initTestModule = function (filename, testname) { michael@0: var test_module = this.loadFile(filename, this); michael@0: var has_restarted = !(testname == null); michael@0: test_module.__tests__ = []; michael@0: michael@0: for (var i in test_module) { michael@0: if (typeof(test_module[i]) == "function") { michael@0: test_module[i].__name__ = i; michael@0: michael@0: // Only run setupModule if we are a single test OR if we are the first michael@0: // test of a restart chain (don't run it prior to members in a restart michael@0: // chain) michael@0: if (i == "setupModule" && !has_restarted) { michael@0: test_module.__setupModule__ = test_module[i]; michael@0: } else if (i == "setupTest") { michael@0: test_module.__setupTest__ = test_module[i]; michael@0: } else if (i == "teardownTest") { michael@0: test_module.__teardownTest__ = test_module[i]; michael@0: } else if (i == "teardownModule") { michael@0: test_module.__teardownModule__ = test_module[i]; michael@0: } else if (withs.startsWith(i, "test")) { michael@0: if (testname && (i != testname)) { michael@0: continue; michael@0: } michael@0: michael@0: testname = null; michael@0: test_module.__tests__.push(test_module[i]); michael@0: } michael@0: } michael@0: } michael@0: michael@0: test_module.collector = this; michael@0: test_module.status = 'loaded'; michael@0: michael@0: this.test_modules_by_filename[filename] = test_module; michael@0: michael@0: return test_module; michael@0: } michael@0: michael@0: Collector.prototype.loadFile = function (path, collector) { michael@0: var moduleLoader = new securableModule.Loader({ michael@0: rootPaths: ["resource://mozmill/modules/"], michael@0: defaultPrincipal: "system", michael@0: globals : { Cc: Cc, michael@0: Ci: Ci, michael@0: Cu: Cu, michael@0: Cr: Components.results} michael@0: }); michael@0: michael@0: // load a test module from a file and add some candy michael@0: var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); michael@0: file.initWithPath(path); michael@0: var uri = Services.io.newFileURI(file).spec; michael@0: michael@0: this.loadTestResources(); michael@0: michael@0: var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal(); michael@0: var module = new Components.utils.Sandbox(systemPrincipal); michael@0: module.assert = new assertions.Assert(); michael@0: module.Cc = Cc; michael@0: module.Ci = Ci; michael@0: module.Cr = Components.results; michael@0: module.Cu = Cu; michael@0: module.collector = collector; michael@0: module.driver = moduleLoader.require("driver"); michael@0: module.elementslib = mozelement; michael@0: module.errors = errors; michael@0: module.expect = new assertions.Expect(); michael@0: module.findElement = mozelement; michael@0: module.log = log; michael@0: module.mozmill = mozmill; michael@0: module.persisted = persisted; michael@0: michael@0: module.require = function (mod) { michael@0: var loader = new securableModule.Loader({ michael@0: rootPaths: [Services.io.newFileURI(file.parent).spec, michael@0: "resource://mozmill/modules/"], michael@0: defaultPrincipal: "system", michael@0: globals : { mozmill: mozmill, michael@0: elementslib: mozelement, // This a quick hack to maintain backwards compatibility with 1.5.x michael@0: findElement: mozelement, michael@0: persisted: persisted, michael@0: Cc: Cc, michael@0: Ci: Ci, michael@0: Cu: Cu, michael@0: log: log } michael@0: }); michael@0: michael@0: if (modules != undefined) { michael@0: loader.modules = modules; michael@0: } michael@0: michael@0: var retval = loader.require(mod); michael@0: modules = loader.modules; michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: if (collector != undefined) { michael@0: collector.current_file = file; michael@0: collector.current_path = path; michael@0: } michael@0: michael@0: try { michael@0: Services.scriptloader.loadSubScript(uri, module, "UTF-8"); michael@0: } catch (e) { michael@0: var obj = { michael@0: 'filename': path, michael@0: 'passed': 0, michael@0: 'failed': 1, michael@0: 'passes': [], michael@0: 'fails' : [{'exception' : { michael@0: message: e.message, michael@0: filename: e.filename, michael@0: lineNumber: e.lineNumber}}], michael@0: 'name' :'' michael@0: }; michael@0: michael@0: events.fail({'exception': e}); michael@0: events.fireEvent('endTest', obj); michael@0: } michael@0: michael@0: module.__file__ = path; michael@0: module.__uri__ = uri; michael@0: michael@0: return module; michael@0: } michael@0: michael@0: Collector.prototype.loadTestResources = function () { michael@0: // load resources we want in our tests michael@0: if (mozmill === undefined) { michael@0: mozmill = {}; michael@0: Cu.import("resource://mozmill/driver/mozmill.js", mozmill); michael@0: } michael@0: if (mozelement === undefined) { michael@0: mozelement = {}; michael@0: Cu.import("resource://mozmill/driver/mozelement.js", mozelement); michael@0: } michael@0: } michael@0: michael@0: michael@0: /** michael@0: * michael@0: */ michael@0: function Httpd(aPort) { michael@0: this.http_port = aPort; michael@0: michael@0: while (true) { michael@0: try { michael@0: var srv = new HttpServer(); michael@0: srv.registerContentType("sjs", "sjs"); michael@0: srv.identity.setPrimary("http", "localhost", this.http_port); michael@0: srv.start(this.http_port); michael@0: michael@0: this._httpd = srv; michael@0: break; michael@0: } michael@0: catch (e) { michael@0: // Failure most likely due to port conflict michael@0: this.http_port++; michael@0: } michael@0: } michael@0: } michael@0: michael@0: Httpd.prototype.addHttpResource = function (aDir, aPath) { michael@0: var path = aPath ? ("/" + aPath + "/") : "/"; michael@0: michael@0: try { michael@0: this._httpd.registerDirectory(path, aDir); michael@0: return 'http://localhost:' + this.http_port + path; michael@0: } michael@0: catch (e) { michael@0: throw Error("Failure to register directory: " + aDir.path); michael@0: } michael@0: }; michael@0: michael@0: Httpd.prototype.stop = function () { michael@0: if (!this._httpd) { michael@0: return; michael@0: } michael@0: michael@0: var shutdown = false; michael@0: this._httpd.stop(function () { shutdown = true; }); michael@0: michael@0: assert.waitFor(function () { michael@0: return shutdown; michael@0: }, "Local HTTP server has been stopped", TIMEOUT_SHUTDOWN_HTTPD); michael@0: michael@0: this._httpd = null; michael@0: }; michael@0: michael@0: function startHTTPd() { michael@0: if (!httpd) { michael@0: // Ensure that we start the HTTP server only once during a session michael@0: httpd = new Httpd(43336); michael@0: } michael@0: } michael@0: michael@0: michael@0: function Runner() { michael@0: this.collector = new Collector(); michael@0: this.ended = false; michael@0: michael@0: var m = {}; Cu.import('resource://mozmill/driver/mozmill.js', m); michael@0: this.platform = m.platform; michael@0: michael@0: events.fireEvent('startRunner', true); michael@0: } michael@0: michael@0: Runner.prototype.end = function () { michael@0: if (!this.ended) { michael@0: this.ended = true; michael@0: michael@0: appQuitObserver.runner = null; michael@0: michael@0: events.endTest(); michael@0: events.endModule(events.currentModule); michael@0: events.fireEvent('endRunner', true); michael@0: events.persist(); michael@0: } michael@0: }; michael@0: michael@0: Runner.prototype.runTestFile = function (filename, name) { michael@0: var module = this.collector.initTestModule(filename, name); michael@0: this.runTestModule(module); michael@0: }; michael@0: michael@0: Runner.prototype.runTestModule = function (module) { michael@0: appQuitObserver.runner = this; michael@0: events.setModule(module); michael@0: michael@0: // If setupModule passes, run all the tests. Otherwise mark them as skipped. michael@0: if (this.execFunction(module.__setupModule__, module)) { michael@0: for (var test of module.__tests__) { michael@0: if (events.shutdownRequested) { michael@0: break; michael@0: } michael@0: michael@0: // If setupTest passes, run the test. Otherwise mark it as skipped. michael@0: if (this.execFunction(module.__setupTest__, module)) { michael@0: this.execFunction(test); michael@0: } else { michael@0: this.skipFunction(test, module.__setupTest__.__name__ + " failed"); michael@0: } michael@0: michael@0: this.execFunction(module.__teardownTest__, module); michael@0: } michael@0: michael@0: } else { michael@0: for (var test of module.__tests__) { michael@0: this.skipFunction(test, module.__setupModule__.__name__ + " failed"); michael@0: } michael@0: } michael@0: michael@0: this.execFunction(module.__teardownModule__, module); michael@0: events.endModule(module); michael@0: }; michael@0: michael@0: Runner.prototype.execFunction = function (func, arg) { michael@0: if (typeof func !== "function" || events.shutdownRequested) { michael@0: return true; michael@0: } michael@0: michael@0: var isTest = withs.startsWith(func.__name__, "test"); michael@0: michael@0: events.setState(isTest ? "test" : func.__name); michael@0: events.setTest(func); michael@0: michael@0: // skip excluded platforms michael@0: if (func.EXCLUDED_PLATFORMS != undefined) { michael@0: if (arrays.inArray(func.EXCLUDED_PLATFORMS, this.platform)) { michael@0: events.skip("Platform exclusion"); michael@0: events.endTest(func); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // skip function if requested michael@0: if (func.__force_skip__ != undefined) { michael@0: events.skip(func.__force_skip__); michael@0: events.endTest(func); michael@0: return false; michael@0: } michael@0: michael@0: // execute the test function michael@0: try { michael@0: func(arg); michael@0: } catch (e) { michael@0: if (e instanceof errors.ApplicationQuitError) { michael@0: events.shutdownRequested = true; michael@0: } else { michael@0: events.fail({'exception': e, 'test': func}) michael@0: } michael@0: } michael@0: michael@0: // If a user shutdown has been requested and the function already returned, michael@0: // we can assume that a shutdown will not happen anymore. We should force a michael@0: // shutdown then, to prevent the next test from being executed. michael@0: if (events.isUserShutdown()) { michael@0: events.shutdownRequested = true; michael@0: events.toggleUserShutdown(events.userShutdown); michael@0: } michael@0: michael@0: events.endTest(func); michael@0: return events.currentTest.__fails__.length == 0; michael@0: }; michael@0: michael@0: function runTestFile(filename, name) { michael@0: var runner = new Runner(); michael@0: runner.runTestFile(filename, name); michael@0: runner.end(); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: Runner.prototype.skipFunction = function (func, message) { michael@0: events.setTest(func); michael@0: events.skip(message); michael@0: events.endTest(func); michael@0: };