services/sync/tps/extensions/mozmill/resource/modules/frame.js

Wed, 31 Dec 2014 07:53:36 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:53:36 +0100
branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
permissions
-rw-r--r--

Correct small whitespace inconsistency, lost while renaming variables.

     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/. */
     5 var EXPORTED_SYMBOLS = ['Collector','Runner','events', 'runTestFile', 'log',
     6                         'timers', 'persisted', 'shutdownApplication'];
     8 const Cc = Components.classes;
     9 const Ci = Components.interfaces;
    10 const Cu = Components.utils;
    12 const TIMEOUT_SHUTDOWN_HTTPD = 15000;
    14 Cu.import("resource://gre/modules/Services.jsm");
    16 Cu.import('resource://mozmill/stdlib/httpd.js');
    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);
    27 var securableModule = {};
    28 Cu.import('resource://mozmill/stdlib/securable-module.js', securableModule);
    30 var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
    32 var httpd = null;
    33 var persisted = {};
    35 var assert = new assertions.Assert();
    37 var mozmill = undefined;
    38 var mozelement = undefined;
    39 var modules = undefined;
    41 var timers = [];
    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;
    55   if (aFlags) {
    56     flags |= aFlags;
    57   }
    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);
    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   }
    75   var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
    76   timer.initWithCallback(event, 100, Ci.nsITimer.TYPE_ONE_SHOT);
    77 }
    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   }
    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   }
    97   // Fire jsbridge notification, logging notification, listener notifications
    98   events[target] = v;
    99   events.fireEvent(cmeta, target);
   100 }
   103 var events = {
   104   appQuit           : false,
   105   currentModule     : null,
   106   currentState      : null,
   107   currentTest       : null,
   108   shutdownRequested : false,
   109   userShutdown      : null,
   110   userShutdownTimer : null,
   112   listeners       : {},
   113   globalListeners : []
   114 }
   116 events.setState = function (v) {
   117   return stateChangeBase(['dependencies', 'setupModule', 'teardownModule',
   118                           'test', 'setupTest', 'teardownTest', 'collection'],
   119                           null, 'currentState', 'setState', v);
   120 }
   122 events.toggleUserShutdown = function (obj){
   123   if (!this.userShutdown) {
   124     this.userShutdown = obj;
   126     var event = {
   127       notify: function(timer) {
   128        events.toggleUserShutdown(obj);
   129       }
   130     }
   132     this.userShutdownTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   133     this.userShutdownTimer.initWithCallback(event, obj.timeout, Ci.nsITimer.TYPE_ONE_SHOT);
   135   } else {
   136     this.userShutdownTimer.cancel();
   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});
   145       var flags = Ci.nsIAppStartup.eAttemptQuit;
   146       if (events.isRestartShutdown()) {
   147         flags |= Ci.nsIAppStartup.eRestart;
   148       }
   150       shutdownApplication(flags);
   151     }
   152   }
   153 }
   155 events.isUserShutdown = function () {
   156   return this.userShutdown ? this.userShutdown["user"] : false;
   157 }
   159 events.isRestartShutdown = function () {
   160   return this.userShutdown.restart;
   161 }
   163 events.startShutdown = function (obj) {
   164   events.fireEvent('shutdown', obj);
   166   if (obj["user"]) {
   167     events.toggleUserShutdown(obj);
   168   } else {
   169     shutdownApplication(obj.flags);
   170   }
   171 }
   173 events.setTest = function (test) {
   174   test.__start__ = Date.now();
   175   test.__passes__ = [];
   176   test.__fails__ = [];
   178   events.currentTest = test;
   180   var obj = {'filename': events.currentModule.__file__,
   181              'name': test.__name__}
   182   events.fireEvent('setTest', obj);
   183 }
   185 events.endTest = function (test) {
   186   // use the current test unless specified
   187   if (test === undefined) {
   188     test = events.currentTest;
   189   }
   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;
   196   // report the end of a test
   197   test.__end__ = Date.now();
   198   test.status = 'done';
   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__}
   209   if (test.skipped) {
   210     obj['skipped'] = true;
   211     obj.skipped_reason = test.skipped_reason;
   212   }
   214   if (test.meta) {
   215     obj.meta = test.meta;
   216   }
   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 }
   224 events.setModule = function (aModule) {
   225   aModule.__start__ = Date.now();
   226   aModule.__status__ = 'running';
   228   var result = stateChangeBase(null,
   229                                [function (aModule) {return (aModule.__file__ != undefined)}],
   230                                'currentModule', 'setModule', aModule);
   232   return result;
   233 }
   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;
   240   aModule.__end__ = Date.now();
   241   aModule.__status__ = 'done';
   243   var obj = {
   244     'filename': aModule.__file__,
   245     'time_start': aModule.__start__,
   246     'time_end': aModule.__end__
   247   }
   249   events.fireEvent('endModule', obj);
   250 }
   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   }
   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   }
   266   events.fireEvent('pass', obj);
   267 }
   269 events.fail = function (obj) {
   270   var error = obj.exception;
   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   }
   283   // a low level event, such as a keystroke, fails
   284   if (events.currentTest) {
   285     events.currentTest.__fails__.push(obj);
   286   }
   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   }
   296   events.fireEvent('fail', obj);
   297 }
   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;
   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   }
   312   events.fireEvent('skip', reason);
   313 }
   315 events.fireEvent = function (name, obj) {
   316   if (events.appQuit) {
   317     // dump('* Event discarded: ' + name + ' ' + JSON.stringify(obj) + '\n');
   318     return;
   319   }
   321   if (this.listeners[name]) {
   322     for (var i in this.listeners[name]) {
   323       this.listeners[name][i](obj);
   324     }
   325   }
   327   for each(var listener in this.globalListeners) {
   328     listener(name, obj);
   329   }
   330 }
   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 }
   342 events.removeListener = function (listener) {
   343   for (var listenerIndex in this.listeners) {
   344     var e = this.listeners[listenerIndex];
   346     for (var i in e){
   347       if (e[i] == listener) {
   348         this.listeners[listenerIndex] = arrays.remove(e, i);
   349       }
   350     }
   351   }
   353   for (var i in this.globalListeners) {
   354     if (this.globalListeners[i] == listener) {
   355       this.globalListeners = arrays.remove(this.globalListeners, i);
   356     }
   357   }
   358 }
   360 events.persist = function () {
   361   try {
   362     events.fireEvent('persist', persisted);
   363   } catch (e) {
   364     events.fireEvent('error', "persist serialization failed.")
   365   }
   366 }
   368 events.firePythonCallback = function (obj) {
   369   obj['test'] = events.currentModule.__file__;
   370   events.fireEvent('firePythonCallback', obj);
   371 }
   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   }
   382   obj['test_file'] = events.currentModule.__file__;
   383   obj['test_name'] = testName;
   384   events.fireEvent('screenshot', obj);
   385 }
   387 var log = function (obj) {
   388   events.fireEvent('log', obj);
   389 }
   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                  });
   402 try {
   403   Cu.import('resource://jsbridge/modules/Events.jsm');
   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 }
   413 /**
   414  * Observer for notifications when the application is going to shutdown
   415  */
   416 function AppQuitObserver() {
   417   this.runner = null;
   419   Services.obs.addObserver(this, "quit-application-requested", false);
   420 }
   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");
   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});
   434         if (this.runner) {
   435           this.runner.end();
   436         }
   438         if (httpd) {
   439           httpd.stop();
   440         }
   442         events.appQuit = true;
   444         break;
   445     }
   446   }
   447 }
   449 var appQuitObserver = new AppQuitObserver();
   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 }
   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));
   463   return httpd.addHttpResource(fp, aPath);
   464 }
   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__ = [];
   471   for (var i in test_module) {
   472     if (typeof(test_module[i]) == "function") {
   473       test_module[i].__name__ = i;
   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         }
   491         testname = null;
   492         test_module.__tests__.push(test_module[i]);
   493       }
   494     }
   495   }
   497   test_module.collector = this;
   498   test_module.status = 'loaded';
   500   this.test_modules_by_filename[filename] = test_module;
   502   return test_module;
   503 }
   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   });
   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;
   520   this.loadTestResources();
   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;
   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     });
   554     if (modules != undefined) {
   555       loader.modules = modules;
   556     }
   558     var retval = loader.require(mod);
   559     modules = loader.modules;
   561     return retval;
   562   }
   564   if (collector != undefined) {
   565     collector.current_file = file;
   566     collector.current_path = path;
   567   }
   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     };
   584     events.fail({'exception': e});
   585     events.fireEvent('endTest', obj);
   586   }
   588   module.__file__ = path;
   589   module.__uri__ = uri;
   591   return module;
   592 }
   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 }
   607 /**
   608  *
   609  */
   610 function Httpd(aPort) {
   611   this.http_port = aPort;
   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);
   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 }
   630 Httpd.prototype.addHttpResource = function (aDir, aPath) {
   631   var path = aPath ? ("/" + aPath + "/") : "/";
   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 };
   642 Httpd.prototype.stop = function () {
   643   if (!this._httpd) {
   644     return;
   645   }
   647   var shutdown = false;
   648   this._httpd.stop(function () { shutdown = true; });
   650   assert.waitFor(function () {
   651     return shutdown;
   652   }, "Local HTTP server has been stopped", TIMEOUT_SHUTDOWN_HTTPD);
   654   this._httpd = null;
   655 };
   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 }
   665 function Runner() {
   666   this.collector = new Collector();
   667   this.ended = false;
   669   var m = {}; Cu.import('resource://mozmill/driver/mozmill.js', m);
   670   this.platform = m.platform;
   672   events.fireEvent('startRunner', true);
   673 }
   675 Runner.prototype.end = function () {
   676   if (!this.ended) {
   677     this.ended = true;
   679     appQuitObserver.runner = null;
   681     events.endTest();
   682     events.endModule(events.currentModule);
   683     events.fireEvent('endRunner', true);
   684     events.persist();
   685   }
   686 };
   688 Runner.prototype.runTestFile = function (filename, name) {
   689   var module = this.collector.initTestModule(filename, name);
   690   this.runTestModule(module);
   691 };
   693 Runner.prototype.runTestModule = function (module) {
   694   appQuitObserver.runner = this;
   695   events.setModule(module);
   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       }
   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       }
   711       this.execFunction(module.__teardownTest__, module);
   712     }
   714   } else {
   715     for (var test of module.__tests__) {
   716       this.skipFunction(test, module.__setupModule__.__name__ + " failed");
   717     }
   718   }
   720   this.execFunction(module.__teardownModule__, module);
   721   events.endModule(module);
   722 };
   724 Runner.prototype.execFunction = function (func, arg) {
   725   if (typeof func !== "function" || events.shutdownRequested) {
   726     return true;
   727   }
   729   var isTest = withs.startsWith(func.__name__, "test");
   731   events.setState(isTest ? "test" : func.__name);
   732   events.setTest(func);
   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   }
   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   }
   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   }
   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   }
   769   events.endTest(func);
   770   return events.currentTest.__fails__.length == 0;
   771 };
   773 function runTestFile(filename, name) {
   774   var runner = new Runner();
   775   runner.runTestFile(filename, name);
   776   runner.end();
   778   return true;
   779 }
   781 Runner.prototype.skipFunction = function (func, message) {
   782   events.setTest(func);
   783   events.skip(message);
   784   events.endTest(func);
   785 };

mercurial