def create_xpi(xpiname, pkg_name="aardvark", dirname="static-files"): configs = test_packaging.get_configs(pkg_name, dirname) options = {"main": configs.target_cfg.main} options.update(configs.build) xpi.build_xpi( template_root_dir=xpi_template_path, manifest=fake_manifest, xpi_name=xpiname, harness_options=options )
def create_xpi(xpiname, pkg_name='aardvark', dirname='static-files'): configs = test_packaging.get_configs(pkg_name, dirname) options = {'main': configs.target_cfg.main} options.update(configs.build) xpi.build_xpi(template_root_dir=xpi_template_path, manifest=fake_manifest, xpi_name=xpiname, harness_options=options)
def create_xpi(xpiname): configs = test_packaging.get_configs('aardvark') options = {'main': configs.target_cfg.main} options.update(configs.build) xpi.build_xpi(template_root_dir=xpi_template_path, manifest=fake_manifest, xpi_name=xpiname, harness_options=options, xpts=[])
def create_xpi(xpiname, pkg_name='aardvark', dirname='static-files', extra_harness_options={}): configs = test_packaging.get_configs(pkg_name, dirname) options = {'main': configs.target_cfg.main} options.update(configs.build) xpi.build_xpi(template_root_dir=xpi_template_path, manifest=fake_manifest, xpi_path=xpiname, harness_options=options, extra_harness_options=extra_harness_options)
def test_scantests_filter(self): target_cfg = self.get_pkg("three") package_path = [self.get_linker_files_dir("three-deps")] pkg_cfg = packaging.build_config(self.root, target_cfg, packagepath=package_path) deps = packaging.get_deps_for_targets(pkg_cfg, [target_cfg.name, "addon-sdk"]) FILTER = ".*one.*" m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=True, test_filter_re=FILTER) self.failUnlessEqual(sorted(m.get_all_test_modules()), sorted(["test-one"])) # the current __init__.py code omits limit_to=used_files for 'cfx # test', so all test files are included in the XPI. But the test # runner will only execute the tests that m.get_all_test_modules() # tells us about (which are put into the .allTestModules property of # harness-options.json). used_deps = m.get_used_packages() build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name, used_deps, include_tests=True) options = {'main': target_cfg.main} options.update(build) basedir = self.make_basedir() xpi_name = os.path.join(basedir, "contents.xpi") xpi.build_xpi(template_root_dir=xpi_template_path, manifest=fake_manifest, xpi_path=xpi_name, harness_options=options, limit_to=None) x = zipfile.ZipFile(xpi_name, "r") names = x.namelist() self.failUnless( "resources/addon-sdk/lib/sdk/deprecated/unit-test.js" in names, names) self.failUnless( "resources/addon-sdk/lib/sdk/deprecated/unit-test-finder.js" in names, names) self.failUnless("resources/addon-sdk/lib/sdk/test/harness.js" in names, names) self.failUnless("resources/addon-sdk/lib/sdk/test/runner.js" in names, names) # get_all_test_modules() respects the filter. But all files are still # copied into the XPI. self.failUnless("resources/three/tests/test-one.js" in names, names) self.failUnless("resources/three/tests/test-two.js" in names, names) self.failUnless("resources/three/tests/nontest.js" in names, names)
def test_scantests_filter(self): target_cfg = self.get_pkg("three") package_path = [self.get_linker_files_dir("three-deps")] pkg_cfg = packaging.build_config(self.root, target_cfg, packagepath=package_path) deps = packaging.get_deps_for_targets(pkg_cfg, [target_cfg.name, "addon-sdk"]) FILTER = ".*one.*" m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=True, test_filter_re=FILTER) self.failUnlessEqual(sorted(m.get_all_test_modules()), sorted(["test-one"])) # the current __init__.py code omits limit_to=used_files for 'cfx # test', so all test files are included in the XPI. But the test # runner will only execute the tests that m.get_all_test_modules() # tells us about (which are put into the .allTestModules property of # harness-options.json). used_deps = m.get_used_packages() build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name, used_deps, include_tests=True) options = {'main': target_cfg.main} options.update(build) basedir = self.make_basedir() xpi_name = os.path.join(basedir, "contents.xpi") xpi.build_xpi(template_root_dir=xpi_template_path, manifest=fake_manifest, xpi_path=xpi_name, harness_options=options, limit_to=None) x = zipfile.ZipFile(xpi_name, "r") names = x.namelist() self.failUnless("resources/addon-sdk/lib/sdk/deprecated/unit-test.js" in names, names) self.failUnless("resources/addon-sdk/lib/sdk/deprecated/unit-test-finder.js" in names, names) self.failUnless("resources/addon-sdk/lib/sdk/test/harness.js" in names, names) self.failUnless("resources/addon-sdk/lib/sdk/test/runner.js" in names, names) # get_all_test_modules() respects the filter. But all files are still # copied into the XPI. self.failUnless("resources/three/tests/test-one.js" in names, names) self.failUnless("resources/three/tests/test-two.js" in names, names) self.failUnless("resources/three/tests/nontest.js" in names, names)
def test_contents(self): target_cfg = self.get_pkg("three") package_path = [self.get_linker_files_dir("three-deps")] pkg_cfg = packaging.build_config(self.root, target_cfg, packagepath=package_path) deps = packaging.get_deps_for_targets(pkg_cfg, [target_cfg.name, "addon-kit"]) m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) used_files = list(m.get_used_files()) here = up(os.path.abspath(__file__)) def absify(*parts): fn = os.path.join(here, "linker-files", *parts) return os.path.abspath(fn) expected = [absify(*parts) for parts in [("three", "lib", "main.js"), ("three-deps", "three-a", "lib", "main.js"), ("three-deps", "three-a", "lib", "subdir", "subfile.js"), ("three-deps", "three-a", "data", "msg.txt"), ("three-deps", "three-a", "data", "subdir", "submsg.txt"), ("three-deps", "three-b", "lib", "main.js"), ("three-deps", "three-c", "lib", "main.js"), ("three-deps", "three-c", "lib", "sub", "foo.js"), ]] missing = set(expected) - set(used_files) extra = set(used_files) - set(expected) self.failUnlessEqual((list(missing), list(extra)), ([], [])) used_deps = m.get_used_packages() build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name, used_deps, include_tests=False) options = {'main': target_cfg.main} options.update(build) basedir = self.make_basedir() xpi_name = os.path.join(basedir, "contents.xpi") xpi.build_xpi(template_root_dir=xpi_template_path, manifest=fake_manifest, xpi_path=xpi_name, harness_options=options, limit_to=used_files) x = zipfile.ZipFile(xpi_name, "r") names = x.namelist() expected = ["components/", "components/harness.js", # the real template also has 'bootstrap.js', but the fake # one in tests/static-files/xpi-template doesn't "harness-options.json", "install.rdf", "resources/", "resources/api-utils/", "resources/api-utils/data/", "resources/api-utils/lib/", "resources/three/", "resources/three/lib/", "resources/three/lib/main.js", "resources/three-a/", "resources/three-a/data/", "resources/three-a/data/msg.txt", "resources/three-a/data/subdir/", "resources/three-a/data/subdir/submsg.txt", "resources/three-a/lib/", "resources/three-a/lib/main.js", "resources/three-a/lib/subdir/", "resources/three-a/lib/subdir/subfile.js", "resources/three-b/", "resources/three-b/lib/", "resources/three-b/lib/main.js", "resources/three-c/", "resources/three-c/lib/", "resources/three-c/lib/main.js", "resources/three-c/lib/sub/", "resources/three-c/lib/sub/foo.js", # notably absent: three-a/lib/unused.js ] # showing deltas makes failures easier to investigate missing = set(expected) - set(names) extra = set(names) - set(expected) self.failUnlessEqual((list(missing), list(extra)), ([], [])) self.failUnlessEqual(sorted(names), sorted(expected))
def test_contents(self): target_cfg = self.get_pkg("three") package_path = [self.get_linker_files_dir("three-deps")] pkg_cfg = packaging.build_config(self.root, target_cfg, packagepath=package_path) deps = packaging.get_deps_for_targets(pkg_cfg, [target_cfg.name, "addon-kit"]) m = manifest.build_manifest(target_cfg, pkg_cfg, deps, "P/", scan_tests=False) used_files = list(m.get_used_files()) here = up(os.path.abspath(__file__)) def absify(*parts): fn = os.path.join(here, "linker-files", *parts) return os.path.abspath(fn) expected = [ absify(*parts) for parts in [ ("three", "lib", "main.js"), ("three-deps", "three-a", "lib", "main.js"), ("three-deps", "three-b", "lib", "main.js"), ("three-deps", "three-c", "lib", "main.js"), ("three-deps", "three-c", "lib", "sub", "foo.js"), ] ] self.failUnlessEqual(sorted(used_files), sorted(expected)) used_deps = m.get_used_packages() build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name, used_deps, prefix="p-", include_tests=False) options = {'main': target_cfg.main} options.update(build) basedir = self.make_basedir() xpi_name = os.path.join(basedir, "contents.xpi") xpi.build_xpi(template_root_dir=xpi_template_path, manifest=fake_manifest, xpi_name=xpi_name, harness_options=options, limit_to=used_files) x = zipfile.ZipFile(xpi_name, "r") names = x.namelist() expected = [ "components/harness.js", # the real template also has 'bootstrap.js', but the fake # one in tests/static-files/xpi-template doesn't "harness-options.json", "install.rdf", "resources/p-api-utils-data/", "resources/p-api-utils-lib/", "resources/p-three-lib/", "resources/p-three-lib/main.js", "resources/p-three-a-lib/", "resources/p-three-a-lib/main.js", "resources/p-three-b-lib/", "resources/p-three-b-lib/main.js", "resources/p-three-c-lib/", "resources/p-three-c-lib/main.js", "resources/p-three-c-lib/sub/foo.js", # notably absent: p-three-a-lib/unused.js ] # showing deltas makes failures easier to investigate missing = set(expected) - set(names) self.failUnlessEqual(list(missing), []) extra = set(names) - set(expected) self.failUnlessEqual(list(extra), []) self.failUnlessEqual(sorted(names), sorted(expected))
used_files = set(manifest.get_used_files()) if options.no_strip_xpi: used_files = None # disables the filter, includes all files if command == 'xpi': from cuddlefish.xpi import build_xpi extra_harness_options = {} for kv in options.extra_harness_option_args: key,value = kv.split("=", 1) extra_harness_options[key] = value xpi_path = XPI_FILENAME % target_cfg.name print >>stdout, "Exporting extension to %s." % xpi_path build_xpi(template_root_dir=app_extension_dir, manifest=manifest_rdf, xpi_path=xpi_path, harness_options=harness_options, limit_to=used_files, extra_harness_options=extra_harness_options) else: from cuddlefish.runner import run_app if options.profiledir: options.profiledir = os.path.expanduser(options.profiledir) options.profiledir = os.path.abspath(options.profiledir) if options.addons is not None: options.addons = options.addons.split(",") try: retval = run_app(harness_root_dir=app_extension_dir, manifest_rdf=manifest_rdf,
# Generate extra options extra_harness_options = {} for kv in options.extra_harness_option_args: key, value = kv.split("=", 1) extra_harness_options[key] = value # Generate xpi filepath if options.output_file: xpi_path = options.output_file else: xpi_path = XPI_FILENAME % target_cfg.name print >> stdout, "Exporting extension to %s." % xpi_path build_xpi(template_root_dir=app_extension_dir, manifest=manifest_rdf, xpi_path=xpi_path, harness_options=harness_options, limit_to=used_files, extra_harness_options=extra_harness_options, bundle_sdk=True, pkgdir=options.pkgdir) else: from cuddlefish.runner import run_app if options.profiledir: options.profiledir = os.path.expanduser(options.profiledir) options.profiledir = os.path.abspath(options.profiledir) if options.addons is not None: options.addons = options.addons.split(",") try: retval = run_app(harness_root_dir=app_extension_dir,
def run( arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, defaults=None, env_root=os.environ.get("CUDDLEFISH_ROOT"), stdout=sys.stdout, ): parser_kwargs = dict( arguments=arguments, global_options=global_options, parser_groups=parser_groups, usage=usage, defaults=defaults ) (options, args) = parse_args(**parser_kwargs) config_args = get_config_args(options.config, env_root) # reparse configs with arguments from local.json if config_args: parser_kwargs["arguments"] += config_args (options, args) = parse_args(**parser_kwargs) command = args[0] if command == "init": initializer(env_root, args) return if command == "develop": run_development_mode(env_root, defaults=options.__dict__) return if command == "testpkgs": test_all_packages(env_root, defaults=options.__dict__) return elif command == "testex": test_all_examples(env_root, defaults=options.__dict__) return elif command == "testall": test_all(env_root, defaults=options.__dict__) return elif command == "testcfx": test_cfx(env_root, options.verbose) return elif command == "docs": import subprocess import time import cuddlefish.server print >> stdout, "One moment." popen = subprocess.Popen([sys.executable, cuddlefish.server.__file__, "daemonic"]) # TODO: See if there's actually a way to block on # a particular event occurring, rather than this # relatively arbitrary/generous amount. time.sleep(cuddlefish.server.IDLE_WEBPAGE_TIMEOUT * 2) return elif command == "sdocs": import cuddlefish.server # TODO: Allow user to change this filename via cmd line. filename = "addon-sdk-docs.tgz" cuddlefish.server.generate_static_docs(env_root, filename, options.baseurl) print >> stdout, "Wrote %s." % filename return target_cfg_json = None if not target_cfg: if not options.pkgdir: options.pkgdir = find_parent_package(os.getcwd()) if not options.pkgdir: print >> sys.stderr, ("cannot find 'package.json' in the" " current directory or any parent.") sys.exit(1) else: options.pkgdir = os.path.abspath(options.pkgdir) if not os.path.exists(os.path.join(options.pkgdir, "package.json")): print >> sys.stderr, ("cannot find 'package.json' in" " %s." % options.pkgdir) sys.exit(1) target_cfg_json = os.path.join(options.pkgdir, "package.json") target_cfg = packaging.get_config_in_dir(options.pkgdir) # At this point, we're either building an XPI or running Jetpack code in # a Mozilla application (which includes running tests). use_main = False timeout = None inherited_options = ["verbose", "enable_e10s"] if command == "xpi": use_main = True elif command == "test": if "tests" not in target_cfg: target_cfg["tests"] = [] timeout = TEST_RUN_TIMEOUT inherited_options.extend(["iterations", "filter", "profileMemory"]) elif command == "run": use_main = True else: print >> sys.stderr, "Unknown command: %s" % command print >> sys.stderr, "Try using '--help' for assistance." sys.exit(1) if use_main and "main" not in target_cfg: # If the user supplies a template dir, then the main # program may be contained in the template. if not options.templatedir: print >> sys.stderr, "package.json does not have a 'main' entry." sys.exit(1) if not pkg_cfg: pkg_cfg = packaging.build_config(env_root, target_cfg, options.packagepath) target = target_cfg.name # the harness_guid is used for an XPCOM class ID. We use the # JetpackID for the add-on ID and the XPCOM contract ID. if "harnessClassID" in target_cfg: # For the sake of non-bootstrapped extensions, we allow to specify the # classID of harness' XPCOM component in package.json. This makes it # possible to register the component using a static chrome.manifest file harness_guid = target_cfg["harnessClassID"] else: import uuid harness_guid = str(uuid.uuid4()) # TODO: Consider keeping a cache of dynamic UUIDs, based # on absolute filesystem pathname, in the root directory # or something. if command in ("xpi", "run"): from cuddlefish.preflight import preflight_config if target_cfg_json: config_was_ok, modified = preflight_config(target_cfg, target_cfg_json) if not config_was_ok: if modified: # we need to re-read package.json . The safest approach # is to re-run the "cfx xpi"/"cfx run" command. print >> sys.stderr, ("package.json modified: please re-run" " 'cfx %s'" % command) else: print >> sys.stderr, ( "package.json needs modification:" " please update it and then re-run" " 'cfx %s'" % command ) sys.exit(1) # if we make it this far, we have a JID else: assert command == "test" if "id" in target_cfg: jid = target_cfg["id"] if not ("@" in jid or jid.startswith("{")): jid = jid + "@jetpack" unique_prefix = "%s-" % jid # used for resource: URLs else: # The Jetpack ID is not required for cfx test, in which case we have to # make one up based on the GUID. if options.use_server: # The harness' contractID (hence also the jid and the harness_guid) # need to be static in the "development mode", so that bootstrap.js # can unload the previous version of the package being developed. harness_guid = "2974c5b5-b671-46f8-a4bb-63c6eca6261b" unique_prefix = "%s-" % target jid = harness_guid bundle_id = jid # the resource: URL's prefix is treated too much like a DNS hostname unique_prefix = unique_prefix.lower() unique_prefix = unique_prefix.replace("@", "-at-") unique_prefix = unique_prefix.replace(".", "-dot-") targets = [target] if command == "test": targets.append(options.test_runner_pkg) extra_packages = [] if options.extra_packages: extra_packages = options.extra_packages.split(",") if extra_packages: targets.extend(extra_packages) target_cfg.extra_dependencies = extra_packages deps = packaging.get_deps_for_targets(pkg_cfg, targets) from cuddlefish.manifest import build_manifest uri_prefix = "resource://%s" % unique_prefix # Figure out what loader files should be scanned. This is normally # computed inside packaging.generate_build_for_target(), by the first # dependent package that defines a "loader" property in its package.json. # This property is interpreted as a filename relative to the top of that # file, and stored as a URI in build.loader . generate_build_for_target() # cannot be called yet (it needs the list of used_deps that # build_manifest() computes, but build_manifest() needs the list of # loader files that it computes). We could duplicate or factor out this # build.loader logic, but that would be messy, so instead we hard-code # the choice of loader for manifest-generation purposes. In practice, # this means that alternative loaders probably won't work with # --strip-xpi. assert packaging.DEFAULT_LOADER == "api-utils" assert pkg_cfg.packages["api-utils"].loader == "lib/cuddlefish.js" cuddlefish_js_path = os.path.join(pkg_cfg.packages["api-utils"].root_dir, "lib", "cuddlefish.js") loader_modules = [("api-utils", "lib", "cuddlefish", cuddlefish_js_path)] manifest = build_manifest(target_cfg, pkg_cfg, deps, uri_prefix, False, loader_modules) used_deps = manifest.get_used_packages() if command == "test": # The test runner doesn't appear to link against any actual packages, # because it loads everything at runtime (invisible to the linker). # If we believe that, we won't set up URI mappings for anything, and # tests won't be able to run. used_deps = deps for xp in extra_packages: if xp not in used_deps: used_deps.append(xp) build = packaging.generate_build_for_target( pkg_cfg, target, used_deps, prefix=unique_prefix, # used to create resource: URLs include_dep_tests=options.dep_tests, ) if "resources" in build: resources = build.resources for name in resources: resources[name] = os.path.abspath(resources[name]) harness_contract_id = "@mozilla.org/harness-service;1?id=%s" % jid harness_options = { "bootstrap": {"contractID": harness_contract_id, "classID": "{%s}" % harness_guid}, "jetpackID": jid, "bundleID": bundle_id, "uriPrefix": uri_prefix, "staticArgs": options.static_args, "name": target, } harness_options.update(build) if command == "test": # This should be contained in the test runner package. harness_options["main"] = "run-tests" else: harness_options["main"] = target_cfg.get("main") for option in inherited_options: harness_options[option] = getattr(options, option) harness_options["metadata"] = packaging.get_metadata(pkg_cfg, used_deps) sdk_version = get_version(env_root) harness_options["sdkVersion"] = sdk_version packaging.call_plugins(pkg_cfg, used_deps) retval = 0 if options.templatedir: app_extension_dir = os.path.abspath(options.templatedir) else: mydir = os.path.dirname(os.path.abspath(__file__)) if sys.platform == "darwin": # If we're on OS X, at least point into the XULRunner # app dir so we run as a proper app if using XULRunner. app_extension_dir = os.path.join(mydir, "Test App.app", "Contents", "Resources") else: app_extension_dir = os.path.join(mydir, "app-extension") harness_options["manifest"] = manifest.get_harness_options_manifest(uri_prefix) if command == "xpi": from cuddlefish.xpi import build_xpi from cuddlefish.rdf import gen_manifest, RDFUpdate manifest_rdf = gen_manifest( template_root_dir=app_extension_dir, target_cfg=target_cfg, bundle_id=bundle_id, update_url=options.update_url, bootstrap=True, ) if options.update_link: rdf_name = UPDATE_RDF_FILENAME % target_cfg.name print >> stdout, "Exporting update description to %s." % rdf_name update = RDFUpdate() update.add(manifest_rdf, options.update_link) open(rdf_name, "w").write(str(update)) # ask the manifest what files were used, so we can construct an XPI # without the rest. This will include the loader (and everything it # uses) because of the "loader_modules" starting points we passed to # build_manifest earlier used_files = set(manifest.get_used_files()) if not options.strip_xpi: used_files = None # disables the filter xpi_name = XPI_FILENAME % target_cfg.name print >> stdout, "Exporting extension to %s." % xpi_name build_xpi( template_root_dir=app_extension_dir, manifest=manifest_rdf, xpi_name=xpi_name, harness_options=harness_options, limit_to=used_files, ) else: if options.use_server: from cuddlefish.server import run_app else: from cuddlefish.runner import run_app if options.profiledir: options.profiledir = os.path.expanduser(options.profiledir) options.profiledir = os.path.abspath(options.profiledir) if options.addons is not None: options.addons = options.addons.split(",") try: retval = run_app( harness_root_dir=app_extension_dir, harness_options=harness_options, app_type=options.app, binary=options.binary, profiledir=options.profiledir, verbose=options.verbose, timeout=timeout, logfile=options.logfile, addons=options.addons, args=options.cmdargs, norun=options.no_run, ) except Exception, e: if str(e).startswith(MOZRUNNER_BIN_NOT_FOUND): print >> sys.stderr, MOZRUNNER_BIN_NOT_FOUND_HELP.strip() retval = -1 else: raise
def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, defaults=None, env_root=os.environ.get('CUDDLEFISH_ROOT')): parser_kwargs = dict(arguments=arguments, global_options=global_options, parser_groups=parser_groups, usage=usage, defaults=defaults) (options, args) = parse_args(**parser_kwargs) config_args = get_config_args(options.config, env_root); # reparse configs with arguments from local.json if config_args: parser_kwargs['arguments'] += config_args (options, args) = parse_args(**parser_kwargs) command = args[0] if command == "init": initializer(env_root, args) return if command == "develop": run_development_mode(env_root, defaults=options.__dict__) return if command == "testpkgs": test_all_packages(env_root, defaults=options.__dict__) return elif command == "testex": test_all_examples(env_root, defaults=options.__dict__) return elif command == "testall": test_all(env_root, defaults=options.__dict__) return elif command == "testcfx": test_cfx(env_root, options.verbose) return elif command == "docs": import subprocess import time import cuddlefish.server print "One moment." popen = subprocess.Popen([sys.executable, cuddlefish.server.__file__, 'daemonic']) # TODO: See if there's actually a way to block on # a particular event occurring, rather than this # relatively arbitrary/generous amount. time.sleep(cuddlefish.server.IDLE_WEBPAGE_TIMEOUT * 2) return elif command == "sdocs": import cuddlefish.server # TODO: Allow user to change this filename via cmd line. filename = 'addon-sdk-docs.tgz' cuddlefish.server.generate_static_docs(env_root, filename, options.baseurl) print "Wrote %s." % filename return target_cfg_json = None if not target_cfg: if not options.pkgdir: options.pkgdir = find_parent_package(os.getcwd()) if not options.pkgdir: print >>sys.stderr, ("cannot find 'package.json' in the" " current directory or any parent.") sys.exit(1) else: options.pkgdir = os.path.abspath(options.pkgdir) if not os.path.exists(os.path.join(options.pkgdir, 'package.json')): print >>sys.stderr, ("cannot find 'package.json' in" " %s." % options.pkgdir) sys.exit(1) target_cfg_json = os.path.join(options.pkgdir, 'package.json') target_cfg = packaging.get_config_in_dir(options.pkgdir) # At this point, we're either building an XPI or running Jetpack code in # a Mozilla application (which includes running tests). use_main = False timeout = None inherited_options = ['verbose', 'enable_e10s'] if command == "xpi": use_main = True elif command == "test": if 'tests' not in target_cfg: target_cfg['tests'] = [] timeout = TEST_RUN_TIMEOUT inherited_options.extend(['iterations', 'filter', 'profileMemory']) elif command == "run": use_main = True else: print >>sys.stderr, "Unknown command: %s" % command print >>sys.stderr, "Try using '--help' for assistance." sys.exit(1) if use_main and 'main' not in target_cfg: # If the user supplies a template dir, then the main # program may be contained in the template. if not options.templatedir: print >>sys.stderr, "package.json does not have a 'main' entry." sys.exit(1) if not pkg_cfg: pkg_cfg = packaging.build_config(env_root, target_cfg, options.packagepath) target = target_cfg.name # the harness_guid is used for an XPCOM class ID. We use the # JetpackID for the add-on ID and the XPCOM contract ID. if "harnessClassID" in target_cfg: # For the sake of non-bootstrapped extensions, we allow to specify the # classID of harness' XPCOM component in package.json. This makes it # possible to register the component using a static chrome.manifest file harness_guid = target_cfg["harnessClassID"] else: import uuid harness_guid = str(uuid.uuid4()) # TODO: Consider keeping a cache of dynamic UUIDs, based # on absolute filesystem pathname, in the root directory # or something. if command in ('xpi', 'run'): from cuddlefish.preflight import preflight_config if target_cfg_json: config_was_ok, modified = preflight_config(target_cfg, target_cfg_json) if not config_was_ok: if modified: # we need to re-read package.json . The safest approach # is to re-run the "cfx xpi"/"cfx run" command. print >>sys.stderr, ("package.json modified: please re-run" " 'cfx %s'" % command) else: print >>sys.stderr, ("package.json needs modification:" " please update it and then re-run" " 'cfx %s'" % command) sys.exit(1) # if we make it this far, we have a JID else: assert command == "test" if "id" in target_cfg: jid = target_cfg["id"] assert not jid.endswith("@jetpack") unique_prefix = '%s-' % jid # used for resource: URLs else: # The Jetpack ID is not required for cfx test, in which case we have to # make one up based on the GUID. if options.use_server: # The harness' contractID (hence also the jid and the harness_guid) # need to be static in the "development mode", so that bootstrap.js # can unload the previous version of the package being developed. harness_guid = '2974c5b5-b671-46f8-a4bb-63c6eca6261b' unique_prefix = '%s-' % target jid = harness_guid assert not jid.endswith("@jetpack") if ( jid.startswith("jid0-") or jid.startswith("jid1-") or jid.startswith("anonid0-") ): bundle_id = jid + "@jetpack" # Don't append "@jetpack" to old-style IDs, as they should be exactly # as specified by the addon author so AMO and Firefox continue to treat # their addon bundles as representing the same addon (and also because # they may already have an @ sign in them, and there can be only one). else: bundle_id = jid # the resource: URL's prefix is treated too much like a DNS hostname unique_prefix = unique_prefix.lower() unique_prefix = unique_prefix.replace("@", "-at-") unique_prefix = unique_prefix.replace(".", "-dot-") targets = [target] if command == "test": targets.append(options.test_runner_pkg) if options.extra_packages: targets.extend(options.extra_packages.split(",")) deps = packaging.get_deps_for_targets(pkg_cfg, targets) build = packaging.generate_build_for_target( pkg_cfg, target, deps, prefix=unique_prefix, # used to create resource: URLs include_dep_tests=options.dep_tests ) if 'resources' in build: resources = build.resources for name in resources: resources[name] = os.path.abspath(resources[name]) harness_contract_id = ('@mozilla.org/harness-service;1?id=%s' % jid) harness_options = { 'bootstrap': { 'contractID': harness_contract_id, 'classID': '{%s}' % harness_guid }, 'jetpackID': jid, 'bundleID': bundle_id, 'staticArgs': options.static_args, 'name': target, } harness_options.update(build) if command == "test": # This should be contained in the test runner package. harness_options['main'] = 'run-tests' else: harness_options['main'] = target_cfg.get('main') for option in inherited_options: harness_options[option] = getattr(options, option) harness_options['metadata'] = packaging.get_metadata(pkg_cfg, deps) sdk_version = get_version(env_root) harness_options['sdkVersion'] = sdk_version packaging.call_plugins(pkg_cfg, deps) retval = 0 if options.templatedir: app_extension_dir = os.path.abspath(options.templatedir) else: mydir = os.path.dirname(os.path.abspath(__file__)) if sys.platform == "darwin": # If we're on OS X, at least point into the XULRunner # app dir so we run as a proper app if using XULRunner. app_extension_dir = os.path.join(mydir, "Test App.app", "Contents", "Resources") else: app_extension_dir = os.path.join(mydir, "app-extension") from cuddlefish.manifest import build_manifest uri_prefix = "resource://%s" % unique_prefix include_tests = False #bool(command=="test") manifest = build_manifest(target_cfg, pkg_cfg, deps, uri_prefix, include_tests) harness_options['manifest'] = manifest.get_harness_options_manifest(uri_prefix) if command == 'xpi': from cuddlefish.xpi import build_xpi from cuddlefish.rdf import gen_manifest, RDFUpdate manifest_rdf = gen_manifest(template_root_dir=app_extension_dir, target_cfg=target_cfg, bundle_id=bundle_id, update_url=options.update_url, bootstrap=True) if options.update_link: rdf_name = UPDATE_RDF_FILENAME % target_cfg.name print "Exporting update description to %s." % rdf_name update = RDFUpdate() update.add(manifest_rdf, options.update_link) open(rdf_name, "w").write(str(update)) xpi_name = XPI_FILENAME % target_cfg.name print "Exporting extension to %s." % xpi_name build_xpi(template_root_dir=app_extension_dir, manifest=manifest_rdf, xpi_name=xpi_name, harness_options=harness_options) else: if options.use_server: from cuddlefish.server import run_app else: from cuddlefish.runner import run_app if options.profiledir: options.profiledir = os.path.expanduser(options.profiledir) options.profiledir = os.path.abspath(options.profiledir) if options.addons is not None: options.addons = options.addons.split(",") try: retval = run_app(harness_root_dir=app_extension_dir, harness_options=harness_options, app_type=options.app, binary=options.binary, profiledir=options.profiledir, verbose=options.verbose, timeout=timeout, logfile=options.logfile, addons=options.addons, args=options.cmdargs, norun=options.no_run) except Exception, e: if str(e).startswith(MOZRUNNER_BIN_NOT_FOUND): print >>sys.stderr, MOZRUNNER_BIN_NOT_FOUND_HELP.strip() retval = -1 else: raise
def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, defaults=None, env_root=os.environ.get('CUDDLEFISH_ROOT')): parser_kwargs = dict(arguments=arguments, parser_options=parser_options, parser_groups=parser_groups, usage=usage, defaults=defaults) (options, args) = parse_args(**parser_kwargs) config_args = get_config_args(options.config, env_root) # reparse configs with arguments from local.json if config_args: parser_kwargs['arguments'] += config_args (options, args) = parse_args(**parser_kwargs) command = args[0] if command == "develop": run_development_mode(env_root, defaults=options.__dict__) return if command == "testpkgs": test_all_packages(env_root, defaults=options.__dict__) return elif command == "testex": test_all_examples(env_root, defaults=options.__dict__) return elif command == "testall": test_all(env_root, defaults=options.__dict__) return elif command == "testcfx": test_cfx(env_root, options.verbose) return elif command == "docs": import subprocess import time import cuddlefish.server print "One moment." popen = subprocess.Popen( [sys.executable, cuddlefish.server.__file__, 'daemonic']) # TODO: See if there's actually a way to block on # a particular event occurring, rather than this # relatively arbitrary/generous amount. time.sleep(cuddlefish.server.IDLE_WEBPAGE_TIMEOUT * 2) return elif command == "sdocs": import cuddlefish.server # TODO: Allow user to change this filename via cmd line. filename = 'jetpack-sdk-docs.tgz' cuddlefish.server.generate_static_docs(env_root, filename) print "Wrote %s." % filename return target_cfg_json = None if not target_cfg: if not options.pkgdir: options.pkgdir = find_parent_package(os.getcwd()) if not options.pkgdir: print >> sys.stderr, ("cannot find 'package.json' in the" " current directory or any parent.") sys.exit(1) else: options.pkgdir = os.path.abspath(options.pkgdir) if not os.path.exists(os.path.join(options.pkgdir, 'package.json')): print >> sys.stderr, ("cannot find 'package.json' in" " %s." % options.pkgdir) sys.exit(1) target_cfg_json = os.path.join(options.pkgdir, 'package.json') target_cfg = packaging.get_config_in_dir(options.pkgdir) use_main = False if command == "xpcom": if 'xpcom' not in target_cfg: print >> sys.stderr, "package.json does not have a 'xpcom' entry." sys.exit(1) if not (options.moz_srcdir and options.moz_objdir): print >> sys.stderr, "srcdir and objdir not specified." sys.exit(1) options.moz_srcdir = os.path.expanduser(options.moz_srcdir) options.moz_objdir = os.path.expanduser(options.moz_objdir) xpcom = target_cfg.xpcom from cuddlefish.xpcom import build_xpcom_components if 'typelibs' in xpcom: xpt_output_dir = packaging.resolve_dir(target_cfg, xpcom.typelibs) else: xpt_output_dir = None build_xpcom_components( comp_src_dir=packaging.resolve_dir(target_cfg, xpcom.src), moz_srcdir=options.moz_srcdir, moz_objdir=options.moz_objdir, base_output_dir=packaging.resolve_dir(target_cfg, xpcom.dest), xpt_output_dir=xpt_output_dir, module_name=xpcom.module) sys.exit(0) elif command == "xpi": use_main = True elif command == "test": if 'tests' not in target_cfg: target_cfg['tests'] = [] elif command == "run": use_main = True else: print >> sys.stderr, "Unknown command: %s" % command print >> sys.stderr, "Try using '--help' for assistance." sys.exit(1) if use_main and 'main' not in target_cfg: # If the user supplies a template dir, then the main # program may be contained in the template. if not options.templatedir: print >> sys.stderr, "package.json does not have a 'main' entry." sys.exit(1) if not pkg_cfg: pkg_cfg = packaging.build_config(env_root, target_cfg) target = target_cfg.name # TODO: Consider keeping a cache of dynamic UUIDs, based # on absolute filesystem pathname, in the root directory # or something. if command in ('xpi', 'run'): from cuddlefish.preflight import preflight_config if target_cfg_json: config_was_ok, modified = preflight_config( target_cfg, target_cfg_json, keydir=options.keydir, err_if_privkey_not_found=False) if not config_was_ok: if modified: # we need to re-read package.json . The safest approach # is to re-run the "cfx xpi"/"cfx run" command. print >> sys.stderr, ( "package.json modified: please re-run" " 'cfx %s'" % command) else: print >> sys.stderr, ("package.json needs modification:" " please update it and then re-run" " 'cfx %s'" % command) sys.exit(1) # if we make it this far, we have a JID jid = target_cfg["id"] assert not jid.endswith("@jetpack") unique_prefix = '%s-' % jid # used for resource: URLs # the harness_guid is used for an XPCOM class ID. We use the # JetpackID for the add-on ID and the XPCOM contract ID. import uuid harness_guid = str(uuid.uuid4()) else: if options.use_server: harness_guid = '2974c5b5-b671-46f8-a4bb-63c6eca6261b' else: harness_guid = '6724fc1b-3ec4-40e2-8583-8061088b3185' unique_prefix = '%s-' % target jid = harness_guid assert not jid.endswith("@jetpack") bundle_id = jid + "@jetpack" # the resource: URLs prefix is treated too much like a DNS hostname unique_prefix = unique_prefix.lower() assert "@" not in unique_prefix assert "." not in unique_prefix timeout = None targets = [target] if not use_main: timeout = TEST_RUN_TIMEOUT targets.append(options.test_runner_pkg) if options.extra_packages: targets.extend(options.extra_packages.split(",")) deps = packaging.get_deps_for_targets(pkg_cfg, targets) build = packaging.generate_build_for_target( pkg_cfg, target, deps, prefix=unique_prefix, # used to create resource: URLs include_dep_tests=options.dep_tests) if 'resources' in build: resources = build.resources for name in resources: resources[name] = os.path.abspath(resources[name]) dep_xpt_dirs = [] for dep in deps: dep_cfg = pkg_cfg.packages[dep] if 'xpcom' in dep_cfg and 'typelibs' in dep_cfg.xpcom: abspath = packaging.resolve_dir(dep_cfg, dep_cfg.xpcom.typelibs) dep_xpt_dirs.append(abspath) xpts = get_xpts(dep_xpt_dirs) harness_contract_id = ('@mozilla.org/harness-service;1?id=%s' % jid) harness_options = { 'bootstrap': { 'contractID': harness_contract_id, 'classID': '{%s}' % harness_guid }, 'jetpackID': jid, 'bundleID': bundle_id, } harness_options.update(build) inherited_options = ['verbose'] if use_main: harness_options['main'] = target_cfg.get('main') else: harness_options['main'] = "run-tests" inherited_options.extend(['iterations', 'filter', 'profileMemory']) for option in inherited_options: harness_options[option] = getattr(options, option) harness_options['metadata'] = packaging.get_metadata(pkg_cfg, deps) packaging.call_plugins(pkg_cfg, deps) retval = 0 if options.templatedir: app_extension_dir = os.path.abspath(options.templatedir) else: mydir = os.path.dirname(os.path.abspath(__file__)) if sys.platform == "darwin": # If we're on OS X, at least point into the XULRunner # app dir so we run as a proper app if using XULRunner. app_extension_dir = os.path.join(mydir, "Test App.app", "Contents", "Resources") else: app_extension_dir = os.path.join(mydir, "app-extension") if command == 'xpi': from cuddlefish.xpi import build_xpi from cuddlefish.rdf import gen_manifest, RDFUpdate manifest = gen_manifest(template_root_dir=app_extension_dir, target_cfg=target_cfg, bundle_id=bundle_id, update_url=options.update_url, bootstrap=True) if options.update_link: rdf_name = UPDATE_RDF_FILENAME % target_cfg.name print "Exporting update description to %s." % rdf_name update = RDFUpdate() update.add(manifest, options.update_link) open(rdf_name, "w").write(str(update)) xpi_name = XPI_FILENAME % target_cfg.name print "Exporting extension to %s." % xpi_name build_xpi(template_root_dir=app_extension_dir, manifest=manifest, xpi_name=xpi_name, harness_options=harness_options, xpts=xpts) else: if options.use_server: from cuddlefish.server import run_app else: from cuddlefish.runner import run_app if options.profiledir: options.profiledir = os.path.expanduser(options.profiledir) options.profiledir = os.path.abspath(options.profiledir) if options.addons is not None: options.addons = options.addons.split(",") try: retval = run_app(harness_root_dir=app_extension_dir, harness_options=harness_options, xpts=xpts, app_type=options.app, binary=options.binary, profiledir=options.profiledir, verbose=options.verbose, timeout=timeout, logfile=options.logfile, addons=options.addons) except Exception, e: if e.message.startswith(MOZRUNNER_BIN_NOT_FOUND): print >> sys.stderr, MOZRUNNER_BIN_NOT_FOUND_HELP.strip() retval = -1 else: raise
def test_contents(self): target_cfg = self.get_pkg("three") package_path = [self.get_linker_files_dir("three-deps")] pkg_cfg = packaging.build_config(self.root, target_cfg, packagepath=package_path) deps = packaging.get_deps_for_targets(pkg_cfg, [target_cfg.name, "addon-sdk"]) addon_sdk_dir = pkg_cfg.packages["addon-sdk"].lib[0] m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) used_files = list(m.get_used_files()) here = up(os.path.abspath(__file__)) def absify(*parts): fn = os.path.join(here, "linker-files", *parts) return os.path.abspath(fn) expected = [absify(*parts) for parts in [("three", "lib", "main.js"), ("three-deps", "three-a", "lib", "main.js"), ("three-deps", "three-a", "lib", "subdir", "subfile.js"), ("three", "data", "msg.txt"), ("three", "data", "subdir", "submsg.txt"), ("three-deps", "three-b", "lib", "main.js"), ("three-deps", "three-c", "lib", "main.js"), ("three-deps", "three-c", "lib", "sub", "foo.js") ]] add_addon_sdk= lambda path: os.path.join(addon_sdk_dir, path) expected.extend([add_addon_sdk(module) for module in [ os.path.join("sdk", "self.js"), os.path.join("sdk", "core", "promise.js"), os.path.join("sdk", "net", "url.js"), os.path.join("sdk", "util", "object.js") ]]) missing = set(expected) - set(used_files) extra = set(used_files) - set(expected) self.failUnlessEqual(list(missing), []) self.failUnlessEqual(list(extra), []) used_deps = m.get_used_packages() build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name, used_deps, include_tests=False) options = {'main': target_cfg.main} options.update(build) basedir = self.make_basedir() xpi_name = os.path.join(basedir, "contents.xpi") xpi.build_xpi(template_root_dir=xpi_template_path, manifest=fake_manifest, xpi_path=xpi_name, harness_options=options, limit_to=used_files) x = zipfile.ZipFile(xpi_name, "r") names = x.namelist() expected = ["components/", "components/harness.js", # the real template also has 'bootstrap.js', but the fake # one in tests/static-files/xpi-template doesn't "harness-options.json", "install.rdf", "defaults/preferences/prefs.js", "resources/", "resources/addon-sdk/", "resources/addon-sdk/data/", "resources/addon-sdk/lib/", "resources/addon-sdk/lib/sdk/", "resources/addon-sdk/lib/sdk/self.js", "resources/addon-sdk/lib/sdk/core/", "resources/addon-sdk/lib/sdk/util/", "resources/addon-sdk/lib/sdk/net/", "resources/addon-sdk/lib/sdk/core/promise.js", "resources/addon-sdk/lib/sdk/util/object.js", "resources/addon-sdk/lib/sdk/net/url.js", "resources/three/", "resources/three/lib/", "resources/three/lib/main.js", "resources/three/data/", "resources/three/data/msg.txt", "resources/three/data/subdir/", "resources/three/data/subdir/submsg.txt", "resources/three-a/", "resources/three-a/lib/", "resources/three-a/lib/main.js", "resources/three-a/lib/subdir/", "resources/three-a/lib/subdir/subfile.js", "resources/three-b/", "resources/three-b/lib/", "resources/three-b/lib/main.js", "resources/three-c/", "resources/three-c/lib/", "resources/three-c/lib/main.js", "resources/three-c/lib/sub/", "resources/three-c/lib/sub/foo.js", # notably absent: three-a/lib/unused.js "locale/", "locale/fr-FR.json", "locales.json", ] # showing deltas makes failures easier to investigate missing = set(expected) - set(names) extra = set(names) - set(expected) self.failUnlessEqual((list(missing), list(extra)), ([], [])) self.failUnlessEqual(sorted(names), sorted(expected)) # check locale files localedata = json.loads(x.read("locales.json")) self.failUnlessEqual(sorted(localedata["locales"]), sorted(["fr-FR"])) content = x.read("locale/fr-FR.json") locales = json.loads(content) # Locale files are merged into one. # Conflicts are silently resolved by taking last package translation, # so that we get "No" translation from three-c instead of three-b one. self.failUnlessEqual(locales, json.loads(u''' { "No": "Nein", "one": "un", "What?": "Quoi?", "Yes": "Oui", "plural": { "other": "other", "one": "one" }, "uft8_value": "\u00e9" }'''))
# Generate extra options extra_harness_options = {} for kv in options.extra_harness_option_args: key,value = kv.split("=", 1) extra_harness_options[key] = value # Generate xpi filepath if options.output_file: xpi_path = options.output_file else: xpi_path = XPI_FILENAME % target_cfg.name print >>stdout, "Exporting extension to %s." % xpi_path build_xpi(template_root_dir=app_extension_dir, manifest=manifest_rdf, xpi_path=xpi_path, harness_options=harness_options, limit_to=used_files, extra_harness_options=extra_harness_options, bundle_sdk=True, pkgdir=options.pkgdir) else: from cuddlefish.runner import run_app if options.profiledir: options.profiledir = os.path.expanduser(options.profiledir) options.profiledir = os.path.abspath(options.profiledir) if options.addons is not None: options.addons = options.addons.split(",") enable_e10s = options.enable_e10s or target_cfg.get('e10s', False)
# ask the manifest what files were used, so we can construct an XPI # without the rest. This will include the loader (and everything it # uses) because of the "loader_modules" starting points we passed to # build_manifest earlier used_files = set(manifest.get_used_files()) if options.strip_xpi: print >>stdout, "--strip-xpi is now the default: argument ignored" if options.no_strip_xpi: used_files = None # disables the filter, includes all files xpi_name = XPI_FILENAME % target_cfg.name print >>stdout, "Exporting extension to %s." % xpi_name build_xpi(template_root_dir=app_extension_dir, manifest=manifest_rdf, xpi_name=xpi_name, harness_options=harness_options, limit_to=used_files) else: from cuddlefish.runner import run_app if options.profiledir: options.profiledir = os.path.expanduser(options.profiledir) options.profiledir = os.path.abspath(options.profiledir) if options.addons is not None: options.addons = options.addons.split(",") try: retval = run_app(harness_root_dir=app_extension_dir, harness_options=harness_options,
used_files = set(manifest.get_used_files()) if options.no_strip_xpi: used_files = None # disables the filter, includes all files if command == 'xpi': from cuddlefish.xpi import build_xpi extra_harness_options = {} for kv in options.extra_harness_option_args: key, value = kv.split("=", 1) extra_harness_options[key] = value xpi_path = XPI_FILENAME % target_cfg.name print >> stdout, "Exporting extension to %s." % xpi_path build_xpi(template_root_dir=app_extension_dir, manifest=manifest_rdf, xpi_path=xpi_path, harness_options=harness_options, limit_to=used_files, extra_harness_options=extra_harness_options) else: from cuddlefish.runner import run_app if options.profiledir: options.profiledir = os.path.expanduser(options.profiledir) options.profiledir = os.path.abspath(options.profiledir) if options.addons is not None: options.addons = options.addons.split(",") try: retval = run_app(harness_root_dir=app_extension_dir, manifest_rdf=manifest_rdf,
def run( arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, defaults=None, env_root=os.environ.get("CUDDLEFISH_ROOT") ): parser_kwargs = dict( arguments=arguments, parser_options=parser_options, parser_groups=parser_groups, usage=usage, defaults=defaults ) (options, args) = parse_args(**parser_kwargs) config_args = get_config_args(options.config, env_root) # reparse configs with arguments from local.json if config_args: parser_kwargs["arguments"] += config_args (options, args) = parse_args(**parser_kwargs) command = args[0] if command == "develop": run_development_mode(env_root, defaults=options.__dict__) return if command == "testpkgs": test_all_packages(env_root, defaults=options.__dict__) return elif command == "testex": test_all_examples(env_root, defaults=options.__dict__) return elif command == "testall": test_all(env_root, defaults=options.__dict__) return elif command == "testcfx": test_cfx(env_root, options.verbose) return elif command == "docs": import subprocess import time import cuddlefish.server print "One moment." popen = subprocess.Popen([sys.executable, cuddlefish.server.__file__, "daemonic"]) # TODO: See if there's actually a way to block on # a particular event occurring, rather than this # relatively arbitrary/generous amount. time.sleep(cuddlefish.server.IDLE_WEBPAGE_TIMEOUT * 2) return elif command == "sdocs": import cuddlefish.server # TODO: Allow user to change this filename via cmd line. filename = "jetpack-sdk-docs.tgz" cuddlefish.server.generate_static_docs(env_root, filename) print "Wrote %s." % filename return target_cfg_json = None if not target_cfg: if not options.pkgdir: options.pkgdir = find_parent_package(os.getcwd()) if not options.pkgdir: print >> sys.stderr, ("cannot find 'package.json' in the" " current directory or any parent.") sys.exit(1) else: options.pkgdir = os.path.abspath(options.pkgdir) if not os.path.exists(os.path.join(options.pkgdir, "package.json")): print >> sys.stderr, ("cannot find 'package.json' in" " %s." % options.pkgdir) sys.exit(1) target_cfg_json = os.path.join(options.pkgdir, "package.json") target_cfg = packaging.get_config_in_dir(options.pkgdir) use_main = False if command == "xpcom": if "xpcom" not in target_cfg: print >> sys.stderr, "package.json does not have a 'xpcom' entry." sys.exit(1) if not (options.moz_srcdir and options.moz_objdir): print >> sys.stderr, "srcdir and objdir not specified." sys.exit(1) options.moz_srcdir = os.path.expanduser(options.moz_srcdir) options.moz_objdir = os.path.expanduser(options.moz_objdir) xpcom = target_cfg.xpcom from cuddlefish.xpcom import build_xpcom_components if "typelibs" in xpcom: xpt_output_dir = packaging.resolve_dir(target_cfg, xpcom.typelibs) else: xpt_output_dir = None build_xpcom_components( comp_src_dir=packaging.resolve_dir(target_cfg, xpcom.src), moz_srcdir=options.moz_srcdir, moz_objdir=options.moz_objdir, base_output_dir=packaging.resolve_dir(target_cfg, xpcom.dest), xpt_output_dir=xpt_output_dir, module_name=xpcom.module, ) sys.exit(0) elif command == "xpi": use_main = True elif command == "test": if "tests" not in target_cfg: target_cfg["tests"] = [] elif command == "run": use_main = True else: print >> sys.stderr, "Unknown command: %s" % command print >> sys.stderr, "Try using '--help' for assistance." sys.exit(1) if use_main and "main" not in target_cfg: # If the user supplies a template dir, then the main # program may be contained in the template. if not options.templatedir: print >> sys.stderr, "package.json does not have a 'main' entry." sys.exit(1) if not pkg_cfg: pkg_cfg = packaging.build_config(env_root, target_cfg) target = target_cfg.name # TODO: Consider keeping a cache of dynamic UUIDs, based # on absolute filesystem pathname, in the root directory # or something. if command in ("xpi", "run"): from cuddlefish.preflight import preflight_config if target_cfg_json: config_was_ok, modified = preflight_config( target_cfg, target_cfg_json, keydir=options.keydir, err_if_privkey_not_found=False ) if not config_was_ok: if modified: # we need to re-read package.json . The safest approach # is to re-run the "cfx xpi"/"cfx run" command. print >> sys.stderr, ("package.json modified: please re-run" " 'cfx %s'" % command) else: print >> sys.stderr, ( "package.json needs modification:" " please update it and then re-run" " 'cfx %s'" % command ) sys.exit(1) # if we make it this far, we have a JID jid = target_cfg["id"] assert not jid.endswith("@jetpack") unique_prefix = "%s-" % jid # used for resource: URLs # the harness_guid is used for an XPCOM class ID. We use the # JetpackID for the add-on ID and the XPCOM contract ID. import uuid harness_guid = str(uuid.uuid4()) else: if options.use_server: harness_guid = "2974c5b5-b671-46f8-a4bb-63c6eca6261b" else: harness_guid = "6724fc1b-3ec4-40e2-8583-8061088b3185" unique_prefix = "%s-" % target jid = harness_guid assert not jid.endswith("@jetpack") bundle_id = jid + "@jetpack" # the resource: URLs prefix is treated too much like a DNS hostname unique_prefix = unique_prefix.lower() assert "@" not in unique_prefix assert "." not in unique_prefix timeout = None targets = [target] if not use_main: timeout = TEST_RUN_TIMEOUT targets.append(options.test_runner_pkg) if options.extra_packages: targets.extend(options.extra_packages.split(",")) deps = packaging.get_deps_for_targets(pkg_cfg, targets) build = packaging.generate_build_for_target( pkg_cfg, target, deps, prefix=unique_prefix, # used to create resource: URLs include_dep_tests=options.dep_tests, ) if "resources" in build: resources = build.resources for name in resources: resources[name] = os.path.abspath(resources[name]) dep_xpt_dirs = [] for dep in deps: dep_cfg = pkg_cfg.packages[dep] if "xpcom" in dep_cfg and "typelibs" in dep_cfg.xpcom: abspath = packaging.resolve_dir(dep_cfg, dep_cfg.xpcom.typelibs) dep_xpt_dirs.append(abspath) xpts = get_xpts(dep_xpt_dirs) harness_contract_id = "@mozilla.org/harness-service;1?id=%s" % jid harness_options = { "bootstrap": {"contractID": harness_contract_id, "classID": "{%s}" % harness_guid}, "jetpackID": jid, "bundleID": bundle_id, } harness_options.update(build) inherited_options = ["verbose"] if use_main: harness_options["main"] = target_cfg.get("main") else: harness_options["main"] = "run-tests" inherited_options.extend(["iterations", "filter", "profileMemory"]) for option in inherited_options: harness_options[option] = getattr(options, option) harness_options["metadata"] = packaging.get_metadata(pkg_cfg, deps) packaging.call_plugins(pkg_cfg, deps) retval = 0 if options.templatedir: app_extension_dir = os.path.abspath(options.templatedir) else: mydir = os.path.dirname(os.path.abspath(__file__)) if sys.platform == "darwin": # If we're on OS X, at least point into the XULRunner # app dir so we run as a proper app if using XULRunner. app_extension_dir = os.path.join(mydir, "Test App.app", "Contents", "Resources") else: app_extension_dir = os.path.join(mydir, "app-extension") if command == "xpi": from cuddlefish.xpi import build_xpi from cuddlefish.rdf import gen_manifest, RDFUpdate manifest = gen_manifest( template_root_dir=app_extension_dir, target_cfg=target_cfg, bundle_id=bundle_id, update_url=options.update_url, bootstrap=True, ) if options.update_link: rdf_name = UPDATE_RDF_FILENAME % target_cfg.name print "Exporting update description to %s." % rdf_name update = RDFUpdate() update.add(manifest, options.update_link) open(rdf_name, "w").write(str(update)) xpi_name = XPI_FILENAME % target_cfg.name print "Exporting extension to %s." % xpi_name build_xpi( template_root_dir=app_extension_dir, manifest=manifest, xpi_name=xpi_name, harness_options=harness_options, xpts=xpts, ) else: if options.use_server: from cuddlefish.server import run_app else: from cuddlefish.runner import run_app if options.profiledir: options.profiledir = os.path.expanduser(options.profiledir) options.profiledir = os.path.abspath(options.profiledir) if options.addons is not None: options.addons = options.addons.split(",") try: retval = run_app( harness_root_dir=app_extension_dir, harness_options=harness_options, xpts=xpts, app_type=options.app, binary=options.binary, profiledir=options.profiledir, verbose=options.verbose, timeout=timeout, logfile=options.logfile, addons=options.addons, ) except Exception, e: if e.message.startswith(MOZRUNNER_BIN_NOT_FOUND): print >> sys.stderr, MOZRUNNER_BIN_NOT_FOUND_HELP.strip() retval = -1 else: raise
def run( arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, defaults=None, env_root=os.environ.get("CUDDLEFISH_ROOT") ): parser_kwargs = dict( arguments=arguments, parser_options=parser_options, parser_groups=parser_groups, usage=usage, defaults=defaults ) (options, args) = parse_args(**parser_kwargs) if options.config: parser_kwargs["arguments"] += get_config_args(options.config, env_root) (options, args) = parse_args(**parser_kwargs) command = args[0] if command == "testall": test_all_packages(env_root, defaults=options.__dict__) return elif command == "docs": import subprocess import time import cuddlefish.server print "One moment." popen = subprocess.Popen([sys.executable, cuddlefish.server.__file__, "daemonic"]) # TODO: See if there's actually a way to block on # a particular event occurring, rather than this # relatively arbitrary/generous amount. time.sleep(cuddlefish.server.IDLE_WEBPAGE_TIMEOUT * 2) return if not target_cfg: if not options.pkgdir: options.pkgdir = find_parent_package(os.getcwd()) if not options.pkgdir: print ("cannot find 'package.json' in the current " "directory or any parent.") sys.exit(1) else: options.pkgdir = os.path.abspath(options.pkgdir) if not os.path.exists(os.path.join(options.pkgdir, "package.json")): print "cannot find 'package.json' in %s." % options.pkgdir sys.exit(1) target_cfg = packaging.get_config_in_dir(options.pkgdir) use_main = False if command == "xpcom": if "xpcom" not in target_cfg: print "package.json does not have a 'xpcom' entry." sys.exit(1) if not (options.moz_srcdir and options.moz_objdir): print "srcdir and objdir not specified." sys.exit(1) options.moz_srcdir = os.path.expanduser(options.moz_srcdir) options.moz_objdir = os.path.expanduser(options.moz_objdir) xpcom = target_cfg.xpcom from cuddlefish.xpcom import build_xpcom_components if "typelibs" in xpcom: xpt_output_dir = packaging.resolve_dir(target_cfg, xpcom.typelibs) else: xpt_output_dir = None build_xpcom_components( comp_src_dir=packaging.resolve_dir(target_cfg, xpcom.src), moz_srcdir=options.moz_srcdir, moz_objdir=options.moz_objdir, base_output_dir=packaging.resolve_dir(target_cfg, xpcom.dest), xpt_output_dir=xpt_output_dir, module_name=xpcom.module, ) sys.exit(0) elif command == "xpi": use_main = True elif command == "test": if "tests" not in target_cfg: target_cfg["tests"] = [] elif command == "run": use_main = True else: print "Unknown command: %s" % command print "Try using '--help' for assistance." sys.exit(1) if use_main and "main" not in target_cfg: # If the user supplies a template dir, then the main # program may be contained in the template. if not options.templatedir: print "package.json does not have a 'main' entry." sys.exit(1) if not pkg_cfg: pkg_cfg = packaging.build_config(env_root, target_cfg) target = target_cfg.name # TODO: Consider keeping a cache of dynamic UUIDs, based # on absolute filesystem pathname, in the root directory # or something. if command == "xpi": import uuid harness_guid = str(uuid.uuid4()) unique_prefix = "%s-" % harness_guid else: if options.use_server: harness_guid = "2974c5b5-b671-46f8-a4bb-63c6eca6261b" else: harness_guid = "6724fc1b-3ec4-40e2-8583-8061088b3185" unique_prefix = "%s-" % target identifier = target_cfg.get("id", "{%s}" % harness_guid) timeout = None targets = [target] if not use_main: timeout = TEST_RUN_TIMEOUT targets.append("test-harness") if options.extra_packages: targets.extend(options.extra_packages.split(",")) deps = packaging.get_deps_for_targets(pkg_cfg, targets) build = packaging.generate_build_for_target( pkg_cfg, target, deps, prefix=unique_prefix, include_dep_tests=options.dep_tests ) if "resources" in build: resources = build.resources for name in resources: resources[name] = os.path.abspath(resources[name]) dep_xpt_dirs = [] for dep in deps: dep_cfg = pkg_cfg.packages[dep] if "xpcom" in dep_cfg and "typelibs" in dep_cfg.xpcom: abspath = packaging.resolve_dir(dep_cfg, dep_cfg.xpcom.typelibs) dep_xpt_dirs.append(abspath) xpts = get_xpts(dep_xpt_dirs) harness_contract_id = "@mozilla.org/harness-service;1?id=%s" % identifier harness_options = {"bootstrap": {"contractID": harness_contract_id, "classID": "{%s}" % harness_guid}} harness_options.update(build) inherited_options = ["verbose"] if use_main: harness_options["main"] = target_cfg.get("main") else: harness_options["main"] = "run-tests" inherited_options.extend(["iterations"]) for option in inherited_options: harness_options[option] = getattr(options, option) harness_options["metadata"] = packaging.get_metadata(pkg_cfg, deps) packaging.call_plugins(pkg_cfg, deps) retval = 0 if options.templatedir: app_extension_dir = os.path.abspath(options.templatedir) else: app_extension_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "app-extension") if command == "xpi": from cuddlefish.xpi import build_xpi from cuddlefish.rdf import gen_manifest, RDFUpdate manifest = gen_manifest( template_root_dir=app_extension_dir, target_cfg=target_cfg, default_id=identifier, update_url=options.update_url, ) if options.update_link: rdf_name = UPDATE_RDF_FILENAME % target_cfg.name print "Exporting update description to %s." % rdf_name update = RDFUpdate() update.add(manifest, options.update_link) open(rdf_name, "w").write(str(update)) xpi_name = XPI_FILENAME % target_cfg.name print "Exporting extension to %s." % xpi_name build_xpi( template_root_dir=app_extension_dir, manifest=manifest, xpi_name=xpi_name, harness_options=harness_options, xpts=xpts, ) else: if options.use_server: from cuddlefish.server import run_app else: from cuddlefish.runner import run_app retval = run_app( harness_root_dir=app_extension_dir, harness_options=harness_options, xpts=xpts, app_type=options.app, binary=options.binary, verbose=options.verbose, no_quit=options.no_quit, timeout=timeout, ) sys.exit(retval)
def run_app(harness_root_dir, manifest_rdf, harness_options, app_type, binary=None, profiledir=None, verbose=False, enforce_timeouts=False, logfile=None, addons=None, args=None, extra_environment={}, norun=None, used_files=None, enable_mobile=False, mobile_app_name=None): if binary: binary = os.path.expanduser(binary) if addons is None: addons = [] else: addons = list(addons) cmdargs = [] preferences = dict(DEFAULT_COMMON_PREFS) # For now, only allow running on Mobile with --force-mobile argument if app_type in ["fennec", "fennec-on-device"] and not enable_mobile: print """ WARNING: Firefox Mobile support is still experimental. If you would like to run an addon on this platform, use --force-mobile flag: cfx --force-mobile""" return 0 if app_type == "fennec-on-device": profile_class = FennecProfile preferences.update(DEFAULT_FENNEC_PREFS) runner_class = RemoteFennecRunner # We pass the intent name through command arguments cmdargs.append(mobile_app_name) elif enable_mobile or app_type == "fennec": profile_class = FennecProfile preferences.update(DEFAULT_FENNEC_PREFS) runner_class = FennecRunner elif app_type == "xulrunner": profile_class = XulrunnerAppProfile runner_class = XulrunnerAppRunner cmdargs.append(os.path.join(harness_root_dir, 'application.ini')) elif app_type == "firefox": profile_class = mozrunner.FirefoxProfile preferences.update(DEFAULT_FIREFOX_PREFS) runner_class = mozrunner.FirefoxRunner elif app_type == "thunderbird": profile_class = mozrunner.ThunderbirdProfile preferences.update(DEFAULT_THUNDERBIRD_PREFS) runner_class = mozrunner.ThunderbirdRunner else: raise ValueError("Unknown app: %s" % app_type) if sys.platform == 'darwin' and app_type != 'xulrunner': cmdargs.append('-foreground') if args: cmdargs.extend(shlex.split(args)) # TODO: handle logs on remote device if app_type != "fennec-on-device": # tempfile.gettempdir() was constant, preventing two simultaneous "cfx # run"/"cfx test" on the same host. On unix it points at /tmp (which is # world-writeable), enabling a symlink attack (e.g. imagine some bad guy # does 'ln -s ~/.ssh/id_rsa /tmp/harness_result'). NamedTemporaryFile # gives us a unique filename that fixes both problems. We leave the # (0-byte) file in place until the browser-side code starts writing to # it, otherwise the symlink attack becomes possible again. fileno, resultfile = tempfile.mkstemp(prefix="harness-result-") os.close(fileno) harness_options['resultFile'] = resultfile def maybe_remove_logfile(): if os.path.exists(logfile): os.remove(logfile) logfile_tail = None # We always buffer output through a logfile for two reasons: # 1. On Windows, it's the only way to print console output to stdout/err. # 2. It enables us to keep track of the last time output was emitted, # so we can raise an exception if the test runner hangs. if not logfile: fileno, logfile = tempfile.mkstemp(prefix="harness-log-") os.close(fileno) logfile_tail = follow_file(logfile) atexit.register(maybe_remove_logfile) logfile = os.path.abspath(os.path.expanduser(logfile)) maybe_remove_logfile() if app_type != "fennec-on-device": harness_options['logFile'] = logfile env = {} env.update(os.environ) env['MOZ_NO_REMOTE'] = '1' env['XPCOM_DEBUG_BREAK'] = 'stack' env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1' env.update(extra_environment) if norun: cmdargs.append("-no-remote") # Create the addon XPI so mozrunner will copy it to the profile it creates. # We delete it below after getting mozrunner to create the profile. from cuddlefish.xpi import build_xpi xpi_path = tempfile.mktemp(suffix='cfx-tmp.xpi') build_xpi(template_root_dir=harness_root_dir, manifest=manifest_rdf, xpi_path=xpi_path, harness_options=harness_options, limit_to=used_files) addons.append(xpi_path) starttime = last_output_time = time.time() # Redirect runner output to a file so we can catch output not generated # by us. # In theory, we could do this using simple redirection on all platforms # other than Windows, but this way we only have a single codepath to # maintain. fileno, outfile = tempfile.mkstemp(prefix="harness-stdout-") os.close(fileno) outfile_tail = follow_file(outfile) def maybe_remove_outfile(): if os.path.exists(outfile): os.remove(outfile) atexit.register(maybe_remove_outfile) outf = open(outfile, "w") popen_kwargs = {'stdout': outf, 'stderr': outf} profile = None if app_type == "fennec-on-device": # Install a special addon when we run firefox on mobile device # in order to be able to kill it mydir = os.path.dirname(os.path.abspath(__file__)) addon_dir = os.path.join(mydir, "mobile-utils") addons.append(addon_dir) # the XPI file is copied into the profile here profile = profile_class(addons=addons, profile=profiledir, preferences=preferences) # Delete the temporary xpi file os.remove(xpi_path) runner = runner_class(profile=profile, binary=binary, env=env, cmdargs=cmdargs, kp_kwargs=popen_kwargs) sys.stdout.flush() sys.stderr.flush() if app_type == "fennec-on-device": if not enable_mobile: print >> sys.stderr, """ WARNING: Firefox Mobile support is still experimental. If you would like to run an addon on this platform, use --force-mobile flag: cfx --force-mobile""" return 0 # In case of mobile device, we need to get stdio from `adb logcat` cmd: # First flush logs in order to avoid catching previous ones subprocess.call([binary, "logcat", "-c"]) # Launch adb command runner.start() # We can immediatly remove temporary profile folder # as it has been uploaded to the device profile.cleanup() # We are not going to use the output log file outf.close() # Then we simply display stdout of `adb logcat` p = subprocess.Popen( [binary, "logcat", "stderr:V stdout:V GeckoConsole:V *:S"], stdout=subprocess.PIPE) while True: line = p.stdout.readline() if line == '': break # mobile-utils addon contains an application quit event observer # that will print this string: if "APPLICATION-QUIT" in line: break if verbose: # if --verbose is given, we display everything: # All JS Console messages, stdout and stderr. m = CLEANUP_ADB.match(line) if not m: print line.rstrip() continue print m.group(3) else: # Otherwise, display addons messages dispatched through # console.[info, log, debug, warning, error](msg) m = FILTER_ONLY_CONSOLE_FROM_ADB.match(line) if m: print m.group(2) print >> sys.stderr, "Program terminated successfully." return 0 print >> sys.stderr, "Using binary at '%s'." % runner.binary # Ensure cfx is being used with Firefox 4.0+. # TODO: instead of dying when Firefox is < 4, warn when Firefox is outside # the minVersion/maxVersion boundaries. version_output = check_output(runner.command + ["-v"]) # Note: this regex doesn't handle all valid versions in the Toolkit Version # Format <https://developer.mozilla.org/en/Toolkit_version_format>, just the # common subset that we expect Mozilla apps to use. mo = re.search(r"Mozilla (Firefox|Iceweasel|Fennec)\b[^ ]* ((\d+)\.\S*)", version_output) if not mo: # cfx may be used with Thunderbird, SeaMonkey or an exotic Firefox # version. print """ WARNING: cannot determine Firefox version; please ensure you are running a Mozilla application equivalent to Firefox 4.0 or greater. """ elif mo.group(1) == "Fennec": # For now, only allow running on Mobile with --force-mobile argument if not enable_mobile: print """ WARNING: Firefox Mobile support is still experimental. If you would like to run an addon on this platform, use --force-mobile flag: cfx --force-mobile""" return else: version = mo.group(3) if int(version) < 4: print """ cfx requires Firefox 4 or greater and is unable to find a compatible binary. Please install a newer version of Firefox or provide the path to your existing compatible version with the --binary flag: cfx --binary=PATH_TO_FIREFOX_BINARY""" return # Set the appropriate extensions.checkCompatibility preference to false, # so the tests run even if the SDK is not marked as compatible with the # version of Firefox on which they are running, and we don't have to # ensure we update the maxVersion before the version of Firefox changes # every six weeks. # # The regex we use here is effectively the same as BRANCH_REGEX from # /toolkit/mozapps/extensions/content/extensions.js, which toolkit apps # use to determine whether or not to load an incompatible addon. # br = re.search(r"^([^\.]+\.[0-9]+[a-z]*).*", mo.group(2), re.I) if br: prefname = 'extensions.checkCompatibility.' + br.group(1) profile.preferences[prefname] = False # Calling profile.set_preferences here duplicates the list of prefs # in prefs.js, since the profile calls self.set_preferences in its # constructor, but that is ok, because it doesn't change the set of # preferences that are ultimately registered in Firefox. profile.set_preferences(profile.preferences) print >> sys.stderr, "Using profile at '%s'." % profile.profile sys.stderr.flush() if norun: print "To launch the application, enter the following command:" print " ".join(runner.command) + " " + (" ".join(runner.cmdargs)) return 0 runner.start() done = False result = None try: while not done: time.sleep(0.05) for tail in (logfile_tail, outfile_tail): if tail: new_chars = tail.next() if new_chars: last_output_time = time.time() sys.stderr.write(new_chars) sys.stderr.flush() if os.path.exists(resultfile): result = open(resultfile).read() if result: if result in ['OK', 'FAIL']: done = True else: sys.stderr.write( "Hrm, resultfile (%s) contained something weird (%d bytes)\n" % (resultfile, len(result))) sys.stderr.write("'" + result + "'\n") if enforce_timeouts: if time.time() - last_output_time > OUTPUT_TIMEOUT: raise Exception("Test output exceeded timeout (%ds)." % OUTPUT_TIMEOUT) if time.time() - starttime > RUN_TIMEOUT: raise Exception("Test run exceeded timeout (%ds)." % RUN_TIMEOUT) except: runner.stop() raise else: runner.wait(10) finally: outf.close() if profile: profile.cleanup() print >> sys.stderr, "Total time: %f seconds" % (time.time() - starttime) if result == 'OK': print >> sys.stderr, "Program terminated successfully." return 0 else: print >> sys.stderr, "Program terminated unsuccessfully." return -1
def run_app(harness_root_dir, manifest_rdf, harness_options, app_type, binary=None, profiledir=None, verbose=False, parseable=False, enforce_timeouts=False, logfile=None, addons=None, args=None, extra_environment={}, norun=None, noquit=None, used_files=None, enable_mobile=False, mobile_app_name=None, env_root=None, is_running_tests=False, overload_modules=False, bundle_sdk=True, pkgdir="", enable_e10s=False, no_connections=False): if binary: binary = os.path.expanduser(binary) if addons is None: addons = [] else: addons = list(addons) cmdargs = [] preferences = dict(DEFAULT_COMMON_PREFS) if is_running_tests: preferences.update(DEFAULT_TEST_PREFS) if no_connections: preferences.update(DEFAULT_NO_CONNECTIONS_PREFS) if enable_e10s: preferences['browser.tabs.remote.autostart'] = True # For now, only allow running on Mobile with --force-mobile argument if app_type in ["fennec-on-device"] and not enable_mobile: print """ WARNING: Firefox Mobile support is still experimental. If you would like to run an addon on this platform, use --force-mobile flag: cfx --force-mobile""" return 0 if app_type == "fennec-on-device": profile_class = FennecProfile preferences.update(DEFAULT_FENNEC_PREFS) runner_class = RemoteFennecRunner # We pass the intent name through command arguments cmdargs.append(mobile_app_name) elif app_type == "xulrunner": profile_class = XulrunnerAppProfile runner_class = XulrunnerAppRunner cmdargs.append(os.path.join(harness_root_dir, 'application.ini')) elif app_type == "firefox": profile_class = mozrunner.FirefoxProfile preferences.update(DEFAULT_FIREFOX_PREFS) runner_class = mozrunner.FirefoxRunner elif app_type == "thunderbird": profile_class = mozrunner.ThunderbirdProfile preferences.update(DEFAULT_THUNDERBIRD_PREFS) runner_class = mozrunner.ThunderbirdRunner else: raise ValueError("Unknown app: %s" % app_type) if sys.platform == 'darwin' and app_type != 'xulrunner': cmdargs.append('-foreground') if args: cmdargs.extend(shlex.split(args)) # TODO: handle logs on remote device if app_type != "fennec-on-device": # tempfile.gettempdir() was constant, preventing two simultaneous "cfx # run"/"cfx test" on the same host. On unix it points at /tmp (which is # world-writeable), enabling a symlink attack (e.g. imagine some bad guy # does 'ln -s ~/.ssh/id_rsa /tmp/harness_result'). NamedTemporaryFile # gives us a unique filename that fixes both problems. We leave the # (0-byte) file in place until the browser-side code starts writing to # it, otherwise the symlink attack becomes possible again. fileno,resultfile = tempfile.mkstemp(prefix="harness-result-") os.close(fileno) harness_options['resultFile'] = resultfile def maybe_remove_logfile(): if os.path.exists(logfile): os.remove(logfile) logfile_tail = None # We always buffer output through a logfile for two reasons: # 1. On Windows, it's the only way to print console output to stdout/err. # 2. It enables us to keep track of the last time output was emitted, # so we can raise an exception if the test runner hangs. if not logfile: fileno,logfile = tempfile.mkstemp(prefix="harness-log-") os.close(fileno) logfile_tail = follow_file(logfile) atexit.register(maybe_remove_logfile) logfile = os.path.abspath(os.path.expanduser(logfile)) maybe_remove_logfile() env = {} env.update(os.environ) if no_connections: env['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] = '1' env['MOZ_NO_REMOTE'] = '1' env['XPCOM_DEBUG_BREAK'] = 'stack' env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1' env.update(extra_environment) if norun: cmdargs.append("-no-remote") # Create the addon XPI so mozrunner will copy it to the profile it creates. # We delete it below after getting mozrunner to create the profile. from cuddlefish.xpi import build_xpi xpi_path = tempfile.mktemp(suffix='cfx-tmp.xpi') build_xpi(template_root_dir=harness_root_dir, manifest=manifest_rdf, xpi_path=xpi_path, harness_options=harness_options, limit_to=used_files, bundle_sdk=bundle_sdk, pkgdir=pkgdir) addons.append(xpi_path) starttime = last_output_time = time.time() # Redirect runner output to a file so we can catch output not generated # by us. # In theory, we could do this using simple redirection on all platforms # other than Windows, but this way we only have a single codepath to # maintain. fileno,outfile = tempfile.mkstemp(prefix="harness-stdout-") os.close(fileno) outfile_tail = follow_file(outfile) def maybe_remove_outfile(): if os.path.exists(outfile): try: os.remove(outfile) except Exception, e: print "Error Cleaning up: " + str(e)
def run_app(harness_root_dir, manifest_rdf, harness_options, app_type, binary=None, profiledir=None, verbose=False, timeout=None, logfile=None, addons=None, args=None, norun=None, used_files=None, enable_mobile=False): if binary: binary = os.path.expanduser(binary) if addons is None: addons = [] else: addons = list(addons) cmdargs = [] preferences = dict(DEFAULT_COMMON_PREFS) if app_type == "xulrunner": profile_class = XulrunnerAppProfile runner_class = XulrunnerAppRunner cmdargs.append(os.path.join(harness_root_dir, 'application.ini')) else: if app_type == "firefox": profile_class = mozrunner.FirefoxProfile preferences.update(DEFAULT_FIREFOX_PREFS) runner_class = mozrunner.FirefoxRunner elif app_type == "thunderbird": profile_class = mozrunner.ThunderbirdProfile preferences.update(DEFAULT_THUNDERBIRD_PREFS) runner_class = mozrunner.ThunderbirdRunner elif app_type == "fennec": profile_class = FennecProfile preferences.update(DEFAULT_FENNEC_PREFS) runner_class = FennecRunner else: raise ValueError("Unknown app: %s" % app_type) if sys.platform == 'darwin': cmdargs.append('-foreground') if args: cmdargs.extend(shlex.split(args)) # tempfile.gettempdir() was constant, preventing two simultaneous "cfx # run"/"cfx test" on the same host. On unix it points at /tmp (which is # world-writeable), enabling a symlink attack (e.g. imagine some bad guy # does 'ln -s ~/.ssh/id_rsa /tmp/harness_result'). NamedTemporaryFile # gives us a unique filename that fixes both problems. We leave the # (0-byte) file in place until the browser-side code starts writing to # it, otherwise the symlink attack becomes possible again. fileno,resultfile = tempfile.mkstemp(prefix="harness-result-") os.close(fileno) harness_options['resultFile'] = resultfile def maybe_remove_logfile(): if os.path.exists(logfile): os.remove(logfile) logfile_tail = None if sys.platform in ['win32', 'cygwin']: if not logfile: # If we're on Windows, we need to keep a logfile simply # to print console output to stdout. fileno,logfile = tempfile.mkstemp(prefix="harness-log-") os.close(fileno) logfile_tail = follow_file(logfile) atexit.register(maybe_remove_logfile) if logfile: logfile = os.path.abspath(os.path.expanduser(logfile)) maybe_remove_logfile() harness_options['logFile'] = logfile env = {} env.update(os.environ) env['MOZ_NO_REMOTE'] = '1' env['XPCOM_DEBUG_BREAK'] = 'warn' env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1' if norun: cmdargs.append("-no-remote") # Create the addon XPI so mozrunner will copy it to the profile it creates. # We delete it below after getting mozrunner to create the profile. from cuddlefish.xpi import build_xpi xpi_path = tempfile.mktemp(suffix='cfx-tmp.xpi') build_xpi(template_root_dir=harness_root_dir, manifest=manifest_rdf, xpi_path=xpi_path, harness_options=harness_options, limit_to=used_files) addons.append(xpi_path) starttime = time.time() popen_kwargs = {} profile = None # the XPI file is copied into the profile here profile = profile_class(addons=addons, profile=profiledir, preferences=preferences) # Delete the temporary xpi file os.remove(xpi_path) runner = runner_class(profile=profile, binary=binary, env=env, cmdargs=cmdargs, kp_kwargs=popen_kwargs) sys.stdout.flush(); sys.stderr.flush() print >>sys.stderr, "Using binary at '%s'." % runner.binary # Ensure cfx is being used with Firefox 4.0+. # TODO: instead of dying when Firefox is < 4, warn when Firefox is outside # the minVersion/maxVersion boundaries. version_output = check_output(runner.command + ["-v"]) # Note: this regex doesn't handle all valid versions in the Toolkit Version # Format <https://developer.mozilla.org/en/Toolkit_version_format>, just the # common subset that we expect Mozilla apps to use. mo = re.search(r"Mozilla (Firefox|Iceweasel|Fennec) ((\d+)\.\S*)", version_output) if not mo: # cfx may be used with Thunderbird, SeaMonkey or an exotic Firefox # version. print """ WARNING: cannot determine Firefox version; please ensure you are running a Mozilla application equivalent to Firefox 4.0 or greater. """ elif mo.group(1) == "Fennec": # For now, only allow running on Mobile with --force-mobile argument if not enable_mobile: print """ WARNING: Firefox Mobile support is still experimental. If you would like to run an addon on this platform, use --force-mobile flag: cfx --force-mobile""" return else: version = mo.group(3) if int(version) < 4: print """ cfx requires Firefox 4 or greater and is unable to find a compatible binary. Please install a newer version of Firefox or provide the path to your existing compatible version with the --binary flag: cfx --binary=PATH_TO_FIREFOX_BINARY""" return # Set the appropriate extensions.checkCompatibility preference to false, # so the tests run even if the SDK is not marked as compatible with the # version of Firefox on which they are running, and we don't have to # ensure we update the maxVersion before the version of Firefox changes # every six weeks. # # The regex we use here is effectively the same as BRANCH_REGEX from # /toolkit/mozapps/extensions/content/extensions.js, which toolkit apps # use to determine whether or not to load an incompatible addon. # br = re.search(r"^([^\.]+\.[0-9]+[a-z]*).*", mo.group(2), re.I) if br: prefname = 'extensions.checkCompatibility.' + br.group(1) profile.preferences[prefname] = False # Calling profile.set_preferences here duplicates the list of prefs # in prefs.js, since the profile calls self.set_preferences in its # constructor, but that is ok, because it doesn't change the set of # preferences that are ultimately registered in Firefox. profile.set_preferences(profile.preferences) print >>sys.stderr, "Using profile at '%s'." % profile.profile sys.stderr.flush() if norun: print "To launch the application, enter the following command:" print " ".join(runner.command) + " " + (" ".join(runner.cmdargs)) return 0 runner.start() done = False output = None try: while not done: time.sleep(0.05) if logfile_tail: new_chars = logfile_tail.next() if new_chars: sys.stderr.write(new_chars) sys.stderr.flush() if os.path.exists(resultfile): output = open(resultfile).read() if output: if output in ['OK', 'FAIL']: done = True else: sys.stderr.write("Hrm, resultfile (%s) contained something weird (%d bytes)\n" % (resultfile, len(output))) sys.stderr.write("'"+output+"'\n") if timeout and (time.time() - starttime > timeout): raise Exception("Wait timeout exceeded (%ds)" % timeout) except: runner.stop() raise else: runner.wait(10) finally: if profile: profile.cleanup() print >>sys.stderr, "Total time: %f seconds" % (time.time() - starttime) if output == 'OK': print >>sys.stderr, "Program terminated successfully." return 0 else: print >>sys.stderr, "Program terminated unsuccessfully." return -1
def run_app(harness_root_dir, manifest_rdf, harness_options, app_type, binary=None, profiledir=None, verbose=False, parseable=False, enforce_timeouts=False, logfile=None, addons=None, args=None, extra_environment={}, norun=None, used_files=None, enable_mobile=False, mobile_app_name=None, env_root=None, is_running_tests=False, overload_modules=False, bundle_sdk=True): if binary: binary = os.path.expanduser(binary) if addons is None: addons = [] else: addons = list(addons) cmdargs = [] preferences = dict(DEFAULT_COMMON_PREFS) # For now, only allow running on Mobile with --force-mobile argument if app_type in ["fennec", "fennec-on-device"] and not enable_mobile: print """ WARNING: Firefox Mobile support is still experimental. If you would like to run an addon on this platform, use --force-mobile flag: cfx --force-mobile""" return 0 if app_type == "fennec-on-device": profile_class = FennecProfile preferences.update(DEFAULT_FENNEC_PREFS) runner_class = RemoteFennecRunner # We pass the intent name through command arguments cmdargs.append(mobile_app_name) elif enable_mobile or app_type == "fennec": profile_class = FennecProfile preferences.update(DEFAULT_FENNEC_PREFS) runner_class = FennecRunner elif app_type == "xulrunner": profile_class = XulrunnerAppProfile runner_class = XulrunnerAppRunner cmdargs.append(os.path.join(harness_root_dir, 'application.ini')) elif app_type == "firefox": profile_class = mozrunner.FirefoxProfile preferences.update(DEFAULT_FIREFOX_PREFS) runner_class = mozrunner.FirefoxRunner elif app_type == "thunderbird": profile_class = mozrunner.ThunderbirdProfile preferences.update(DEFAULT_THUNDERBIRD_PREFS) runner_class = mozrunner.ThunderbirdRunner else: raise ValueError("Unknown app: %s" % app_type) if sys.platform == 'darwin' and app_type != 'xulrunner': cmdargs.append('-foreground') if args: cmdargs.extend(shlex.split(args)) # TODO: handle logs on remote device if app_type != "fennec-on-device": # tempfile.gettempdir() was constant, preventing two simultaneous "cfx # run"/"cfx test" on the same host. On unix it points at /tmp (which is # world-writeable), enabling a symlink attack (e.g. imagine some bad guy # does 'ln -s ~/.ssh/id_rsa /tmp/harness_result'). NamedTemporaryFile # gives us a unique filename that fixes both problems. We leave the # (0-byte) file in place until the browser-side code starts writing to # it, otherwise the symlink attack becomes possible again. fileno,resultfile = tempfile.mkstemp(prefix="harness-result-") os.close(fileno) harness_options['resultFile'] = resultfile def maybe_remove_logfile(): if os.path.exists(logfile): os.remove(logfile) logfile_tail = None # We always buffer output through a logfile for two reasons: # 1. On Windows, it's the only way to print console output to stdout/err. # 2. It enables us to keep track of the last time output was emitted, # so we can raise an exception if the test runner hangs. if not logfile: fileno,logfile = tempfile.mkstemp(prefix="harness-log-") os.close(fileno) logfile_tail = follow_file(logfile) atexit.register(maybe_remove_logfile) logfile = os.path.abspath(os.path.expanduser(logfile)) maybe_remove_logfile() if app_type != "fennec-on-device": harness_options['logFile'] = logfile env = {} env.update(os.environ) env['MOZ_NO_REMOTE'] = '1' env['XPCOM_DEBUG_BREAK'] = 'stack' env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1' env.update(extra_environment) if norun: cmdargs.append("-no-remote") # Create the addon XPI so mozrunner will copy it to the profile it creates. # We delete it below after getting mozrunner to create the profile. from cuddlefish.xpi import build_xpi xpi_path = tempfile.mktemp(suffix='cfx-tmp.xpi') build_xpi(template_root_dir=harness_root_dir, manifest=manifest_rdf, xpi_path=xpi_path, harness_options=harness_options, limit_to=used_files, bundle_sdk=bundle_sdk) addons.append(xpi_path) starttime = last_output_time = time.time() # Redirect runner output to a file so we can catch output not generated # by us. # In theory, we could do this using simple redirection on all platforms # other than Windows, but this way we only have a single codepath to # maintain. fileno,outfile = tempfile.mkstemp(prefix="harness-stdout-") os.close(fileno) outfile_tail = follow_file(outfile) def maybe_remove_outfile(): if os.path.exists(outfile): os.remove(outfile) atexit.register(maybe_remove_outfile) outf = open(outfile, "w") popen_kwargs = { 'stdout': outf, 'stderr': outf} profile = None if app_type == "fennec-on-device": # Install a special addon when we run firefox on mobile device # in order to be able to kill it mydir = os.path.dirname(os.path.abspath(__file__)) addon_dir = os.path.join(mydir, "mobile-utils") addons.append(addon_dir) # Overload addon-specific commonjs modules path with lib/ folder overloads = dict() if overload_modules: overloads[""] = os.path.join(env_root, "lib") # Overload tests/ mapping with test/ folder, only when running test if is_running_tests: overloads["tests"] = os.path.join(env_root, "test") set_overloaded_modules(env_root, app_type, harness_options["jetpackID"], \ preferences, overloads) # the XPI file is copied into the profile here profile = profile_class(addons=addons, profile=profiledir, preferences=preferences) # Delete the temporary xpi file os.remove(xpi_path) # Copy overloaded files registered in set_overloaded_modules # For testing on device, we have to copy overloaded files from fs # to the device and use device path instead of local fs path. # (has to be done after the call to profile_class() which eventualy creates # profile folder) if app_type == "fennec-on-device": profile_path = profile.profile for name, path in overloads.items(): shutil.copytree(path, \ os.path.join(profile_path, "overloads", name)) runner = runner_class(profile=profile, binary=binary, env=env, cmdargs=cmdargs, kp_kwargs=popen_kwargs) sys.stdout.flush(); sys.stderr.flush() if app_type == "fennec-on-device": if not enable_mobile: print >>sys.stderr, """ WARNING: Firefox Mobile support is still experimental. If you would like to run an addon on this platform, use --force-mobile flag: cfx --force-mobile""" return 0 # In case of mobile device, we need to get stdio from `adb logcat` cmd: # First flush logs in order to avoid catching previous ones subprocess.call([binary, "logcat", "-c"]) # Launch adb command runner.start() # We can immediatly remove temporary profile folder # as it has been uploaded to the device profile.cleanup() # We are not going to use the output log file outf.close() # Then we simply display stdout of `adb logcat` p = subprocess.Popen([binary, "logcat", "stderr:V stdout:V GeckoConsole:V *:S"], stdout=subprocess.PIPE) while True: line = p.stdout.readline() if line == '': break # mobile-utils addon contains an application quit event observer # that will print this string: if "APPLICATION-QUIT" in line: break if verbose: # if --verbose is given, we display everything: # All JS Console messages, stdout and stderr. m = CLEANUP_ADB.match(line) if not m: print line.rstrip() continue print m.group(3) else: # Otherwise, display addons messages dispatched through # console.[info, log, debug, warning, error](msg) m = FILTER_ONLY_CONSOLE_FROM_ADB.match(line) if m: print m.group(2) print >>sys.stderr, "Program terminated successfully." return 0 print >>sys.stderr, "Using binary at '%s'." % runner.binary # Ensure cfx is being used with Firefox 4.0+. # TODO: instead of dying when Firefox is < 4, warn when Firefox is outside # the minVersion/maxVersion boundaries. version_output = check_output(runner.command + ["-v"]) # Note: this regex doesn't handle all valid versions in the Toolkit Version # Format <https://developer.mozilla.org/en/Toolkit_version_format>, just the # common subset that we expect Mozilla apps to use. mo = re.search(r"Mozilla (Firefox|Iceweasel|Fennec)\b[^ ]* ((\d+)\.\S*)", version_output) if not mo: # cfx may be used with Thunderbird, SeaMonkey or an exotic Firefox # version. print """ WARNING: cannot determine Firefox version; please ensure you are running a Mozilla application equivalent to Firefox 4.0 or greater. """ elif mo.group(1) == "Fennec": # For now, only allow running on Mobile with --force-mobile argument if not enable_mobile: print """ WARNING: Firefox Mobile support is still experimental. If you would like to run an addon on this platform, use --force-mobile flag: cfx --force-mobile""" return else: version = mo.group(3) if int(version) < 4: print """ cfx requires Firefox 4 or greater and is unable to find a compatible binary. Please install a newer version of Firefox or provide the path to your existing compatible version with the --binary flag: cfx --binary=PATH_TO_FIREFOX_BINARY""" return # Set the appropriate extensions.checkCompatibility preference to false, # so the tests run even if the SDK is not marked as compatible with the # version of Firefox on which they are running, and we don't have to # ensure we update the maxVersion before the version of Firefox changes # every six weeks. # # The regex we use here is effectively the same as BRANCH_REGEX from # /toolkit/mozapps/extensions/content/extensions.js, which toolkit apps # use to determine whether or not to load an incompatible addon. # br = re.search(r"^([^\.]+\.[0-9]+[a-z]*).*", mo.group(2), re.I) if br: prefname = 'extensions.checkCompatibility.' + br.group(1) profile.preferences[prefname] = False # Calling profile.set_preferences here duplicates the list of prefs # in prefs.js, since the profile calls self.set_preferences in its # constructor, but that is ok, because it doesn't change the set of # preferences that are ultimately registered in Firefox. profile.set_preferences(profile.preferences) print >>sys.stderr, "Using profile at '%s'." % profile.profile sys.stderr.flush() if norun: print "To launch the application, enter the following command:" print " ".join(runner.command) + " " + (" ".join(runner.cmdargs)) return 0 runner.start() done = False result = None test_name = "unknown" def Timeout(message, test_name, parseable): if parseable: sys.stderr.write("TEST-UNEXPECTED-FAIL | %s | %s\n" % (test_name, message)) sys.stderr.flush() return Exception(message) try: while not done: time.sleep(0.05) for tail in (logfile_tail, outfile_tail): if tail: new_chars = tail.next() if new_chars: last_output_time = time.time() sys.stderr.write(new_chars) sys.stderr.flush() if is_running_tests and parseable: match = PARSEABLE_TEST_NAME.search(new_chars) if match: test_name = match.group(1) if os.path.exists(resultfile): result = open(resultfile).read() if result: if result in ['OK', 'FAIL']: done = True else: sys.stderr.write("Hrm, resultfile (%s) contained something weird (%d bytes)\n" % (resultfile, len(result))) sys.stderr.write("'"+result+"'\n") if enforce_timeouts: if time.time() - last_output_time > OUTPUT_TIMEOUT: raise Timeout("Test output exceeded timeout (%ds)." % OUTPUT_TIMEOUT, test_name, parseable) if time.time() - starttime > RUN_TIMEOUT: raise Timeout("Test run exceeded timeout (%ds)." % RUN_TIMEOUT, test_name, parseable) except: runner.stop() raise else: runner.wait(10) finally: outf.close() if profile: profile.cleanup() print >>sys.stderr, "Total time: %f seconds" % (time.time() - starttime) if result == 'OK': print >>sys.stderr, "Program terminated successfully." return 0 else: print >>sys.stderr, "Program terminated unsuccessfully." return -1
def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, defaults=None, env_root=os.environ.get('CUDDLEFISH_ROOT')): parser_kwargs = dict(arguments=arguments, global_options=global_options, parser_groups=parser_groups, usage=usage, defaults=defaults) (options, args) = parse_args(**parser_kwargs) config_args = get_config_args(options.config, env_root) # reparse configs with arguments from local.json if config_args: parser_kwargs['arguments'] += config_args (options, args) = parse_args(**parser_kwargs) command = args[0] if command == "init": initializer(env_root, args) return if command == "develop": run_development_mode(env_root, defaults=options.__dict__) return if command == "testpkgs": test_all_packages(env_root, defaults=options.__dict__) return elif command == "testex": test_all_examples(env_root, defaults=options.__dict__) return elif command == "testall": test_all(env_root, defaults=options.__dict__) return elif command == "testcfx": test_cfx(env_root, options.verbose) return elif command == "docs": import subprocess import time import cuddlefish.server print "One moment." popen = subprocess.Popen( [sys.executable, cuddlefish.server.__file__, 'daemonic']) # TODO: See if there's actually a way to block on # a particular event occurring, rather than this # relatively arbitrary/generous amount. time.sleep(cuddlefish.server.IDLE_WEBPAGE_TIMEOUT * 2) return elif command == "sdocs": import cuddlefish.server # TODO: Allow user to change this filename via cmd line. filename = 'addon-sdk-docs.tgz' cuddlefish.server.generate_static_docs(env_root, filename, options.baseurl) print "Wrote %s." % filename return target_cfg_json = None if not target_cfg: if not options.pkgdir: options.pkgdir = find_parent_package(os.getcwd()) if not options.pkgdir: print >> sys.stderr, ("cannot find 'package.json' in the" " current directory or any parent.") sys.exit(1) else: options.pkgdir = os.path.abspath(options.pkgdir) if not os.path.exists(os.path.join(options.pkgdir, 'package.json')): print >> sys.stderr, ("cannot find 'package.json' in" " %s." % options.pkgdir) sys.exit(1) target_cfg_json = os.path.join(options.pkgdir, 'package.json') target_cfg = packaging.get_config_in_dir(options.pkgdir) # At this point, we're either building an XPI or running Jetpack code in # a Mozilla application (which includes running tests). use_main = False timeout = None inherited_options = ['verbose', 'enable_e10s'] if command == "xpi": use_main = True elif command == "test": if 'tests' not in target_cfg: target_cfg['tests'] = [] timeout = TEST_RUN_TIMEOUT inherited_options.extend(['iterations', 'filter', 'profileMemory']) elif command == "run": use_main = True else: print >> sys.stderr, "Unknown command: %s" % command print >> sys.stderr, "Try using '--help' for assistance." sys.exit(1) if use_main and 'main' not in target_cfg: # If the user supplies a template dir, then the main # program may be contained in the template. if not options.templatedir: print >> sys.stderr, "package.json does not have a 'main' entry." sys.exit(1) if not pkg_cfg: pkg_cfg = packaging.build_config(env_root, target_cfg) target = target_cfg.name # the harness_guid is used for an XPCOM class ID. We use the # JetpackID for the add-on ID and the XPCOM contract ID. if "harnessClassID" in target_cfg: # For the sake of non-bootstrapped extensions, we allow to specify the # classID of harness' XPCOM component in package.json. This makes it # possible to register the component using a static chrome.manifest file harness_guid = target_cfg["harnessClassID"] else: import uuid harness_guid = str(uuid.uuid4()) # TODO: Consider keeping a cache of dynamic UUIDs, based # on absolute filesystem pathname, in the root directory # or something. if command in ('xpi', 'run'): from cuddlefish.preflight import preflight_config if target_cfg_json: config_was_ok, modified = preflight_config( target_cfg, target_cfg_json, keydir=options.keydir, err_if_privkey_not_found=False) if not config_was_ok: if modified: # we need to re-read package.json . The safest approach # is to re-run the "cfx xpi"/"cfx run" command. print >> sys.stderr, ( "package.json modified: please re-run" " 'cfx %s'" % command) else: print >> sys.stderr, ("package.json needs modification:" " please update it and then re-run" " 'cfx %s'" % command) sys.exit(1) # if we make it this far, we have a JID else: assert command == "test" if "id" in target_cfg: jid = target_cfg["id"] assert not jid.endswith("@jetpack") unique_prefix = '%s-' % jid # used for resource: URLs else: # The Jetpack ID is not required for cfx test, in which case we have to # make one up based on the GUID. if options.use_server: # The harness' contractID (hence also the jid and the harness_guid) # need to be static in the "development mode", so that bootstrap.js # can unload the previous version of the package being developed. harness_guid = '2974c5b5-b671-46f8-a4bb-63c6eca6261b' unique_prefix = '%s-' % target jid = harness_guid assert not jid.endswith("@jetpack") if (jid.startswith("jid0-") or jid.startswith("anonid0-")): bundle_id = jid + "@jetpack" # Don't append "@jetpack" to old-style IDs, as they should be exactly # as specified by the addon author so AMO and Firefox continue to treat # their addon bundles as representing the same addon (and also because # they may already have an @ sign in them, and there can be only one). else: bundle_id = jid # the resource: URL's prefix is treated too much like a DNS hostname unique_prefix = unique_prefix.lower() unique_prefix = unique_prefix.replace("@", "-at-") unique_prefix = unique_prefix.replace(".", "-dot-") targets = [target] if command == "test": targets.append(options.test_runner_pkg) if options.extra_packages: targets.extend(options.extra_packages.split(",")) deps = packaging.get_deps_for_targets(pkg_cfg, targets) build = packaging.generate_build_for_target( pkg_cfg, target, deps, prefix=unique_prefix, # used to create resource: URLs include_dep_tests=options.dep_tests) if 'resources' in build: resources = build.resources for name in resources: resources[name] = os.path.abspath(resources[name]) harness_contract_id = ('@mozilla.org/harness-service;1?id=%s' % jid) harness_options = { 'bootstrap': { 'contractID': harness_contract_id, 'classID': '{%s}' % harness_guid }, 'jetpackID': jid, 'bundleID': bundle_id, 'staticArgs': options.static_args, 'name': target, } harness_options.update(build) if command == "test": # This should be contained in the test runner package. harness_options['main'] = 'run-tests' else: harness_options['main'] = target_cfg.get('main') for option in inherited_options: harness_options[option] = getattr(options, option) harness_options['metadata'] = packaging.get_metadata(pkg_cfg, deps) sdk_version = get_version(env_root) harness_options['sdkVersion'] = sdk_version packaging.call_plugins(pkg_cfg, deps) retval = 0 if options.templatedir: app_extension_dir = os.path.abspath(options.templatedir) else: mydir = os.path.dirname(os.path.abspath(__file__)) if sys.platform == "darwin": # If we're on OS X, at least point into the XULRunner # app dir so we run as a proper app if using XULRunner. app_extension_dir = os.path.join(mydir, "Test App.app", "Contents", "Resources") else: app_extension_dir = os.path.join(mydir, "app-extension") if command == 'xpi': from cuddlefish.xpi import build_xpi from cuddlefish.rdf import gen_manifest, RDFUpdate manifest = gen_manifest(template_root_dir=app_extension_dir, target_cfg=target_cfg, bundle_id=bundle_id, update_url=options.update_url, bootstrap=True) if options.update_link: rdf_name = UPDATE_RDF_FILENAME % target_cfg.name print "Exporting update description to %s." % rdf_name update = RDFUpdate() update.add(manifest, options.update_link) open(rdf_name, "w").write(str(update)) xpi_name = XPI_FILENAME % target_cfg.name print "Exporting extension to %s." % xpi_name build_xpi(template_root_dir=app_extension_dir, manifest=manifest, xpi_name=xpi_name, harness_options=harness_options) else: if options.use_server: from cuddlefish.server import run_app else: from cuddlefish.runner import run_app if options.profiledir: options.profiledir = os.path.expanduser(options.profiledir) options.profiledir = os.path.abspath(options.profiledir) if options.addons is not None: options.addons = options.addons.split(",") try: retval = run_app(harness_root_dir=app_extension_dir, harness_options=harness_options, app_type=options.app, binary=options.binary, profiledir=options.profiledir, verbose=options.verbose, timeout=timeout, logfile=options.logfile, addons=options.addons) except Exception, e: if str(e).startswith(MOZRUNNER_BIN_NOT_FOUND): print >> sys.stderr, MOZRUNNER_BIN_NOT_FOUND_HELP.strip() retval = -1 else: raise
def run_app(harness_root_dir, manifest_rdf, harness_options, app_type, binary=None, profiledir=None, verbose=False, parseable=False, enforce_timeouts=False, logfile=None, addons=None, args=None, extra_environment={}, norun=None, noquit=None, used_files=None, enable_mobile=False, mobile_app_name=None, env_root=None, is_running_tests=False, overload_modules=False, bundle_sdk=True, pkgdir="", enable_e10s=False, no_connections=False): if binary: binary = os.path.expanduser(binary) if addons is None: addons = [] else: addons = list(addons) cmdargs = [] preferences = dict(DEFAULT_COMMON_PREFS) if is_running_tests: preferences.update(DEFAULT_TEST_PREFS) if no_connections: preferences.update(DEFAULT_NO_CONNECTIONS_PREFS) if enable_e10s: preferences['browser.tabs.remote.autostart'] = True # For now, only allow running on Mobile with --force-mobile argument if app_type in ["fennec-on-device"] and not enable_mobile: print """ WARNING: Firefox Mobile support is still experimental. If you would like to run an addon on this platform, use --force-mobile flag: cfx --force-mobile""" return 0 if app_type == "fennec-on-device": profile_class = FennecProfile preferences.update(DEFAULT_FENNEC_PREFS) runner_class = RemoteFennecRunner # We pass the intent name through command arguments cmdargs.append(mobile_app_name) elif app_type == "xulrunner": profile_class = XulrunnerAppProfile runner_class = XulrunnerAppRunner cmdargs.append(os.path.join(harness_root_dir, 'application.ini')) elif app_type == "firefox": profile_class = mozrunner.FirefoxProfile preferences.update(DEFAULT_FIREFOX_PREFS) runner_class = mozrunner.FirefoxRunner elif app_type == "thunderbird": profile_class = mozrunner.ThunderbirdProfile preferences.update(DEFAULT_THUNDERBIRD_PREFS) runner_class = mozrunner.ThunderbirdRunner else: raise ValueError("Unknown app: %s" % app_type) if sys.platform == 'darwin' and app_type != 'xulrunner': cmdargs.append('-foreground') if args: cmdargs.extend(shlex.split(args)) # TODO: handle logs on remote device if app_type != "fennec-on-device": # tempfile.gettempdir() was constant, preventing two simultaneous "cfx # run"/"cfx test" on the same host. On unix it points at /tmp (which is # world-writeable), enabling a symlink attack (e.g. imagine some bad guy # does 'ln -s ~/.ssh/id_rsa /tmp/harness_result'). NamedTemporaryFile # gives us a unique filename that fixes both problems. We leave the # (0-byte) file in place until the browser-side code starts writing to # it, otherwise the symlink attack becomes possible again. fileno,resultfile = tempfile.mkstemp(prefix="harness-result-") os.close(fileno) harness_options['resultFile'] = resultfile def maybe_remove_logfile(): if os.path.exists(logfile): os.remove(logfile) logfile_tail = None # We always buffer output through a logfile for two reasons: # 1. On Windows, it's the only way to print console output to stdout/err. # 2. It enables us to keep track of the last time output was emitted, # so we can raise an exception if the test runner hangs. if not logfile: fileno,logfile = tempfile.mkstemp(prefix="harness-log-") os.close(fileno) logfile_tail = follow_file(logfile) atexit.register(maybe_remove_logfile) logfile = os.path.abspath(os.path.expanduser(logfile)) maybe_remove_logfile() env = {} env.update(os.environ) if no_connections: env['MOZ_DISABLE_NONLOCAL_CONNECTIONS'] = '1' env['MOZ_NO_REMOTE'] = '1' env['XPCOM_DEBUG_BREAK'] = 'stack' env.update(extra_environment) if norun: cmdargs.append("-no-remote") # Create the addon XPI so mozrunner will copy it to the profile it creates. # We delete it below after getting mozrunner to create the profile. from cuddlefish.xpi import build_xpi xpi_path = tempfile.mktemp(suffix='cfx-tmp.xpi') build_xpi(template_root_dir=harness_root_dir, manifest=manifest_rdf, xpi_path=xpi_path, harness_options=harness_options, limit_to=used_files, bundle_sdk=bundle_sdk, pkgdir=pkgdir) addons.append(xpi_path) starttime = last_output_time = time.time() # Redirect runner output to a file so we can catch output not generated # by us. # In theory, we could do this using simple redirection on all platforms # other than Windows, but this way we only have a single codepath to # maintain. fileno,outfile = tempfile.mkstemp(prefix="harness-stdout-") os.close(fileno) outfile_tail = follow_file(outfile) def maybe_remove_outfile(): if os.path.exists(outfile): try: os.remove(outfile) except Exception, e: print "Error Cleaning up: " + str(e)