diff options
Diffstat (limited to 'addon-sdk/source/lib/sdk/test/harness.js')
-rw-r--r-- | addon-sdk/source/lib/sdk/test/harness.js | 645 |
1 files changed, 0 insertions, 645 deletions
diff --git a/addon-sdk/source/lib/sdk/test/harness.js b/addon-sdk/source/lib/sdk/test/harness.js deleted file mode 100644 index 1b31a1c79..000000000 --- a/addon-sdk/source/lib/sdk/test/harness.js +++ /dev/null @@ -1,645 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -"use strict"; - -module.metadata = { - "stability": "experimental" -}; - -const { Cc, Ci, Cu } = require("chrome"); -const { Loader } = require('./loader'); -const { serializeStack, parseStack } = require("toolkit/loader"); -const { setTimeout } = require('../timers'); -const { PlainTextConsole } = require("../console/plain-text"); -const { when: unload } = require("../system/unload"); -const { format, fromException } = require("../console/traceback"); -const system = require("../system"); -const { gc: gcPromise } = require('./memory'); -const { defer } = require('../core/promise'); -const { extend } = require('../core/heritage'); - -// Trick manifest builder to make it think we need these modules ? -const unit = require("../deprecated/unit-test"); -const test = require("../../test"); -const url = require("../url"); - -function emptyPromise() { - let { promise, resolve } = defer(); - resolve(); - return promise; -} - -var cService = Cc['@mozilla.org/consoleservice;1'].getService(Ci.nsIConsoleService); - -// The console used to log messages -var testConsole; - -// Cuddlefish loader in which we load and execute tests. -var loader; - -// Function to call when we're done running tests. -var onDone; - -// Function to print text to a console, w/o CR at the end. -var print; - -// How many more times to run all tests. -var iterationsLeft; - -// Whether to report memory profiling information. -var profileMemory; - -// Whether we should stop as soon as a test reports a failure. -var stopOnError; - -// Function to call to retrieve a list of tests to execute -var findAndRunTests; - -// Combined information from all test runs. -var results; - -// A list of the compartments and windows loaded after startup -var startLeaks; - -// JSON serialization of last memory usage stats; we keep it stringified -// so we don't actually change the memory usage stats (in terms of objects) -// of the JSRuntime we're profiling. -var lastMemoryUsage; - -function analyzeRawProfilingData(data) { - var graph = data.graph; - var shapes = {}; - - // Convert keys in the graph from strings to ints. - // TODO: Can we get rid of this ridiculousness? - var newGraph = {}; - for (id in graph) { - newGraph[parseInt(id)] = graph[id]; - } - graph = newGraph; - - var modules = 0; - var moduleIds = []; - var moduleObjs = {UNKNOWN: 0}; - for (let name in data.namedObjects) { - moduleObjs[name] = 0; - moduleIds[data.namedObjects[name]] = name; - modules++; - } - - var count = 0; - for (id in graph) { - var parent = graph[id].parent; - while (parent) { - if (parent in moduleIds) { - var name = moduleIds[parent]; - moduleObjs[name]++; - break; - } - if (!(parent in graph)) { - moduleObjs.UNKNOWN++; - break; - } - parent = graph[parent].parent; - } - count++; - } - - print("\nobject count is " + count + " in " + modules + " modules" + - " (" + data.totalObjectCount + " across entire JS runtime)\n"); - if (lastMemoryUsage) { - var last = JSON.parse(lastMemoryUsage); - var diff = { - moduleObjs: dictDiff(last.moduleObjs, moduleObjs), - totalObjectClasses: dictDiff(last.totalObjectClasses, - data.totalObjectClasses) - }; - - for (let name in diff.moduleObjs) - print(" " + diff.moduleObjs[name] + " in " + name + "\n"); - for (let name in diff.totalObjectClasses) - print(" " + diff.totalObjectClasses[name] + " instances of " + - name + "\n"); - } - lastMemoryUsage = JSON.stringify( - {moduleObjs: moduleObjs, - totalObjectClasses: data.totalObjectClasses} - ); -} - -function dictDiff(last, curr) { - var diff = {}; - - for (let name in last) { - var result = (curr[name] || 0) - last[name]; - if (result) - diff[name] = (result > 0 ? "+" : "") + result; - } - for (let name in curr) { - var result = curr[name] - (last[name] || 0); - if (result) - diff[name] = (result > 0 ? "+" : "") + result; - } - return diff; -} - -function reportMemoryUsage() { - if (!profileMemory) { - return emptyPromise(); - } - - return gcPromise().then((() => { - var mgr = Cc["@mozilla.org/memory-reporter-manager;1"] - .getService(Ci.nsIMemoryReporterManager); - let count = 0; - function logReporter(process, path, kind, units, amount, description) { - print(((++count == 1) ? "\n" : "") + description + ": " + amount + "\n"); - } - mgr.getReportsForThisProcess(logReporter, null, /* anonymize = */ false); - })); -} - -var gWeakrefInfo; - -function checkMemory() { - return gcPromise().then(_ => { - let leaks = getPotentialLeaks(); - - let compartmentURLs = Object.keys(leaks.compartments).filter(function(url) { - return !(url in startLeaks.compartments); - }); - - let windowURLs = Object.keys(leaks.windows).filter(function(url) { - return !(url in startLeaks.windows); - }); - - for (let url of compartmentURLs) - console.warn("LEAKED", leaks.compartments[url]); - - for (let url of windowURLs) - console.warn("LEAKED", leaks.windows[url]); - }).then(showResults); -} - -function showResults() { - let { promise, resolve } = defer(); - - if (gWeakrefInfo) { - gWeakrefInfo.forEach( - function(info) { - var ref = info.weakref.get(); - if (ref !== null) { - var data = ref.__url__ ? ref.__url__ : ref; - var warning = data == "[object Object]" - ? "[object " + data.constructor.name + "(" + - Object.keys(data).join(", ") + ")]" - : data; - console.warn("LEAK", warning, info.bin); - } - } - ); - } - - onDone(results); - - resolve(); - return promise; -} - -function cleanup() { - let coverObject = {}; - try { - loader.unload(); - - if (loader.globals.console.errorsLogged && !results.failed) { - results.failed++; - console.error("warnings and/or errors were logged."); - } - - if (consoleListener.errorsLogged && !results.failed) { - console.warn(consoleListener.errorsLogged + " " + - "warnings or errors were logged to the " + - "platform's nsIConsoleService, which could " + - "be of no consequence; however, they could also " + - "be indicative of aberrant behavior."); - } - - // read the code coverage object, if it exists, from CoverJS-moz - if (typeof loader.globals.global == "object") { - coverObject = loader.globals.global['__$coverObject'] || {}; - } - - consoleListener.errorsLogged = 0; - loader = null; - - consoleListener.unregister(); - - Cu.forceGC(); - } - catch (e) { - results.failed++; - console.error("unload.send() threw an exception."); - console.exception(e); - }; - - setTimeout(require("./options").checkMemory ? checkMemory : showResults, 1); - - // dump the coverobject - if (Object.keys(coverObject).length){ - const self = require('sdk/self'); - const {pathFor} = require("sdk/system"); - let file = require('sdk/io/file'); - const {env} = require('sdk/system/environment'); - console.log("CWD:", env.PWD); - let out = file.join(env.PWD,'coverstats-'+self.id+'.json'); - console.log('coverstats:', out); - let outfh = file.open(out,'w'); - outfh.write(JSON.stringify(coverObject,null,2)); - outfh.flush(); - outfh.close(); - } -} - -function getPotentialLeaks() { - Cu.forceGC(); - - // Things we can assume are part of the platform and so aren't leaks - let GOOD_BASE_URLS = [ - "chrome://", - "resource:///", - "resource://app/", - "resource://gre/", - "resource://gre-resources/", - "resource://pdf.js/", - "resource://pdf.js.components/", - "resource://services-common/", - "resource://services-crypto/", - "resource://services-sync/" - ]; - - let ioService = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); - let uri = ioService.newURI("chrome://global/content/", "UTF-8", null); - let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]. - getService(Ci.nsIChromeRegistry); - uri = chromeReg.convertChromeURL(uri); - let spec = uri.spec; - let pos = spec.indexOf("!/"); - GOOD_BASE_URLS.push(spec.substring(0, pos + 2)); - - let zoneRegExp = new RegExp("^explicit/js-non-window/zones/zone[^/]+/compartment\\((.+)\\)"); - let compartmentRegexp = new RegExp("^explicit/js-non-window/compartments/non-window-global/compartment\\((.+)\\)/"); - let compartmentDetails = new RegExp("^([^,]+)(?:, (.+?))?(?: \\(from: (.*)\\))?$"); - let windowRegexp = new RegExp("^explicit/window-objects/top\\((.*)\\)/active"); - let windowDetails = new RegExp("^(.*), id=.*$"); - - function isPossibleLeak(item) { - if (!item.location) - return false; - - for (let url of GOOD_BASE_URLS) { - if (item.location.substring(0, url.length) == url) { - return false; - } - } - - return true; - } - - let compartments = {}; - let windows = {}; - function logReporter(process, path, kind, units, amount, description) { - let matches; - - if ((matches = compartmentRegexp.exec(path)) || (matches = zoneRegExp.exec(path))) { - if (matches[1] in compartments) - return; - - let details = compartmentDetails.exec(matches[1]); - if (!details) { - console.error("Unable to parse compartment detail " + matches[1]); - return; - } - - let item = { - path: matches[1], - principal: details[1], - location: details[2] ? details[2].replace(/\\/g, "/") : undefined, - source: details[3] ? details[3].split(" -> ").reverse() : undefined, - toString: function() { - return this.location; - } - }; - - if (!isPossibleLeak(item)) - return; - - compartments[matches[1]] = item; - return; - } - - if ((matches = windowRegexp.exec(path))) { - if (matches[1] in windows) - return; - - let details = windowDetails.exec(matches[1]); - if (!details) { - console.error("Unable to parse window detail " + matches[1]); - return; - } - - let item = { - path: matches[1], - location: details[1].replace(/\\/g, "/"), - source: [details[1].replace(/\\/g, "/")], - toString: function() { - return this.location; - } - }; - - if (!isPossibleLeak(item)) - return; - - windows[matches[1]] = item; - } - } - - Cc["@mozilla.org/memory-reporter-manager;1"] - .getService(Ci.nsIMemoryReporterManager) - .getReportsForThisProcess(logReporter, null, /* anonymize = */ false); - - return { compartments: compartments, windows: windows }; -} - -function nextIteration(tests) { - if (tests) { - results.passed += tests.passed; - results.failed += tests.failed; - - reportMemoryUsage().then(_ => { - let testRun = []; - for (let test of tests.testRunSummary) { - let testCopy = {}; - for (let info in test) { - testCopy[info] = test[info]; - } - testRun.push(testCopy); - } - - results.testRuns.push(testRun); - iterationsLeft--; - - checkForEnd(); - }) - } - else { - checkForEnd(); - } -} - -function checkForEnd() { - if (iterationsLeft && (!stopOnError || results.failed == 0)) { - // Pass the loader which has a hooked console that doesn't dispatch - // errors to the JS console and avoid firing false alarm in our - // console listener - findAndRunTests(loader, nextIteration); - } - else { - setTimeout(cleanup, 0); - } -} - -var POINTLESS_ERRORS = [ - 'Invalid chrome URI:', - 'OpenGL LayerManager Initialized Succesfully.', - '[JavaScript Error: "TelemetryStopwatch:', - 'reference to undefined property', - '[JavaScript Error: "The character encoding of the HTML document was ' + - 'not declared.', - '[Javascript Warning: "Error: Failed to preserve wrapper of wrapped ' + - 'native weak map key', - '[JavaScript Warning: "Duplicate resource declaration for', - 'file: "chrome://browser/content/', - 'file: "chrome://global/content/', - '[JavaScript Warning: "The character encoding of a framed document was ' + - 'not declared.', - 'file: "chrome://browser/skin/' -]; - -// These are messages that will cause a test to fail if logged through the -// console service -var IMPORTANT_ERRORS = [ - 'Sending message that cannot be cloned. Are you trying to send an XPCOM object?', -]; - -var consoleListener = { - registered: false, - - register: function() { - if (this.registered) - return; - cService.registerListener(this); - this.registered = true; - }, - - unregister: function() { - if (!this.registered) - return; - cService.unregisterListener(this); - this.registered = false; - }, - - errorsLogged: 0, - - observe: function(object) { - if (!(object instanceof Ci.nsIScriptError)) - return; - this.errorsLogged++; - var message = object.QueryInterface(Ci.nsIConsoleMessage).message; - if (IMPORTANT_ERRORS.find(msg => message.indexOf(msg) >= 0)) { - testConsole.error(message); - return; - } - var pointless = POINTLESS_ERRORS.filter(err => message.indexOf(err) >= 0); - if (pointless.length == 0 && message) - testConsole.log(message); - } -}; - -function TestRunnerConsole(base, options) { - let proto = extend(base, { - errorsLogged: 0, - warn: function warn() { - this.errorsLogged++; - base.warn.apply(base, arguments); - }, - error: function error() { - this.errorsLogged++; - base.error.apply(base, arguments); - }, - info: function info(first) { - if (options.verbose) - base.info.apply(base, arguments); - else - if (first == "pass:") - print("."); - }, - }); - return Object.create(proto); -} - -function stringify(arg) { - try { - return String(arg); - } - catch(ex) { - return "<toString() error>"; - } -} - -function stringifyArgs(args) { - return Array.map(args, stringify).join(" "); -} - -function TestRunnerTinderboxConsole(base, options) { - this.base = base; - this.print = options.print; - this.verbose = options.verbose; - this.errorsLogged = 0; - - // Binding all the public methods to an instance so that they can be used - // as callback / listener functions straightaway. - this.log = this.log.bind(this); - this.info = this.info.bind(this); - this.warn = this.warn.bind(this); - this.error = this.error.bind(this); - this.debug = this.debug.bind(this); - this.exception = this.exception.bind(this); - this.trace = this.trace.bind(this); -}; - -TestRunnerTinderboxConsole.prototype = { - testMessage: function testMessage(pass, expected, test, message) { - let type = "TEST-"; - if (expected) { - if (pass) - type += "PASS"; - else - type += "KNOWN-FAIL"; - } - else { - this.errorsLogged++; - if (pass) - type += "UNEXPECTED-PASS"; - else - type += "UNEXPECTED-FAIL"; - } - - this.print(type + " | " + test + " | " + message + "\n"); - if (!expected) - this.trace(); - }, - - log: function log() { - this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n"); - }, - - info: function info(first) { - this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n"); - }, - - warn: function warn() { - this.errorsLogged++; - this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n"); - }, - - error: function error() { - this.errorsLogged++; - this.print("TEST-UNEXPECTED-FAIL | " + stringifyArgs(arguments) + "\n"); - this.base.error.apply(this.base, arguments); - }, - - debug: function debug() { - this.print("TEST-INFO | " + stringifyArgs(arguments) + "\n"); - }, - - exception: function exception(e) { - this.print("An exception occurred.\n" + - require("../console/traceback").format(e) + "\n" + e + "\n"); - }, - - trace: function trace() { - var traceback = require("../console/traceback"); - var stack = traceback.get(); - stack.splice(-1, 1); - this.print("TEST-INFO | " + stringify(traceback.format(stack)) + "\n"); - } -}; - -var runTests = exports.runTests = function runTests(options) { - iterationsLeft = options.iterations; - profileMemory = options.profileMemory; - stopOnError = options.stopOnError; - onDone = options.onDone; - print = options.print; - findAndRunTests = options.findAndRunTests; - - results = { - passed: 0, - failed: 0, - testRuns: [] - }; - - try { - consoleListener.register(); - print("Running tests on " + system.name + " " + system.version + - "/Gecko " + system.platformVersion + " (Build " + - system.build + ") (" + system.id + ") under " + - system.platform + "/" + system.architecture + ".\n"); - - if (options.parseable) - testConsole = new TestRunnerTinderboxConsole(new PlainTextConsole(), options); - else - testConsole = new TestRunnerConsole(new PlainTextConsole(), options); - - loader = Loader(module, { - console: testConsole, - global: {} // useful for storing things like coverage testing. - }); - - // Load these before getting initial leak stats as they will still be in - // memory when we check later - require("../deprecated/unit-test"); - require("../deprecated/unit-test-finder"); - if (profileMemory) - startLeaks = getPotentialLeaks(); - - nextIteration(); - } catch (e) { - let frames = fromException(e).reverse().reduce(function(frames, frame) { - if (frame.fileName.split("/").pop() === "unit-test-finder.js") - frames.done = true - if (!frames.done) frames.push(frame) - - return frames - }, []) - - let prototype = typeof(e) === "object" ? e.constructor.prototype : - Error.prototype; - let stack = serializeStack(frames.reverse()); - - let error = Object.create(prototype, { - message: { value: e.message, writable: true, configurable: true }, - fileName: { value: e.fileName, writable: true, configurable: true }, - lineNumber: { value: e.lineNumber, writable: true, configurable: true }, - stack: { value: stack, writable: true, configurable: true }, - toString: { value: () => String(e), writable: true, configurable: true }, - }); - - print("Error: " + error + " \n " + format(error)); - onDone({passed: 0, failed: 1}); - } -}; - -unload(_ => consoleListener.unregister()); |