diff options
author | Rogan Creswick <creswick@galois.com> | 2012-03-30 17:07:02 -0700 |
---|---|---|
committer | Rogan Creswick <creswick@galois.com> | 2012-03-30 17:07:02 -0700 |
commit | f6ab6622aab00fe7c2f4c3dc41f786ebbe0f0d73 (patch) | |
tree | 870111038542cd27153e1396ebdc063573249689 /tools/addon-sdk-1.3/packages/test-harness |
initial revision
Diffstat (limited to 'tools/addon-sdk-1.3/packages/test-harness')
7 files changed, 544 insertions, 0 deletions
diff --git a/tools/addon-sdk-1.3/packages/test-harness/README.md b/tools/addon-sdk-1.3/packages/test-harness/README.md new file mode 100644 index 0000000..13e2b66 --- /dev/null +++ b/tools/addon-sdk-1.3/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](#guide/addon-development/implementing-reusable-module) +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.3/packages/test-harness/docs/harness.md b/tools/addon-sdk-1.3/packages/test-harness/docs/harness.md new file mode 100644 index 0000000..9f6cd7b --- /dev/null +++ b/tools/addon-sdk-1.3/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.3/packages/test-harness/docs/run-tests.md b/tools/addon-sdk-1.3/packages/test-harness/docs/run-tests.md new file mode 100644 index 0000000..ad64606 --- /dev/null +++ b/tools/addon-sdk-1.3/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](#guide/addon-development/implementing-reusable-module) +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.3/packages/test-harness/lib/harness.js b/tools/addon-sdk-1.3/packages/test-harness/lib/harness.js new file mode 100644 index 0000000..7f000b7 --- /dev/null +++ b/tools/addon-sdk-1.3/packages/test-harness/lib/harness.js @@ -0,0 +1,365 @@ +/* ***** 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 {Cc,Ci} = require("chrome"); + +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; + +// The directories to look for tests in. +var dirs; + +// 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.sandboxes) + sandbox.memory.track(sandbox.sandboxes[name].globalScope, + "module global scope: " + name); + sandbox.memory.track(sandbox, "Cuddlefish Loader"); + + if (profileMemory) { + gWeakrefInfo = [{ weakref: info.weakref, bin: info.bin } + for each (info in sandbox.memory.getObjects())]; + } + + sandbox.unload(); + + if (sandbox.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) + sandbox.require("api-utils/unit-test").findAndRunTests({ + testOutOfProcess: packaging.enableE10s, + testInProcess: true, + fs: sandbox.fs, + dirs: dirs, + 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 cuddlefish = require("api-utils/cuddlefish"); + var ptc = require("api-utils/plain-text-console"); + var url = require("api-utils/url"); + + dirs = [url.toFilename(path) + for each (path in options.rootPaths)]; + var console = new TestRunnerConsole(new ptc.PlainTextConsole(print), + options); + var globals = {packaging: packaging}; + + var xulApp = require("api-utils/xul-app"); + var xulRuntime = Cc["@mozilla.org/xre/app-info;1"] + .getService(Ci.nsIXULRuntime); + + print("Running tests on " + xulApp.name + " " + xulApp.version + + "/Gecko " + xulApp.platformVersion + " (" + + xulApp.ID + ") under " + + xulRuntime.OS + "/" + xulRuntime.XPCOMABI + ".\n"); + + sandbox = new cuddlefish.Loader({console: console, + globals: globals, + metadata: packaging.options.metadata, + jetpackID: packaging.options.jetpackID, + uriPrefix: packaging.options.uriPrefix, + name: packaging.options.name, + packaging: packaging, + __proto__: 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.3/packages/test-harness/lib/run-tests.js b/tools/addon-sdk-1.3/packages/test-harness/lib/run-tests.js new file mode 100644 index 0000000..bcf76e4 --- /dev/null +++ b/tools/addon-sdk-1.3/packages/test-harness/lib/run-tests.js @@ -0,0 +1,121 @@ +/* ***** 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 {Cc,Ci} = require("chrome"); + +function runTests(iterations, filter, profileMemory, verbose, rootPaths, quit, print) { + var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"] + .getService(Ci.nsIWindowWatcher); + + var window = ww.openWindow(null, "data:text/plain,Running tests...", + "harness", "centerscreen", null); + + var harness = require("./harness"); + + function onDone(tests) { + window.close(); + if (tests.passed > 0 && tests.failed == 0) { + quit("OK"); + } else { + if (tests.passed == 0) { + print("No tests were run\n"); + } else { + printFailedTests(tests, verbose, print); + } + quit("FAIL"); + } + }; + + harness.runTests({iterations: iterations, + filter: filter, + profileMemory: profileMemory, + verbose: verbose, + rootPaths: rootPaths, + print: print, + onDone: onDone}); +} + +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(options, callbacks) { + var testsStarted = false; + + function doRunTests() { + if (!testsStarted) { + testsStarted = true; + runTests(options.iterations, options.filter, + options.profileMemory, options.verbose, + options.rootPaths, callbacks.quit, + callbacks.print); + } + } + + // TODO: This is optional code that might be put in by + // something running this code to force it to just + // run tests immediately, rather than wait. We need + // to actually standardize on this, though. + if (options.runImmediately) { + doRunTests(); + } + else { + obsvc.add(obsvc.topics.APPLICATION_READY, doRunTests); + } +}; diff --git a/tools/addon-sdk-1.3/packages/test-harness/package.json b/tools/addon-sdk-1.3/packages/test-harness/package.json new file mode 100644 index 0000000..9a81139 --- /dev/null +++ b/tools/addon-sdk-1.3/packages/test-harness/package.json @@ -0,0 +1,9 @@ +{ + "name": "test-harness", + "description": "A harness for running Jetpack tests.", + "author": "Atul Varma (http://toolness.com/)", + "keywords": ["jetpack-low-level"], + "version": "1.3", + "license": "MPL 1.1/GPL 2.0/LGPL 2.1", + "dependencies": ["api-utils"] +} diff --git a/tools/addon-sdk-1.3/packages/test-harness/tests/test-packaging.js b/tools/addon-sdk-1.3/packages/test-harness/tests/test-packaging.js new file mode 100644 index 0000000..fb1f616 --- /dev/null +++ b/tools/addon-sdk-1.3/packages/test-harness/tests/test-packaging.js @@ -0,0 +1,30 @@ +var url = require("url"); +var file = require("file"); +var {Cm,Ci} = require("chrome"); + +exports.testPackaging = function(test) { + test.assertEqual(packaging.options.main, + 'test-harness/run-tests', + "main program should be the test harness"); + + var factory = Cm.getClassObjectByContractID( + packaging.options.bootstrap.contractID, + Ci.nsIFactory + ); + + var harness = factory.wrappedJSObject.singleton; + + test.assertEqual(packaging.harnessService, harness); + + test.assertNotEqual(harness.loader, undefined, + "bootstrap component should be available"); + + test.assertEqual(JSON.stringify(harness.options), + JSON.stringify(packaging.options), + ("bootstrap component options should be identical " + + "to packaging.options")); + + test.assertEqual(packaging.options.metadata['test-harness'].author, + 'Atul Varma (http://toolness.com/)', + "packaging metadata should be available"); +}; |