addon-sdk/source/lib/sdk/test/harness.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     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 "use strict";
     6 module.metadata = {
     7   "stability": "experimental"
     8 };
    10 const { Cc, Ci, Cu } = require("chrome");
    11 const { Loader } = require('./loader');
    12 const { serializeStack, parseStack  } = require("toolkit/loader");
    13 const { setTimeout } = require('../timers');
    14 const { PlainTextConsole } = require("../console/plain-text");
    15 const { when: unload } = require("../system/unload");
    16 const { format, fromException }  = require("../console/traceback");
    17 const system = require("../system");
    18 const memory = require('../deprecated/memory');
    19 const { gc: gcPromise } = require('./memory');
    20 const { defer } = require('../core/promise');
    22 // Trick manifest builder to make it think we need these modules ?
    23 const unit = require("../deprecated/unit-test");
    24 const test = require("../../test");
    25 const url = require("../url");
    27 function emptyPromise() {
    28   let { promise, resolve } = defer();
    29   resolve();
    30   return promise;
    31 }
    33 var cService = Cc['@mozilla.org/consoleservice;1'].getService()
    34                .QueryInterface(Ci.nsIConsoleService);
    36 // The console used to log messages
    37 var testConsole;
    39 // Cuddlefish loader in which we load and execute tests.
    40 var loader;
    42 // Function to call when we're done running tests.
    43 var onDone;
    45 // Function to print text to a console, w/o CR at the end.
    46 var print;
    48 // How many more times to run all tests.
    49 var iterationsLeft;
    51 // Whether to report memory profiling information.
    52 var profileMemory;
    54 // Whether we should stop as soon as a test reports a failure.
    55 var stopOnError;
    57 // Function to call to retrieve a list of tests to execute
    58 var findAndRunTests;
    60 // Combined information from all test runs.
    61 var results = {
    62   passed: 0,
    63   failed: 0,
    64   testRuns: []
    65 };
    67 // A list of the compartments and windows loaded after startup
    68 var startLeaks;
    70 // JSON serialization of last memory usage stats; we keep it stringified
    71 // so we don't actually change the memory usage stats (in terms of objects)
    72 // of the JSRuntime we're profiling.
    73 var lastMemoryUsage;
    75 function analyzeRawProfilingData(data) {
    76   var graph = data.graph;
    77   var shapes = {};
    79   // Convert keys in the graph from strings to ints.
    80   // TODO: Can we get rid of this ridiculousness?
    81   var newGraph = {};
    82   for (id in graph) {
    83     newGraph[parseInt(id)] = graph[id];
    84   }
    85   graph = newGraph;
    87   var modules = 0;
    88   var moduleIds = [];
    89   var moduleObjs = {UNKNOWN: 0};
    90   for (let name in data.namedObjects) {
    91     moduleObjs[name] = 0;
    92     moduleIds[data.namedObjects[name]] = name;
    93     modules++;
    94   }
    96   var count = 0;
    97   for (id in graph) {
    98     var parent = graph[id].parent;
    99     while (parent) {
   100       if (parent in moduleIds) {
   101         var name = moduleIds[parent];
   102         moduleObjs[name]++;
   103         break;
   104       }
   105       if (!(parent in graph)) {
   106         moduleObjs.UNKNOWN++;
   107         break;
   108       }
   109       parent = graph[parent].parent;
   110     }
   111     count++;
   112   }
   114   print("\nobject count is " + count + " in " + modules + " modules" +
   115         " (" + data.totalObjectCount + " across entire JS runtime)\n");
   116   if (lastMemoryUsage) {
   117     var last = JSON.parse(lastMemoryUsage);
   118     var diff = {
   119       moduleObjs: dictDiff(last.moduleObjs, moduleObjs),
   120       totalObjectClasses: dictDiff(last.totalObjectClasses,
   121                                    data.totalObjectClasses)
   122     };
   124     for (let name in diff.moduleObjs)
   125       print("  " + diff.moduleObjs[name] + " in " + name + "\n");
   126     for (let name in diff.totalObjectClasses)
   127       print("  " + diff.totalObjectClasses[name] + " instances of " +
   128             name + "\n");
   129   }
   130   lastMemoryUsage = JSON.stringify(
   131     {moduleObjs: moduleObjs,
   132      totalObjectClasses: data.totalObjectClasses}
   133   );
   134 }
   136 function dictDiff(last, curr) {
   137   var diff = {};
   139   for (let name in last) {
   140     var result = (curr[name] || 0) - last[name];
   141     if (result)
   142       diff[name] = (result > 0 ? "+" : "") + result;
   143   }
   144   for (let name in curr) {
   145     var result = curr[name] - (last[name] || 0);
   146     if (result)
   147       diff[name] = (result > 0 ? "+" : "") + result;
   148   }
   149   return diff;
   150 }
   152 function reportMemoryUsage() {
   153   if (!profileMemory) {
   154     return emptyPromise();
   155   }
   157   return gcPromise().then((function () {
   158     var mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
   159               .getService(Ci.nsIMemoryReporterManager);
   160     let count = 0;
   161     function logReporter(process, path, kind, units, amount, description) {
   162       print(((++count == 1) ? "\n" : "") + description + ": " + amount + "\n");
   163     }
   164     mgr.getReportsForThisProcess(logReporter, null);
   166     var weakrefs = [info.weakref.get()
   167                     for each (info in memory.getObjects())];
   168     weakrefs = [weakref for each (weakref in weakrefs) if (weakref)];
   169     print("Tracked memory objects in testing sandbox: " + weakrefs.length + "\n");
   170   }));
   171 }
   173 var gWeakrefInfo;
   175 function checkMemory() {
   176   return gcPromise().then(_ => {
   177     let leaks = getPotentialLeaks();
   179     let compartmentURLs = Object.keys(leaks.compartments).filter(function(url) {
   180       return !(url in startLeaks.compartments);
   181     });
   183     let windowURLs = Object.keys(leaks.windows).filter(function(url) {
   184       return !(url in startLeaks.windows);
   185     });
   187     for (let url of compartmentURLs)
   188       console.warn("LEAKED", leaks.compartments[url]);
   190     for (let url of windowURLs)
   191       console.warn("LEAKED", leaks.windows[url]);
   192   }).then(showResults);
   193 }
   195 function showResults() {
   196   let { promise, resolve } = defer();
   198   if (gWeakrefInfo) {
   199     gWeakrefInfo.forEach(
   200       function(info) {
   201         var ref = info.weakref.get();
   202         if (ref !== null) {
   203           var data = ref.__url__ ? ref.__url__ : ref;
   204           var warning = data == "[object Object]"
   205             ? "[object " + data.constructor.name + "(" +
   206               [p for (p in data)].join(", ") + ")]"
   207             : data;
   208           console.warn("LEAK", warning, info.bin);
   209         }
   210       }
   211     );
   212   }
   214   onDone(results);
   216   resolve();
   217   return promise;
   218 }
   220 function cleanup() {
   221   let coverObject = {};
   222   try {
   223     for (let name in loader.modules)
   224       memory.track(loader.modules[name],
   225                            "module global scope: " + name);
   226       memory.track(loader, "Cuddlefish Loader");
   228     if (profileMemory) {
   229       gWeakrefInfo = [{ weakref: info.weakref, bin: info.bin }
   230                       for each (info in memory.getObjects())];
   231     }
   233     loader.unload();
   235     if (loader.globals.console.errorsLogged && !results.failed) {
   236       results.failed++;
   237       console.error("warnings and/or errors were logged.");
   238     }
   240     if (consoleListener.errorsLogged && !results.failed) {
   241       console.warn(consoleListener.errorsLogged + " " +
   242                    "warnings or errors were logged to the " +
   243                    "platform's nsIConsoleService, which could " +
   244                    "be of no consequence; however, they could also " +
   245                    "be indicative of aberrant behavior.");
   246     }
   248     // read the code coverage object, if it exists, from CoverJS-moz
   249     if (typeof loader.globals.global == "object") {
   250       coverObject = loader.globals.global['__$coverObject'] || {};
   251     }
   253     consoleListener.errorsLogged = 0;
   254     loader = null;
   256     memory.gc();
   257   }
   258   catch (e) {
   259     results.failed++;
   260     console.error("unload.send() threw an exception.");
   261     console.exception(e);
   262   };
   264   setTimeout(require('@test/options').checkMemory ? checkMemory : showResults, 1);
   266   // dump the coverobject
   267   if (Object.keys(coverObject).length){
   268     const self = require('sdk/self');
   269     const {pathFor} = require("sdk/system");
   270     let file = require('sdk/io/file');
   271     const {env} = require('sdk/system/environment');
   272     console.log("CWD:", env.PWD);
   273     let out = file.join(env.PWD,'coverstats-'+self.id+'.json');
   274     console.log('coverstats:', out);
   275     let outfh = file.open(out,'w');
   276     outfh.write(JSON.stringify(coverObject,null,2));
   277     outfh.flush();
   278     outfh.close();
   279   }
   280 }
   282 function getPotentialLeaks() {
   283   memory.gc();
   285   // Things we can assume are part of the platform and so aren't leaks
   286   let WHITELIST_BASE_URLS = [
   287     "chrome://",
   288     "resource:///",
   289     "resource://app/",
   290     "resource://gre/",
   291     "resource://gre-resources/",
   292     "resource://pdf.js/",
   293     "resource://pdf.js.components/",
   294     "resource://services-common/",
   295     "resource://services-crypto/",
   296     "resource://services-sync/"
   297   ];
   299   let ioService = Cc["@mozilla.org/network/io-service;1"].
   300                  getService(Ci.nsIIOService);
   301   let uri = ioService.newURI("chrome://global/content/", "UTF-8", null);
   302   let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
   303                   getService(Ci.nsIChromeRegistry);
   304   uri = chromeReg.convertChromeURL(uri);
   305   let spec = uri.spec;
   306   let pos = spec.indexOf("!/");
   307   WHITELIST_BASE_URLS.push(spec.substring(0, pos + 2));
   309   let zoneRegExp = new RegExp("^explicit/js-non-window/zones/zone[^/]+/compartment\\((.+)\\)");
   310   let compartmentRegexp = new RegExp("^explicit/js-non-window/compartments/non-window-global/compartment\\((.+)\\)/");
   311   let compartmentDetails = new RegExp("^([^,]+)(?:, (.+?))?(?: \\(from: (.*)\\))?$");
   312   let windowRegexp = new RegExp("^explicit/window-objects/top\\((.*)\\)/active");
   313   let windowDetails = new RegExp("^(.*), id=.*$");
   315   function isPossibleLeak(item) {
   316     if (!item.location)
   317       return false;
   319     for (let whitelist of WHITELIST_BASE_URLS) {
   320       if (item.location.substring(0, whitelist.length) == whitelist)
   321         return false;
   322     }
   324     return true;
   325   }
   327   let compartments = {};
   328   let windows = {};
   329   function logReporter(process, path, kind, units, amount, description) {
   330     let matches;
   332     if ((matches = compartmentRegexp.exec(path)) || (matches = zoneRegExp.exec(path))) {
   333       if (matches[1] in compartments)
   334         return;
   336       let details = compartmentDetails.exec(matches[1]);
   337       if (!details) {
   338         console.error("Unable to parse compartment detail " + matches[1]);
   339         return;
   340       }
   342       let item = {
   343         path: matches[1],
   344         principal: details[1],
   345         location: details[2] ? details[2].replace("\\", "/", "g") : undefined,
   346         source: details[3] ? details[3].split(" -> ").reverse() : undefined,
   347         toString: function() this.location
   348       };
   350       if (!isPossibleLeak(item))
   351         return;
   353       compartments[matches[1]] = item;
   354       return;
   355     }
   357     if (matches = windowRegexp.exec(path)) {
   358       if (matches[1] in windows)
   359         return;
   361       let details = windowDetails.exec(matches[1]);
   362       if (!details) {
   363         console.error("Unable to parse window detail " + matches[1]);
   364         return;
   365       }
   367       let item = {
   368         path: matches[1],
   369         location: details[1].replace("\\", "/", "g"),
   370         source: [details[1].replace("\\", "/", "g")],
   371         toString: function() this.location
   372       };
   374       if (!isPossibleLeak(item))
   375         return;
   377       windows[matches[1]] = item;
   378     }
   379   }
   381   Cc["@mozilla.org/memory-reporter-manager;1"]
   382     .getService(Ci.nsIMemoryReporterManager)
   383     .getReportsForThisProcess(logReporter, null);
   385   return { compartments: compartments, windows: windows };
   386 }
   388 function nextIteration(tests) {
   389   if (tests) {
   390     results.passed += tests.passed;
   391     results.failed += tests.failed;
   393     reportMemoryUsage().then(_ => {
   394       let testRun = [];
   395       for each (let test in tests.testRunSummary) {
   396         let testCopy = {};
   397         for (let info in test) {
   398           testCopy[info] = test[info];
   399         }
   400         testRun.push(testCopy);
   401       }
   403       results.testRuns.push(testRun);
   404       iterationsLeft--;
   406       checkForEnd();
   407     })
   408   }
   409   else {
   410     checkForEnd();
   411   }
   412 }
   414 function checkForEnd() {
   415   if (iterationsLeft && (!stopOnError || results.failed == 0)) {
   416     // Pass the loader which has a hooked console that doesn't dispatch
   417     // errors to the JS console and avoid firing false alarm in our
   418     // console listener
   419     findAndRunTests(loader, nextIteration);
   420   }
   421   else {
   422     setTimeout(cleanup, 0);
   423   }
   424 }
   426 var POINTLESS_ERRORS = [
   427   'Invalid chrome URI:',
   428   'OpenGL LayerManager Initialized Succesfully.',
   429   '[JavaScript Error: "TelemetryStopwatch:',
   430   'reference to undefined property',
   431   '[JavaScript Error: "The character encoding of the HTML document was ' +
   432     'not declared.',
   433   '[Javascript Warning: "Error: Failed to preserve wrapper of wrapped ' +
   434     'native weak map key',
   435   '[JavaScript Warning: "Duplicate resource declaration for',
   436   'file: "chrome://browser/content/',
   437   'file: "chrome://global/content/',
   438   '[JavaScript Warning: "The character encoding of a framed document was ' +
   439     'not declared.'
   440 ];
   442 var consoleListener = {
   443   errorsLogged: 0,
   444   observe: function(object) {
   445     if (!(object instanceof Ci.nsIScriptError))
   446       return;
   447     this.errorsLogged++;
   448     var message = object.QueryInterface(Ci.nsIConsoleMessage).message;
   449     var pointless = [err for each (err in POINTLESS_ERRORS)
   450                          if (message.indexOf(err) >= 0)];
   451     if (pointless.length == 0 && message)
   452       testConsole.log(message);
   453   }
   454 };
   456 function TestRunnerConsole(base, options) {
   457   this.__proto__ = {
   458     errorsLogged: 0,
   459     warn: function warn() {
   460       this.errorsLogged++;
   461       base.warn.apply(base, arguments);
   462     },
   463     error: function error() {
   464       this.errorsLogged++;
   465       base.error.apply(base, arguments);
   466     },
   467     info: function info(first) {
   468       if (options.verbose)
   469         base.info.apply(base, arguments);
   470       else
   471         if (first == "pass:")
   472           print(".");
   473     },
   474     __proto__: base
   475   };
   476 }
   478 function stringify(arg) {
   479   try {
   480     return String(arg);
   481   }
   482   catch(ex) {
   483     return "<toString() error>";
   484   }
   485 }
   487 function stringifyArgs(args) {
   488   return Array.map(args, stringify).join(" ");
   489 }
   491 function TestRunnerTinderboxConsole(base, options) {
   492   this.base = base;
   493   this.print = options.print;
   494   this.verbose = options.verbose;
   495   this.errorsLogged = 0;
   497   // Binding all the public methods to an instance so that they can be used
   498   // as callback / listener functions straightaway.
   499   this.log = this.log.bind(this);
   500   this.info = this.info.bind(this);
   501   this.warn = this.warn.bind(this);
   502   this.error = this.error.bind(this);
   503   this.debug = this.debug.bind(this);
   504   this.exception = this.exception.bind(this);
   505   this.trace = this.trace.bind(this);
   506 };
   508 TestRunnerTinderboxConsole.prototype = {
   509   testMessage: function testMessage(pass, expected, test, message) {
   510     let type = "TEST-";
   511     if (expected) {
   512       if (pass)
   513         type += "PASS";
   514       else
   515         type += "KNOWN-FAIL";
   516     }
   517     else {
   518       this.errorsLogged++;
   519       if (pass)
   520         type += "UNEXPECTED-PASS";
   521       else
   522         type += "UNEXPECTED-FAIL";
   523     }
   525     this.print(type + " | " + test + " | " + message + "\n");
   526     if (!expected)
   527       this.trace();
   528   },
   530   log: function log() {
   531     this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
   532   },
   534   info: function info(first) {
   535     this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
   536   },
   538   warn: function warn() {
   539     this.errorsLogged++;
   540     this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n");
   541   },
   543   error: function error() {
   544     this.errorsLogged++;
   545     this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n");
   546     this.base.error.apply(this.base, arguments);
   547   },
   549   debug: function debug() {
   550     this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n");
   551   },
   553   exception: function exception(e) {
   554     this.print("An exception occurred.\n" +
   555                require("../console/traceback").format(e) + "\n" + e + "\n");
   556   },
   558   trace: function trace() {
   559     var traceback = require("../console/traceback");
   560     var stack = traceback.get();
   561     stack.splice(-1, 1);
   562     this.print("TEST-INFO | " + stringify(traceback.format(stack)) + "\n");
   563   }
   564 };
   566 var runTests = exports.runTests = function runTests(options) {
   567   iterationsLeft = options.iterations;
   568   profileMemory = options.profileMemory;
   569   stopOnError = options.stopOnError;
   570   onDone = options.onDone;
   571   print = options.print;
   572   findAndRunTests = options.findAndRunTests;
   574   try {
   575     cService.registerListener(consoleListener);
   576     print("Running tests on " + system.name + " " + system.version +
   577           "/Gecko " + system.platformVersion + " (" +
   578           system.id + ") under " +
   579           system.platform + "/" + system.architecture + ".\n");
   581     if (options.parseable)
   582       testConsole = new TestRunnerTinderboxConsole(new PlainTextConsole(), options);
   583     else
   584       testConsole = new TestRunnerConsole(new PlainTextConsole(), options);
   586     loader = Loader(module, {
   587       console: testConsole,
   588       global: {} // useful for storing things like coverage testing.
   589     });
   591     // Load these before getting initial leak stats as they will still be in
   592     // memory when we check later
   593     require("../deprecated/unit-test");
   594     require("../deprecated/unit-test-finder");
   595     startLeaks = getPotentialLeaks();
   597     nextIteration();
   598   } catch (e) {
   599     let frames = fromException(e).reverse().reduce(function(frames, frame) {
   600       if (frame.fileName.split("/").pop() === "unit-test-finder.js")
   601         frames.done = true
   602       if (!frames.done) frames.push(frame)
   604       return frames
   605     }, [])
   607     let prototype = typeof(e) === "object" ? e.constructor.prototype :
   608                     Error.prototype;
   609     let stack = serializeStack(frames.reverse());
   611     let error = Object.create(prototype, {
   612       message: { value: e.message, writable: true, configurable: true },
   613       fileName: { value: e.fileName, writable: true, configurable: true },
   614       lineNumber: { value: e.lineNumber, writable: true, configurable: true },
   615       stack: { value: stack, writable: true, configurable: true },
   616       toString: { value: function() String(e), writable: true, configurable: true },
   617     });
   619     print("Error: " + error + " \n " + format(error));
   620     onDone({passed: 0, failed: 1});
   621   }
   622 };
   624 unload(function() {
   625   cService.unregisterListener(consoleListener);
   626 });

mercurial