--- a/addon-sdk/Makefile.in
+++ b/addon-sdk/Makefile.in
@@ -1,20 +1,39 @@
# 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/.
+TESTADDONS = source/test/addons
+ADDONSRC = $(srcdir)/$(TESTADDONS)
+TESTROOT = $(CURDIR)/$(DEPTH)/_tests/testing/mochitest/jetpack-addon/$(relativesrcdir)/$(TESTADDONS)
+
+# Build a list of the test add-ons
+ADDONS = $(patsubst $(ADDONSRC)/%/package.json,$(TESTADDONS)/%.xpi,$(wildcard $(ADDONSRC)/*/package.json))
+
+INSTALL_TARGETS += test_addons
+test_addons_FILES = $(ADDONS)
+test_addons_DEST = $(TESTROOT)
+
+sinclude $(topsrcdir)/config/rules.mk
+
+# This can switch to just zipping the files when native jetpacks land
+$(TESTADDONS)/%.xpi: FORCE $(call mkdir_deps,$(CURDIR)/$(TESTADDONS)) $(ADDONSRC)/%
+ $(PYTHON) $(srcdir)/source/bin/cfx xpi --pkgdir=$(lastword $^) --output-file=$@
+
+#libs:: $(ADDONS)
+
TEST_FILES = \
- source/app-extension \
- source/bin \
- source/python-lib \
- source/test \
- source/package.json \
- source/mapping.json \
+ $(srcdir)/source/app-extension \
+ $(srcdir)/source/bin \
+ $(srcdir)/source/python-lib \
+ $(srcdir)/source/test \
+ $(srcdir)/source/package.json \
+ $(srcdir)/source/mapping.json \
$(NULL)
# Remove this once the test harness uses the APIs built into Firefox
-TEST_FILES += source/lib
+TEST_FILES += $(srcdir)/source/lib
PKG_STAGE = $(DIST)/test-stage
stage-tests-package:: $(TEST_FILES)
$(INSTALL) $^ $(PKG_STAGE)/jetpack
--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -5,16 +5,18 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini']
+JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
DIRS += ["source/modules/system"]
EXTRA_JS_MODULES.sdk += [
'source/app-extension/bootstrap.js',
]
if CONFIG['MOZ_WIDGET_TOOLKIT'] != "gonk":
--- a/addon-sdk/mozbuild.template
+++ b/addon-sdk/mozbuild.template
@@ -1,13 +1,15 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# 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/.
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+JETPACK_PACKAGE_MANIFESTS += ['source/test/jetpack-package.ini']
+JETPACK_ADDON_MANIFESTS += ['source/test/addons/jetpack-addon.ini']
DIRS += ["source/modules/system"]
EXTRA_JS_MODULES.sdk += [
'source/app-extension/bootstrap.js',
]
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/jetpack-addon.ini
@@ -0,0 +1,36 @@
+[addon-page.xpi]
+[author-email.xpi]
+[child_process.xpi]
+[chrome.xpi]
+[content-permissions.xpi]
+[contributors.xpi]
+[curly-id.xpi]
+[developers.xpi]
+[e10s.xpi]
+skip-if = true
+[e10s-tabs.xpi]
+skip-if = true
+[l10n.xpi]
+[l10n-properties.xpi]
+[layout-change.xpi]
+[main.xpi]
+[packaging.xpi]
+[packed.xpi]
+[page-mod-debugger-post.xpi]
+[page-mod-debugger-pre.xpi]
+[places.xpi]
+[predefined-id-with-at.xpi]
+[preferences-branch.xpi]
+[private-browsing-supported.xpi]
+skip-if = true
+[require.xpi]
+[self.xpi]
+[simple-prefs.xpi]
+[simple-prefs-l10n.xpi]
+[simple-prefs-regression.xpi]
+[standard-id.xpi]
+[symbiont.xpi]
+[tab-close-on-startup.xpi]
+[translators.xpi]
+[unpacked.xpi]
+[unsafe-content-script.xpi]
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/jetpack-package.ini
@@ -0,0 +1,164 @@
+[DEFAULT]
+support-files =
+ buffers/**
+ commonjs-test-adapter/**
+ event/**
+ fixtures/**
+ loader/**
+ modules/**
+ private-browsing/**
+ sidebar/**
+ tabs/**
+ traits/**
+ windows/**
+ zip/**
+ fixtures.js
+ pagemod-test-helpers.js
+ test-context-menu.html
+ test-tmp-file.txt
+
+[test-addon-installer.js]
+[test-addon-window.js]
+[test-api-utils.js]
+[test-array.js]
+[test-base64.js]
+[test-bootstrap.js]
+[test-browser-events.js]
+[test-buffer.js]
+[test-byte-streams.js]
+[test-child_process.js]
+[test-chrome.js]
+[test-clipboard.js]
+[test-collection.js]
+[test-commonjs-test-adapter.js]
+[test-content-events.js]
+[test-content-loader.js]
+[test-content-script.js]
+[test-content-symbiont.js]
+[test-content-worker.js]
+[test-context-menu.js]
+[test-cortex.js]
+[test-cuddlefish.js]
+# Cuddlefish loader is unsupported
+skip-if = true
+[test-deprecate.js]
+[test-deprecated-list.js]
+[test-dev-panel.js]
+[test-diffpatcher.js]
+[test-dispatcher.js]
+[test-disposable.js]
+[test-dom.js]
+[test-environment.js]
+[test-errors.js]
+[test-event-core.js]
+[test-event-target.js]
+[test-event-utils.js]
+[test-events.js]
+[test-file.js]
+[test-frame-utils.js]
+[test-fs.js]
+[test-functional.js]
+[test-globals.js]
+[test-heritage.js]
+[test-hidden-frame.js]
+[test-host-events.js]
+[test-hotkeys.js]
+[test-httpd.js]
+[test-indexed-db.js]
+[test-keyboard-observer.js]
+[test-keyboard-utils.js]
+[test-l10n-locale.js]
+[test-l10n-plural-rules.js]
+[test-libxul.js]
+[test-light-traits.js]
+[test-list.js]
+[test-loader.js]
+[test-match-pattern.js]
+[test-memory.js]
+[test-method.js]
+[test-module.js]
+[test-modules.js]
+[test-namespace.js]
+[test-native-loader.js]
+[test-native-options.js]
+[test-net-url.js]
+[test-node-os.js]
+[test-notifications.js]
+[test-object.js]
+[test-observers.js]
+[test-page-mod.js]
+[test-page-worker.js]
+[test-panel.js]
+[test-passwords-utils.js]
+[test-passwords.js]
+[test-path.js]
+[test-plain-text-console.js]
+[test-preferences-service.js]
+[test-preferences-target.js]
+[test-private-browsing.js]
+[test-promise.js]
+[test-querystring.js]
+[test-reference.js]
+[test-registry.js]
+[test-request.js]
+[test-require.js]
+[test-rules.js]
+[test-sandbox.js]
+[test-selection.js]
+[test-self.js]
+[test-sequence.js]
+[test-set-exports.js]
+[test-simple-prefs.js]
+[test-simple-storage.js]
+[test-system-events.js]
+[test-system-input-output.js]
+[test-system-runtime.js]
+[test-system-startup.js]
+[test-system.js]
+[test-tab-events.js]
+[test-tab-observer.js]
+[test-tab-utils.js]
+[test-tab.js]
+[test-tabs-common.js]
+[test-tabs.js]
+[test-test-loader.js]
+[test-test-memory.js]
+[test-test-utils-async.js]
+[test-test-utils-sync.js]
+[test-test-utils.js]
+[test-text-streams.js]
+[test-timer.js]
+[test-tmp-file.js]
+[test-traceback.js]
+[test-traits-core.js]
+[test-traits.js]
+[test-type.js]
+[test-ui-action-button.js]
+[test-ui-frame.js]
+[test-ui-id.js]
+[test-ui-sidebar-private-browsing.js]
+[test-ui-sidebar.js]
+[test-ui-toggle-button.js]
+[test-ui-toolbar.js]
+[test-unit-test-finder.js]
+[test-unit-test.js]
+[test-unload.js]
+[test-unsupported-skip.js]
+# Bug 1037235
+skip-if = true
+[test-url.js]
+[test-uuid.js]
+[test-weak-set.js]
+[test-widget.js]
+[test-window-events.js]
+[test-window-loader.js]
+[test-window-observer.js]
+[test-window-utils-global-private-browsing.js]
+[test-window-utils-private-browsing.js]
+[test-window-utils.js]
+[test-window-utils2.js]
+[test-windows-common.js]
+[test-windows.js]
+[test-xhr.js]
+[test-xpcom.js]
+[test-xul-app.js]
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -802,16 +802,24 @@ VARIABLES = {
'A11Y_MANIFESTS': (StrictOrderingOnAppendList, list,
"""List of manifest files defining a11y tests.
""", None),
'BROWSER_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list,
"""List of manifest files defining browser chrome tests.
""", None),
+ 'JETPACK_PACKAGE_MANIFESTS': (StrictOrderingOnAppendList, list,
+ """List of manifest files defining jetpack package tests.
+ """, None),
+
+ 'JETPACK_ADDON_MANIFESTS': (StrictOrderingOnAppendList, list,
+ """List of manifest files defining jetpack addon tests.
+ """, None),
+
'CRASHTEST_MANIFESTS': (StrictOrderingOnAppendList, list,
"""List of manifest files defining crashtests.
These are commonly named crashtests.list.
""", None),
'METRO_CHROME_MANIFESTS': (StrictOrderingOnAppendList, list,
"""List of manifest files defining metro browser chrome tests.
--- a/python/mozbuild/mozbuild/frontend/emitter.py
+++ b/python/mozbuild/mozbuild/frontend/emitter.py
@@ -759,16 +759,18 @@ class TreeMetadataEmitter(LoggingMixin):
# manifest.
#
# We ideally don't filter out inactive tests. However, not every test
# harness can yet deal with test filtering. Once all harnesses can do
# this, this feature can be dropped.
test_manifests = dict(
A11Y=('a11y', 'testing/mochitest', 'a11y', True),
BROWSER_CHROME=('browser-chrome', 'testing/mochitest', 'browser', True),
+ JETPACK_PACKAGE=('jetpack-package', 'testing/mochitest', 'jetpack-package', True),
+ JETPACK_ADDON=('jetpack-addon', 'testing/mochitest', 'jetpack-addon', True),
METRO_CHROME=('metro-chrome', 'testing/mochitest', 'metro', True),
MOCHITEST=('mochitest', 'testing/mochitest', 'tests', True),
MOCHITEST_CHROME=('chrome', 'testing/mochitest', 'chrome', True),
MOCHITEST_WEBAPPRT_CHROME=('webapprt-chrome', 'testing/mochitest', 'webapprtChrome', True),
WEBRTC_SIGNALLING_TEST=('steeplechase', 'steeplechase', '.', True),
XPCSHELL_TESTS=('xpcshell', 'xpcshell', '.', False),
)
@@ -845,21 +847,24 @@ class TreeMetadataEmitter(LoggingMixin):
filtered = m.tests
if filter_inactive:
# We return tests that don't exist because we want manifests
# defining tests that don't exist to result in error.
filtered = m.active_tests(exists=False, disabled=True,
**self.info)
- missing = [t['name'] for t in filtered if not os.path.exists(t['path'])]
- if missing:
- raise SandboxValidationError('Test manifest (%s) lists '
- 'test that does not exist: %s' % (
- path, ', '.join(missing)), context)
+ # Jetpack add-on tests are expected to be generated during the
+ # build process so they won't exist here.
+ if flavor != 'jetpack-addon':
+ missing = [t['name'] for t in filtered if not os.path.exists(t['path'])]
+ if missing:
+ raise SandboxValidationError('Test manifest (%s) lists '
+ 'test that does not exist: %s' % (
+ path, ', '.join(missing)), context)
out_dir = mozpath.join(install_prefix, manifest_reldir)
if 'install-to-subdir' in defaults:
# This is terrible, but what are you going to do?
out_dir = mozpath.join(out_dir, defaults['install-to-subdir'])
obj.manifest_obj_relpath = mozpath.join(manifest_reldir,
defaults['install-to-subdir'],
mozpath.basename(path))
@@ -920,18 +925,21 @@ class TreeMetadataEmitter(LoggingMixin):
continue
obj.installs[full] = (mozpath.normpath(dest_path),
False)
for test in filtered:
obj.tests.append(test)
- obj.installs[mozpath.normpath(test['path'])] = \
- (mozpath.join(out_dir, test['relpath']), True)
+ # Jetpack add-on tests are generated directly in the test
+ # directory
+ if flavor != 'jetpack-addon':
+ obj.installs[mozpath.normpath(test['path'])] = \
+ (mozpath.join(out_dir, test['relpath']), True)
process_support_files(test)
if not filtered:
# If there are no tests, look for support-files under DEFAULT.
process_support_files(defaults)
# We also copy manifests into the output directory,
--- a/python/mozbuild/mozbuild/testing.py
+++ b/python/mozbuild/mozbuild/testing.py
@@ -139,16 +139,20 @@ class TestResolver(MozbuildObject):
self._tests = TestMetadata(filename=os.path.join(self.topobjdir,
'all-tests.json'))
self._test_rewrites = {
'a11y': os.path.join(self.topobjdir, '_tests', 'testing',
'mochitest', 'a11y'),
'browser-chrome': os.path.join(self.topobjdir, '_tests', 'testing',
'mochitest', 'browser'),
+ 'jetpack-package': os.path.join(self.topobjdir, '_tests', 'testing',
+ 'mochitest', 'jetpack-package'),
+ 'jetpack-addon': os.path.join(self.topobjdir, '_tests', 'testing',
+ 'mochitest', 'jetpack-addon'),
'chrome': os.path.join(self.topobjdir, '_tests', 'testing',
'mochitest', 'chrome'),
'mochitest': os.path.join(self.topobjdir, '_tests', 'testing',
'mochitest', 'tests'),
'xpcshell': os.path.join(self.topobjdir, '_tests', 'xpcshell'),
}
def resolve_tests(self, cwd=None, **kwargs):
--- a/testing/mochitest/jar.mn
+++ b/testing/mochitest/jar.mn
@@ -1,13 +1,17 @@
mochikit.jar:
% content mochikit %content/
content/browser-harness.xul (browser-harness.xul)
content/browser-test.js (browser-test.js)
content/browser-test-overlay.xul (browser-test-overlay.xul)
+ content/jetpack-package-harness.js (jetpack-package-harness.js)
+ content/jetpack-package-overlay.xul (jetpack-package-overlay.xul)
+ content/jetpack-addon-harness.js (jetpack-addon-harness.js)
+ content/jetpack-addon-overlay.xul (jetpack-addon-overlay.xul)
content/cc-analyzer.js (cc-analyzer.js)
content/chrome-harness.js (chrome-harness.js)
content/mochitest-e10s-utils.js (mochitest-e10s-utils.js)
content/harness.xul (harness.xul)
content/redirect.html (redirect.html)
content/server.js (server.js)
content/chunkifyTests.js (chunkifyTests.js)
content/manifestLibrary.js (manifestLibrary.js)
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/jetpack-addon-harness.js
@@ -0,0 +1,211 @@
+/* -*- 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");
+
+// Start the tests after the window has been displayed
+window.addEventListener("load", function testOnLoad() {
+ window.removeEventListener("load", testOnLoad);
+ window.addEventListener("MozAfterPaint", function testOnMozAfterPaint() {
+ window.removeEventListener("MozAfterPaint", testOnMozAfterPaint);
+ setTimeout(testInit, 0);
+ });
+});
+
+let sdkpath = null;
+
+// Installs a single add-on returning a promise for when install is completed
+function installAddon(url) {
+ return new Promise(function(resolve, reject) {
+ AddonManager.getInstallForURL(url, function(install) {
+ install.addListener({
+ onDownloadEnded: function(install) {
+ // 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",
+ },
+ }
+ setPrefs("extensions." + install.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." + install.addon.id + ".path", paths);
+ }
+ },
+
+ onInstallEnded: function(install, addon) {
+ resolve(addon);
+ },
+
+ onInstallFailed: function(install) {
+ reject();
+ }
+ });
+
+ install.install();
+ }, "application/x-xpinstall");
+ });
+}
+
+// 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;
+
+ // Some add-ons do async work on uninstall, we must wait for that to
+ // complete
+ let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.init(resolve, 500, timer.TYPE_ONE_SHOT);
+ }
+ });
+
+ oldAddon.uninstall();
+ });
+}
+
+// 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.
+let testAddon = Task.async(function*(url) {
+ let addon = yield installAddon(url);
+ let results = yield waitForResults();
+ yield uninstallAddon(addon);
+
+ 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);
+ }
+
+ if (config.totalChunks && config.thisChunk) {
+ fileNames = chunkifyTests(fileNames, config.totalChunks,
+ config.thisChunk, config.chunkByDir);
+ }
+
+ // 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 an invalid --test-path?\n");
+ }
+ else {
+ dump("Jetpack Addon Test Summary\n");
+ dump("\tPassed: " + passed + "\n" +
+ "\tFailed: " + failed + "\n" +
+ "\tTodo: 0\n");
+ }
+
+ if (config.closeWhenDone) {
+ 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();
+ testAddon(filename).then(results => {
+ passed += results.passed;
+ failed += results.failed;
+ }).then(testNextAddon);
+ }
+
+ testNextAddon();
+ }
+ catch (e) {
+ dump("TEST-UNEXPECTED-FAIL: jetpack-addon-harness.js | error starting test harness (" + e + ")\n");
+ dump(e.stack);
+ }
+ });
+}
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/jetpack-addon-overlay.xul
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<!-- 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/. -->
+
+<overlay id="jetpackTestOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/manifestLibrary.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/chunkifyTests.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/server.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/jetpack-addon-harness.js"/>
+</overlay>
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/jetpack-package-harness.js
@@ -0,0 +1,250 @@
+/* -*- js-indent-level: 2; tab-width: 2; indent-tabs-mode: nil -*- */
+const TEST_PACKAGE = "chrome://mochitests/content/";
+const TEST_ID = "jetpack-tests@mozilla.org";
+
+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");
+
+// Start the tests after the window has been displayed
+window.addEventListener("load", function testOnLoad() {
+ window.removeEventListener("load", testOnLoad);
+ window.addEventListener("MozAfterPaint", function testOnMozAfterPaint() {
+ window.removeEventListener("MozAfterPaint", testOnMozAfterPaint);
+ setTimeout(testInit, 0);
+ });
+});
+
+// Tests a single module
+function testModule(require, url) {
+ return new Promise(resolve => {
+ let path = url.substring(TEST_PACKAGE.length);
+
+ const { stdout } = require("sdk/system");
+
+ const { runTests } = require("sdk/test/harness");
+ const loaderModule = require("toolkit/loader");
+ const options = require("sdk/test/options");
+
+ function findAndRunTests(loader, nextIteration) {
+ const { TestRunner } = loaderModule.main(loader, "sdk/deprecated/unit-test");
+
+ const NOT_TESTS = ['setup', 'teardown'];
+ var runner = new TestRunner();
+
+ let tests = [];
+
+ let suiteModule;
+ try {
+ dump("TEST-INFO: " + path + " | Loading test module\n");
+ suiteModule = loaderModule.main(loader, "tests/" + path.substring(0, path.length - 3));
+ }
+ catch (e) {
+ // If `Unsupported Application` error thrown during test,
+ // skip the test suite
+ suiteModule = {
+ 'test suite skipped': assert => assert.pass(e.message)
+ };
+ }
+
+ for (let name of Object.keys(suiteModule).sort()) {
+ if (NOT_TESTS.indexOf(name) != -1)
+ continue;
+
+ tests.push({
+ setup: suiteModule.setup,
+ teardown: suiteModule.teardown,
+ testFunction: suiteModule[name],
+ name: path + "." + name
+ });
+ }
+
+ runner.startMany({
+ tests: {
+ getNext: () => Promise.resolve(tests.shift())
+ },
+ stopOnError: options.stopOnError,
+ onDone: nextIteration
+ });
+ }
+
+ runTests({
+ findAndRunTests: findAndRunTests,
+ iterations: options.iterations,
+ filter: options.filter,
+ profileMemory: options.profileMemory,
+ stopOnError: options.stopOnError,
+ verbose: options.verbose,
+ parseable: options.parseable,
+ print: stdout.write,
+ onDone: resolve
+ });
+ });
+}
+
+// Sets the test prefs
+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 = /test-.+\.js$/;
+ arrayOfTestFiles(links, fileNames, fileNameRegexp);
+
+ if (config.startAt || config.endAt) {
+ fileNames = skipTests(fileNames, config.startAt, config.endAt);
+ }
+
+ if (config.totalChunks && config.thisChunk) {
+ fileNames = chunkifyTests(fileNames, config.totalChunks,
+ config.thisChunk, config.chunkByDir);
+ }
+
+ // The SDK assumes it is being run from resource URIs
+ let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIChromeRegistry);
+ let realPath = chromeReg.convertChromeURL(Services.io.newURI(TEST_PACKAGE, null, null));
+ let resProtocol = Cc["@mozilla.org/network/protocol;1?name=resource"].getService(Ci.nsIResProtocolHandler);
+ resProtocol.setSubstitution("jetpack-package-tests", realPath);
+
+ // Set the test options
+ const options = {
+ test: {
+ iterations: config.runUntilFailure ? config.repeat : 1,
+ stop: false,
+ keepOpen: true,
+ },
+ profile: {
+ memory: false,
+ leaks: false,
+ },
+ output: {
+ logLevel: "verbose",
+ format: "tbpl",
+ },
+ }
+ setPrefs("extensions." + TEST_ID + ".sdk", options);
+
+ // Override the SDK modules if necessary
+ let sdkpath = "resource://gre/modules/commonjs/";
+ try {
+ let sdklibs = Services.prefs.getCharPref("extensions.sdk.path");
+ // sdkpath is a file path, make it a URI and map a resource URI to it
+ let sdkfile = Cc["@mozilla.org/file/local;1"].
+ createInstance(Ci.nsIFile);
+ sdkfile.initWithPath(sdklibs);
+ let sdkuri = Services.io.newFileURI(sdkfile);
+ resProtocol.setSubstitution("jetpack-modules", sdkuri);
+ sdkpath = "resource://jetpack-modules/";
+ }
+ catch (e) {
+ // Stick with the built-in modules
+ }
+
+ const paths = {
+ "": sdkpath,
+ "tests/": "resource://jetpack-package-tests/",
+ };
+
+ // Create the base module loader to load the test harness
+ const loaderID = "toolkit/loader";
+ const loaderURI = paths[""] + loaderID + ".js";
+ const loaderModule = Cu.import(loaderURI, {}).Loader;
+
+ const modules = {};
+
+ // Manually set the loader's module cache to include itself;
+ // which otherwise fails due to lack of `Components`.
+ modules[loaderID] = loaderModule;
+ modules["@test/options"] = {};
+
+ let loader = loaderModule.Loader({
+ id: TEST_ID,
+ name: "addon-sdk",
+ version: "1.0",
+ loadReason: "install",
+ paths: paths,
+ modules: modules,
+ isNative: true,
+ rootURI: paths["tests/"],
+ prefixURI: paths["tests/"],
+ metadata: {},
+ });
+
+ const module = loaderModule.Module(loaderID, loaderURI);
+ const require = loaderModule.Require(loader, module);
+
+ // Wait until the add-on window is ready
+ require("sdk/addon/window").ready.then(() => {
+ let passed = 0;
+ let failed = 0;
+
+ function finish() {
+ if (passed + failed == 0) {
+ dump("TEST-UNEXPECTED-FAIL | jetpack-package-harness.js | " +
+ "No tests to run. Did you pass an invalid --test-path?\n");
+ }
+ else {
+ dump("Jetpack Package Test Summary\n");
+ dump("\tPassed: " + passed + "\n" +
+ "\tFailed: " + failed + "\n" +
+ "\tTodo: 0\n");
+ }
+
+ if (config.closeWhenDone) {
+ require("sdk/system").exit(failed == 0 ? 0 : 1);
+ }
+ }
+
+ function testNextModule() {
+ if (fileNames.length == 0)
+ return finish();
+
+ let filename = fileNames.shift();
+ testModule(require, filename).then(tests => {
+ passed += tests.passed;
+ failed += tests.failed;
+ }).then(testNextModule);
+ }
+
+ testNextModule();
+ });
+ }
+ catch (e) {
+ dump("TEST-UNEXPECTED-FAIL: jetpack-package-harness.js | error starting test harness (" + e + ")\n");
+ dump(e.stack);
+ }
+ });
+}
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/jetpack-package-overlay.xul
@@ -0,0 +1,14 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<!-- 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/. -->
+
+<overlay id="jetpackTestOverlay"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/manifestLibrary.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/chunkifyTests.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/server.js"/>
+ <script type="application/javascript" src="chrome://mochikit/content/jetpack-package-harness.js"/>
+</overlay>
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -68,16 +68,18 @@ There should be an app called 'test-cont
%s.
'''.lstrip()
# Maps test flavors to mochitest suite type.
FLAVORS = {
'mochitest': 'plain',
'chrome': 'chrome',
'browser-chrome': 'browser',
+ 'jetpack-package': 'jetpack-package',
+ 'jetpack-addon': 'jetpack-addon',
'a11y': 'a11y',
'webapprt-chrome': 'webapprt-chrome',
}
class MochitestRunner(MozbuildObject):
"""Easily run mochitests.
@@ -195,17 +197,17 @@ class MochitestRunner(MozbuildObject):
useTestMediaDevices=False, **kwargs):
"""Runs a mochitest.
test_paths are path to tests. They can be a relative path from the
top source directory, an absolute filename, or a directory containing
test files.
suite is the type of mochitest to run. It can be one of ('plain',
- 'chrome', 'browser', 'metro', 'a11y').
+ 'chrome', 'browser', 'metro', 'a11y', 'jetpack-package', 'jetpack-addon').
debugger is a program name or path to a binary (presumably a debugger)
to run the test in. e.g. 'gdb'
debugger_args are the arguments passed to the debugger.
slowscript is true if the user has requested the SIGSEGV mechanism of
invoking the slow script dialog.
@@ -264,16 +266,20 @@ class MochitestRunner(MozbuildObject):
elif suite == 'chrome':
options.chrome = True
elif suite == 'browser':
options.browserChrome = True
flavor = 'browser-chrome'
elif suite == 'devtools':
options.browserChrome = True
options.subsuite = 'devtools'
+ elif suite == 'jetpack-package':
+ options.jetpackPackage = True
+ elif suite == 'jetpack-addon':
+ options.jetpackAddon = True
elif suite == 'metro':
options.immersiveMode = True
options.browserChrome = True
elif suite == 'a11y':
options.a11y = True
elif suite == 'webapprt-content':
options.webapprtContent = True
options.app = self.get_webapp_runtime_path()
@@ -643,16 +649,30 @@ class MachCommands(MachCommandBase):
@Command('mochitest-devtools', category='testing',
conditions=[conditions.is_firefox],
description='Run a devtools mochitest with browser chrome (integration test with a standard browser with the devtools frame).',
parser=_st_parser)
@MochitestCommand
def run_mochitest_devtools(self, test_paths, **kwargs):
return self.run_mochitest(test_paths, 'devtools', **kwargs)
+ @Command('jetpack-package', category='testing',
+ conditions=[conditions.is_firefox],
+ description='Run a jetpack package test.')
+ @MochitestCommand
+ def run_mochitest_jetpack_package(self, test_paths, **kwargs):
+ return self.run_mochitest(test_paths, 'jetpack-package', **kwargs)
+
+ @Command('jetpack-addon', category='testing',
+ conditions=[conditions.is_firefox],
+ description='Run a jetpack addon test.')
+ @MochitestCommand
+ def run_mochitest_jetpack_addon(self, test_paths, **kwargs):
+ return self.run_mochitest(test_paths, 'jetpack-addon', **kwargs)
+
@Command('mochitest-metro', category='testing',
conditions=[conditions.is_firefox],
description='Run a mochitest with metro browser chrome (tests for Windows touch interface).',
parser=_st_parser)
@MochitestCommand
def run_mochitest_metro(self, test_paths, **kwargs):
return self.run_mochitest(test_paths, 'metro', **kwargs)
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -160,16 +160,28 @@ class MochitestOptions(optparse.OptionPa
"default": False,
}],
[["--subsuite"],
{ "action": "store",
"dest": "subsuite",
"help": "subsuite of tests to run",
"default": "",
}],
+ [["--jetpack-package"],
+ { "action": "store_true",
+ "dest": "jetpackPackage",
+ "help": "run jetpack package tests",
+ "default": False,
+ }],
+ [["--jetpack-addon"],
+ { "action": "store_true",
+ "dest": "jetpackAddon",
+ "help": "run jetpack addon tests",
+ "default": False,
+ }],
[["--webapprt-content"],
{ "action": "store_true",
"dest": "webapprtContent",
"help": "run WebappRT content tests",
"default": False,
}],
[["--webapprt-chrome"],
{ "action": "store_true",
--- a/testing/mochitest/moz.build
+++ b/testing/mochitest/moz.build
@@ -49,16 +49,20 @@ TEST_HARNESS_FILES.testing.mochitest +=
'browser-test-overlay.xul',
'browser-test.js',
'cc-analyzer.js',
'chrome-harness.js',
'chunkifyTests.js',
'gen_template.pl',
'gl.json',
'harness.xul',
+ 'jetpack-addon-harness.js',
+ 'jetpack-addon-overlay.xul',
+ 'jetpack-package-harness.js',
+ 'jetpack-package-overlay.xul',
'manifest.webapp',
'manifestLibrary.js',
'mochitest_options.py',
'pywebsocket_wrapper.py',
'redirect.html',
'runtests.py',
'runtestsb2g.py',
'runtestsremote.py',
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -499,17 +499,18 @@ class MochitestUtilsMixin(object):
options.fileLevel = 'INFO'
# allow relative paths for logFile
if options.logFile:
options.logFile = self.getLogFilePath(options.logFile)
# Note that all tests under options.subsuite need to be browser chrome tests.
if options.browserChrome or options.chrome or options.subsuite or \
- options.a11y or options.webapprtChrome:
+ options.a11y or options.webapprtChrome or options.jetpackPackage or \
+ options.jetpackAddon:
self.makeTestConfig(options)
else:
if options.autorun:
self.urlOpts.append("autorun=1")
if options.timeout:
self.urlOpts.append("timeout=%d" % options.timeout)
if options.closeWhenDone:
self.urlOpts.append("closeWhenDone=1")
@@ -558,31 +559,40 @@ class MochitestUtilsMixin(object):
if options.dumpDMDAfterTest:
self.urlOpts.append("dumpDMDAfterTest=true")
if options.debugger:
self.urlOpts.append("interactiveDebugger=true")
def getTestFlavor(self, options):
if options.browserChrome:
return "browser-chrome"
+ elif options.jetpackPackage:
+ return "jetpack-package"
+ elif options.jetpackAddon:
+ return "jetpack-addon"
elif options.chrome:
return "chrome"
elif options.a11y:
return "a11y"
elif options.webapprtChrome:
return "webapprt-chrome"
else:
return "mochitest"
# This check can be removed when bug 983867 is fixed.
def isTest(self, options, filename):
allow_js_css = False
if options.browserChrome:
allow_js_css = True
testPattern = re.compile(r"browser_.+\.js")
+ elif options.jetpackPackage:
+ allow_js_css = True
+ testPattern = re.compile(r"test-.+\.js")
+ elif options.jetpackAddon:
+ testPattern = re.compile(r".+\.xpi")
elif options.chrome or options.a11y:
testPattern = re.compile(r"(browser|test)_.+\.(xul|html|js|xhtml)")
elif options.webapprtContent:
testPattern = re.compile(r"webapprt_")
elif options.webapprtChrome:
allow_js_css = True
testPattern = re.compile(r"browser_")
else:
@@ -606,16 +616,20 @@ class MochitestUtilsMixin(object):
if hasattr(self, "testRoot"):
return self.testRoot, self.testRootAbs
else:
if options.browserChrome:
if options.immersiveMode:
self.testRoot = 'metro'
else:
self.testRoot = 'browser'
+ elif options.jetpackPackage:
+ self.testRoot = 'jetpack-package'
+ elif options.jetpackAddon:
+ self.testRoot = 'jetpack-addon'
elif options.a11y:
self.testRoot = 'a11y'
elif options.webapprtChrome:
self.testRoot = 'webapprtChrome'
elif options.chrome:
self.testRoot = 'chrome'
else:
self.testRoot = self.TEST_PATH
@@ -624,17 +638,17 @@ class MochitestUtilsMixin(object):
def buildTestURL(self, options):
testHost = "http://mochi.test:8888"
testPath = self.getTestPath(options)
testURL = "/".join([testHost, self.TEST_PATH, testPath])
if os.path.isfile(os.path.join(self.oldcwd, os.path.dirname(__file__), self.TEST_PATH, testPath)) and options.repeat > 0:
testURL = "/".join([testHost, self.TEST_PATH, os.path.dirname(testPath)])
if options.chrome or options.a11y:
testURL = "/".join([testHost, self.CHROME_PATH])
- elif options.browserChrome:
+ elif options.browserChrome or options.jetpackPackage or options.jetpackAddon:
testURL = "about:blank"
return testURL
def buildTestPath(self, options, testsToFilter=None, disabled=True):
""" Build the url path to the specific test harness and test file or directory
Build a manifest of tests to run and write out a json file for the harness to read
testsToFilter option is used to filter/keep the tests provided in the list
@@ -793,16 +807,26 @@ toolbar#nav-bar {
if options.browserChrome or options.chrome or options.a11y or options.webapprtChrome:
chrome += """
overlay chrome://browser/content/browser.xul chrome://mochikit/content/browser-test-overlay.xul
overlay chrome://browser/content/shell.xhtml chrome://mochikit/content/browser-test-overlay.xul
overlay chrome://navigator/content/navigator.xul chrome://mochikit/content/browser-test-overlay.xul
overlay chrome://webapprt/content/webapp.xul chrome://mochikit/content/browser-test-overlay.xul
"""
+ if options.jetpackPackage:
+ chrome += """
+overlay chrome://browser/content/browser.xul chrome://mochikit/content/jetpack-package-overlay.xul
+"""
+
+ if options.jetpackAddon:
+ chrome += """
+overlay chrome://browser/content/browser.xul chrome://mochikit/content/jetpack-addon-overlay.xul
+"""
+
self.installChromeJar(chrome, options)
return manifest
def getExtensionsToInstall(self, options):
"Return a list of extensions to install in the profile"
extensions = options.extensionsToInstall or []
appDir = options.app[:options.app.rfind(os.sep)] if options.app else options.utilityPath