Esempio n. 1
0
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
    )
Esempio n. 2
0
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)
Esempio n. 3
0
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=[])
Esempio n. 4
0
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=[])
Esempio n. 5
0
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)
Esempio n. 6
0
    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)
Esempio n. 7
0
    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)
Esempio n. 8
0
    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))
Esempio n. 9
0
    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))
Esempio n. 10
0
      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,
Esempio n. 11
0
        # 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,
Esempio n. 12
0
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
Esempio n. 13
0
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
Esempio n. 14
0
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
Esempio n. 15
0
    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"
          }'''))
Esempio n. 16
0
        # 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,
Esempio n. 18
0
        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,
Esempio n. 19
0
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
Esempio n. 20
0
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)
Esempio n. 21
0
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
Esempio n. 22
0
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)
Esempio n. 23
0
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
Esempio n. 24
0
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
Esempio n. 25
0
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
Esempio n. 26
0
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)