summaryrefslogtreecommitdiffstats
path: root/testing/mochitest/jetpack-addon-harness.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/mochitest/jetpack-addon-harness.js')
-rw-r--r--testing/mochitest/jetpack-addon-harness.js235
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);
+ }
+ });
+}