diff options
Diffstat (limited to 'tools/addon-sdk-1.4/packages/test-harness')
7 files changed, 526 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.4/packages/test-harness/README.md b/tools/addon-sdk-1.4/packages/test-harness/README.md new file mode 100644 index 0000000..a833d34 --- /dev/null +++ b/tools/addon-sdk-1.4/packages/test-harness/README.md @@ -0,0 +1,8 @@ +<span class="aside"> +For more information on testing in the Add-on SDK, see the +[Reusable Modules](dev-guide/addon-development/implementing-reusable-module.html) +tutorial. +</span> + +This package contains a program that finds and runs tests. It is +automatically used whenever the `cfx test` command is executed. diff --git a/tools/addon-sdk-1.4/packages/test-harness/docs/harness.md b/tools/addon-sdk-1.4/packages/test-harness/docs/harness.md new file mode 100644 index 0000000..9f6cd7b --- /dev/null +++ b/tools/addon-sdk-1.4/packages/test-harness/docs/harness.md @@ -0,0 +1,2 @@ +This module contains the bulk of the test harness setup and execution +implementation. diff --git a/tools/addon-sdk-1.4/packages/test-harness/docs/run-tests.md b/tools/addon-sdk-1.4/packages/test-harness/docs/run-tests.md new file mode 100644 index 0000000..38f4569 --- /dev/null +++ b/tools/addon-sdk-1.4/packages/test-harness/docs/run-tests.md @@ -0,0 +1,9 @@ +<span class="aside"> +For more information on testing in the Add-on SDK, see the +[Reusable Modules](dev-guide/addon-development/implementing-reusable-module.html) +tutorial. +</span> + +This module contains the package's main program, which does a +bit of high-level setup and then delegates test finding and running to +the `harness` module. diff --git a/tools/addon-sdk-1.4/packages/test-harness/lib/harness.js b/tools/addon-sdk-1.4/packages/test-harness/lib/harness.js new file mode 100644 index 0000000..a5a9284 --- /dev/null +++ b/tools/addon-sdk-1.4/packages/test-harness/lib/harness.js @@ -0,0 +1,352 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Jetpack. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Atul Varma <atul@mozilla.com> + * Irakli Gozalishvili <gozala@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +"use strict"; + +const { Cc,Ci } = require("chrome"); +const { Loader } = require("@loader") + +var cService = Cc['@mozilla.org/consoleservice;1'].getService() + .QueryInterface(Ci.nsIConsoleService); + +// Cuddlefish loader for the sandbox in which we load and +// execute tests. +var sandbox; + +// 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; + +// Only tests in files whose names match this regexp filter will be run. +var filter; + +// Whether to report memory profiling information. +var profileMemory; + +// Combined information from all test runs. +var results = { + passed: 0, + failed: 0, + testRuns: [] +}; + +// 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() { + memory.gc(); + sandbox.memory.gc(); + + var mgr = Cc["@mozilla.org/memory-reporter-manager;1"] + .getService(Ci.nsIMemoryReporterManager); + var reporters = mgr.enumerateReporters(); + if (reporters.hasMoreElements()) + print("\n"); + while (reporters.hasMoreElements()) { + var reporter = reporters.getNext(); + reporter.QueryInterface(Ci.nsIMemoryReporter); + print(reporter.description + ": " + reporter.memoryUsed + "\n"); + } + + var weakrefs = [info.weakref.get() + for each (info in sandbox.memory.getObjects())]; + weakrefs = [weakref for each (weakref in weakrefs) if (weakref)]; + print("Tracked memory objects in testing sandbox: " + + weakrefs.length + "\n"); +} + +var gWeakrefInfo; + +function showResults() { + memory.gc(); + + 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 + "(" + + [p for (p in data)].join(", ") + ")]" + : data; + console.warn("LEAK", warning, info.bin); + } + } + ); + } + + print("\n"); + var total = results.passed + results.failed; + print(results.passed + " of " + total + " tests passed.\n"); + onDone(results); +} + +function cleanup() { + try { + for (let name in sandbox.modules) + sandbox.globals.memory.track(sandbox.modules[name], + "module global scope: " + name); + sandbox.globals.memory.track(sandbox, "Cuddlefish Loader"); + + if (profileMemory) { + gWeakrefInfo = [{ weakref: info.weakref, bin: info.bin } + for each (info in sandbox.globals.memory.getObjects())]; + } + + sandbox.unload(); + + if (sandbox.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."); + } + + consoleListener.errorsLogged = 0; + sandbox = null; + + memory.gc(); + } catch (e) { + results.failed++; + console.error("unload.send() threw an exception."); + console.exception(e); + }; + + require("api-utils/timer").setTimeout(showResults, 1); +} + +function nextIteration(tests) { + if (tests) { + results.passed += tests.passed; + results.failed += tests.failed; + + if (profileMemory) + reportMemoryUsage(); + + let testRun = []; + for each (let test in tests.testRunSummary) { + let testCopy = {}; + for (let info in test) { + testCopy[info] = test[info]; + } + testRun.push(testCopy); + } + + results.testRuns.push(testRun); + iterationsLeft--; + } + + if (iterationsLeft) { + let require = Loader.require.bind(sandbox, module.path); + require("api-utils/unit-test").findAndRunTests({ + testOutOfProcess: require('@packaging').enableE10s, + testInProcess: true, + filter: filter, + onDone: nextIteration + }); + } + else { + require("api-utils/timer").setTimeout(cleanup, 0); + } +} + +var POINTLESS_ERRORS = [ + "Invalid chrome URI:", + "OpenGL LayerManager Initialized Succesfully." +]; + +var consoleListener = { + errorsLogged: 0, + observe: function(object) { + if (!(object instanceof Ci.nsIScriptError)) + return; + this.errorsLogged++; + var message = object.QueryInterface(Ci.nsIConsoleMessage).message; + var pointless = [err for each (err in POINTLESS_ERRORS) + if (message.indexOf(err) == 0)]; + if (pointless.length == 0 && message) + print("console: " + message + "\n"); + } +}; + +function TestRunnerConsole(base, options) { + this.__proto__ = { + 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("."); + }, + __proto__: base + }; +} + +var runTests = exports.runTests = function runTests(options) { + iterationsLeft = options.iterations; + filter = options.filter; + profileMemory = options.profileMemory; + onDone = options.onDone; + print = options.print; + + try { + cService.registerListener(consoleListener); + + var ptc = require("api-utils/plain-text-console"); + var url = require("api-utils/url"); + var system = require("api-utils/system"); + + print("Running tests on " + system.name + " " + system.version + + "/Gecko " + system.platformVersion + " (" + + system.id + ") under " + + system.platform + "/" + system.architecture + ".\n"); + + sandbox = Loader.new(require("@packaging")); + Object.defineProperty(sandbox.globals, 'console', { + value: new TestRunnerConsole(new ptc.PlainTextConsole(print), options) + }); + + nextIteration(); + } catch (e) { + print(require("api-utils/traceback").format(e) + "\n" + e + "\n"); + onDone({passed: 0, failed: 1}); + } +}; + +require("api-utils/unload").when(function() { + cService.unregisterListener(consoleListener); +}); diff --git a/tools/addon-sdk-1.4/packages/test-harness/lib/run-tests.js b/tools/addon-sdk-1.4/packages/test-harness/lib/run-tests.js new file mode 100644 index 0000000..4ea6e3f --- /dev/null +++ b/tools/addon-sdk-1.4/packages/test-harness/lib/run-tests.js @@ -0,0 +1,133 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Jetpack. + * + * The Initial Developer of the Original Code is Mozilla. + * Portions created by the Initial Developer are Copyright (C) 2007 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Atul Varma <atul@mozilla.com> + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +"use strict"; + +var obsvc = require("api-utils/observer-service"); +var system = require("api-utils/system"); +var options = require('@packaging'); +var {Cc,Ci} = require("chrome"); + +function runTests(iterations, filter, profileMemory, verbose, rootPaths, exit, print) { + var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"] + .getService(Ci.nsIWindowWatcher); + + let ns = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'; + let msg = 'Running tests...'; + let markup = '<?xml version="1.0"?><window xmlns="' + ns + + '" windowtype="test:runner"><label>' + msg + '</label></window>'; + let url = "data:application/vnd.mozilla.xul+xml," + escape(markup); + + + var window = ww.openWindow(null, url, "harness", "centerscreen", null); + + var harness = require("./harness"); + + function onDone(tests) { + window.close(); + if (tests.failed == 0) { + if (tests.passed === 0) + print("No tests were run\n"); + exit(0); + } else { + printFailedTests(tests, verbose, print); + exit(1); + } + }; + + // We have to wait for this window to be fully loaded *and* focused + // in order to avoid it to mess with our various window/focus tests. + // We are first waiting for our window to be fully loaded before ensuring + // that it will take the focus, and then we wait for it to be focused. + window.addEventListener("load", function onload() { + window.removeEventListener("load", onload, true); + + window.addEventListener("focus", function onfocus() { + window.removeEventListener("focus", onfocus, true); + // Finally, we have to run test on next cycle, otherwise XPCOM components + // are not correctly updated. + // For ex: nsIFocusManager.getFocusedElementForWindow may throw + // NS_ERROR_ILLEGAL_VALUE exception. + require("timer").setTimeout(function () { + harness.runTests({iterations: iterations, + filter: filter, + profileMemory: profileMemory, + verbose: verbose, + rootPaths: rootPaths, + print: print, + onDone: onDone}); + }, 0); + }, true); + window.focus(); + }, true); +} + +function printFailedTests(tests, verbose, print) { + if (!verbose) + return; + + let iterationNumber = 0; + let singleIteration = tests.testRuns.length == 1; + let padding = singleIteration ? "" : " "; + + print("\nThe following tests failed:\n"); + + for each (let testRun in tests.testRuns) { + iterationNumber++; + + if (!singleIteration) + print(" Iteration " + iterationNumber + ":\n"); + + for each (let test in testRun) { + if (test.failed > 0) { + print(padding + " " + test.name + ": " + test.errors +"\n"); + } + } + print("\n"); + } +} + +exports.main = function main() { + var testsStarted = false; + + if (!testsStarted) { + testsStarted = true; + runTests(options.iterations, options.filter, + options.profileMemory, options.verbose, + options.rootPaths, system.exit, + dump); + } +}; diff --git a/tools/addon-sdk-1.4/packages/test-harness/package.json b/tools/addon-sdk-1.4/packages/test-harness/package.json new file mode 100644 index 0000000..1ef10a5 --- /dev/null +++ b/tools/addon-sdk-1.4/packages/test-harness/package.json @@ -0,0 +1,8 @@ +{ + "name": "test-harness", + "description": "A harness for running Jetpack tests.", + "author": "Atul Varma (http://toolness.com/)", + "keywords": ["jetpack-low-level"], + "license": "MPL 1.1/GPL 2.0/LGPL 2.1", + "dependencies": ["api-utils"] +} diff --git a/tools/addon-sdk-1.4/packages/test-harness/tests/test-packaging.js b/tools/addon-sdk-1.4/packages/test-harness/tests/test-packaging.js new file mode 100644 index 0000000..d030267 --- /dev/null +++ b/tools/addon-sdk-1.4/packages/test-harness/tests/test-packaging.js @@ -0,0 +1,14 @@ +var url = require("url"); +var file = require("file"); +var {Cm,Ci} = require("chrome"); +var options = require("@packaging"); + +exports.testPackaging = function(test) { + test.assertEqual(options.main, + 'test-harness/run-tests', + "main program should be the test harness"); + + test.assertEqual(options.metadata['test-harness'].author, + 'Atul Varma (http://toolness.com/)', + "packaging metadata should be available"); +}; |