diff options
Diffstat (limited to 'testing/mochitest/jetpack-addon-harness.js')
-rw-r--r-- | testing/mochitest/jetpack-addon-harness.js | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/testing/mochitest/jetpack-addon-harness.js b/testing/mochitest/jetpack-addon-harness.js new file mode 100644 index 000000000..3b938db9d --- /dev/null +++ b/testing/mochitest/jetpack-addon-harness.js @@ -0,0 +1,235 @@ +/* -*- js-indent-level: 2; tab-width: 2; indent-tabs-mode: nil -*- */ +var gConfig; + +if (Cc === undefined) { + var Cc = Components.classes; + var Ci = Components.interfaces; + var Cu = Components.utils; +} + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Task.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); + +// How long to wait for an add-on to uninstall before aborting +const MAX_UNINSTALL_TIME = 10000; +setTimeout(testInit, 0); + +var sdkpath = null; + +// Strip off the chrome prefix to get the actual path of the test directory +function realPath(chrome) { + return chrome.substring("chrome://mochitests/content/jetpack-addon/".length) + .replace(".xpi", ""); +} + +const chromeRegistry = Cc["@mozilla.org/chrome/chrome-registry;1"] + .getService(Ci.nsIChromeRegistry); + +// Installs a single add-on returning a promise for when install is completed +function installAddon(url) { + let chromeURL = Services.io.newURI(url, null, null); + let file = chromeRegistry.convertChromeURL(chromeURL) + .QueryInterface(Ci.nsIFileURL).file; + + let addon; + const listener = { + onInstalling(_addon) { + addon = _addon; + // Set add-on's test options + const options = { + test: { + iterations: 1, + stop: false, + keepOpen: true, + }, + profile: { + memory: false, + leaks: false, + }, + output: { + logLevel: "verbose", + format: "tbpl", + }, + console: { + logLevel: "info", + }, + } + setPrefs("extensions." + addon.id + ".sdk", options); + + // If necessary override the add-ons module paths to point somewhere + // else + if (sdkpath) { + let paths = {} + for (let path of ["dev", "diffpatcher", "framescript", "method", "node", "sdk", "toolkit"]) { + paths[path] = sdkpath + path; + } + setPrefs("extensions.modules." + addon.id + ".path", paths); + } + }, + }; + AddonManager.addAddonListener(listener); + + return AddonManager.installTemporaryAddon(file) + .then(() => { + AddonManager.removeAddonListener(listener); + return addon; + }); +} + +// Uninstalls an add-on returning a promise for when it is gone +function uninstallAddon(oldAddon) { + return new Promise(function(resolve, reject) { + AddonManager.addAddonListener({ + onUninstalled: function(addon) { + if (addon.id != oldAddon.id) + return; + + AddonManager.removeAddonListener(this); + + dump("TEST-INFO | jetpack-addon-harness.js | Uninstalled test add-on " + addon.id + "\n"); + + // Some add-ons do async work on uninstall, we must wait for that to + // complete + setTimeout(resolve, 500); + } + }); + + oldAddon.uninstall(); + + // The uninstall should happen quickly, if not throw an exception + setTimeout(() => { + reject(new Error(`Addon ${oldAddon.id} failed to uninstall in a timely fashion.`)); + }, MAX_UNINSTALL_TIME); + }); +} + +// Waits for a test add-on to signal it has completed its tests +function waitForResults() { + return new Promise(function(resolve, reject) { + Services.obs.addObserver(function(subject, topic, data) { + Services.obs.removeObserver(arguments.callee, "sdk:test:results"); + + resolve(JSON.parse(data)); + }, "sdk:test:results", false); + }); +} + +// Runs tests for the add-on available at URL. +var testAddon = Task.async(function*({ url }) { + dump("TEST-INFO | jetpack-addon-harness.js | Installing test add-on " + realPath(url) + "\n"); + let addon = yield installAddon(url); + + let results = yield waitForResults(); + + dump("TEST-INFO | jetpack-addon-harness.js | Uninstalling test add-on " + addon.id + "\n"); + yield uninstallAddon(addon); + + dump("TEST-INFO | jetpack-addon-harness.js | Testing add-on " + realPath(url) + " is complete\n"); + return results; +}); + +// Sets a set of prefs for test add-ons +function setPrefs(root, options) { + Object.keys(options).forEach(id => { + const key = root + "." + id; + const value = options[id] + const type = typeof(value); + + value === null ? void(0) : + value === undefined ? void(0) : + type === "boolean" ? Services.prefs.setBoolPref(key, value) : + type === "string" ? Services.prefs.setCharPref(key, value) : + type === "number" ? Services.prefs.setIntPref(key, parseInt(value)) : + type === "object" ? setPrefs(key, value) : + void(0); + }); +} + +function testInit() { + // Make sure to run the test harness for the first opened window only + if (Services.prefs.prefHasUserValue("testing.jetpackTestHarness.running")) + return; + + Services.prefs.setBoolPref("testing.jetpackTestHarness.running", true); + + // Get the list of tests to run + let config = readConfig(); + getTestList(config, function(links) { + try { + let fileNames = []; + let fileNameRegexp = /.+\.xpi$/; + arrayOfTestFiles(links, fileNames, fileNameRegexp); + + if (config.startAt || config.endAt) { + fileNames = skipTests(fileNames, config.startAt, config.endAt); + } + + // Override the SDK modules if necessary + try { + let sdklibs = Services.prefs.getCharPref("extensions.sdk.path"); + // sdkpath is a file path, make it a URI + let sdkfile = Cc["@mozilla.org/file/local;1"]. + createInstance(Ci.nsIFile); + sdkfile.initWithPath(sdklibs); + sdkpath = Services.io.newFileURI(sdkfile).spec; + } + catch (e) { + // Stick with the built-in modules + } + + let passed = 0; + let failed = 0; + + function finish() { + if (passed + failed == 0) { + dump("TEST-UNEXPECTED-FAIL | jetpack-addon-harness.js | " + + "No tests to run. Did you pass invalid test_paths?\n"); + } + else { + dump("Jetpack Addon Test Summary\n"); + dump("\tPassed: " + passed + "\n" + + "\tFailed: " + failed + "\n" + + "\tTodo: 0\n"); + } + + if (config.closeWhenDone) { + dump("TEST-INFO | jetpack-addon-harness.js | Shutting down.\n"); + + const appStartup = Cc['@mozilla.org/toolkit/app-startup;1']. + getService(Ci.nsIAppStartup); + appStartup.quit(appStartup.eAttemptQuit); + } + } + + function testNextAddon() { + if (fileNames.length == 0) + return finish(); + + let filename = fileNames.shift(); + dump("TEST-INFO | jetpack-addon-harness.js | Starting test add-on " + realPath(filename.url) + "\n"); + testAddon(filename).then(results => { + passed += results.passed; + failed += results.failed; + }).then(testNextAddon, error => { + // If something went wrong during the test then a previous test add-on + // may still be installed, this leaves us in an unexpected state so + // probably best to just abandon testing at this point + failed++; + dump("TEST-UNEXPECTED-FAIL | jetpack-addon-harness.js | Error testing " + realPath(filename.url) + ": " + error + "\n"); + finish(); + }); + } + + testNextAddon(); + } + catch (e) { + dump("TEST-UNEXPECTED-FAIL | jetpack-addon-harness.js | error starting test harness (" + e + ")\n"); + dump(e.stack); + } + }); +} |