diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /addon-sdk/source/python-lib/cuddlefish | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'addon-sdk/source/python-lib/cuddlefish')
143 files changed, 6595 insertions, 0 deletions
diff --git a/addon-sdk/source/python-lib/cuddlefish/__init__.py b/addon-sdk/source/python-lib/cuddlefish/__init__.py new file mode 100644 index 000000000..365d96c5e --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py @@ -0,0 +1,959 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import sys +import os +import optparse +import time + +from copy import copy +import simplejson as json +from cuddlefish import packaging +from cuddlefish._version import get_versions + +MOZRUNNER_BIN_NOT_FOUND = 'Mozrunner could not locate your binary' +MOZRUNNER_BIN_NOT_FOUND_HELP = """ +I can't find the application binary in any of its default locations +on your system. Please specify one using the -b/--binary option. +""" + +UPDATE_RDF_FILENAME = "%s.update.rdf" +XPI_FILENAME = "%s.xpi" + +usage = """ +%prog [options] command [command-specific options] + +Supported Commands: + init - create a sample addon in an empty directory + test - run tests + run - run program + xpi - generate an xpi + +Internal Commands: + testcfx - test the cfx tool + testex - test all example code + testpkgs - test all installed packages + testall - test whole environment + +Experimental and internal commands and options are not supported and may be +changed or removed in the future. +""" + +global_options = [ + (("-v", "--verbose",), dict(dest="verbose", + help="enable lots of output", + action="store_true", + default=False)), + ] + +parser_groups = ( + ("Supported Command-Specific Options", [ + (("", "--update-url",), dict(dest="update_url", + help="update URL in install.rdf", + metavar=None, + default=None, + cmds=['xpi'])), + (("", "--update-link",), dict(dest="update_link", + help="generate update.rdf", + metavar=None, + default=None, + cmds=['xpi'])), + (("-p", "--profiledir",), dict(dest="profiledir", + help=("profile directory to pass to " + "app"), + metavar=None, + default=None, + cmds=['test', 'run', 'testex', + 'testpkgs', 'testall'])), + (("-b", "--binary",), dict(dest="binary", + help="path to app binary", + metavar=None, + default=None, + cmds=['test', 'run', 'testex', 'testpkgs', + 'testall'])), + (("", "--binary-args",), dict(dest="cmdargs", + help=("additional arguments passed to the " + "binary"), + metavar=None, + default=None, + cmds=['run', 'test'])), + (("", "--dependencies",), dict(dest="dep_tests", + help="include tests for all deps", + action="store_true", + default=False, + cmds=['test', 'testex', 'testpkgs', + 'testall'])), + (("", "--times",), dict(dest="iterations", + type="int", + help="number of times to run tests", + default=1, + cmds=['test', 'testex', 'testpkgs', + 'testall'])), + (("-f", "--filter",), dict(dest="filter", + help=("only run tests whose filenames " + "match FILENAME and optionally " + "match TESTNAME, both regexps"), + metavar="FILENAME[:TESTNAME]", + default='', + cmds=['test', 'testex', 'testaddons', 'testpkgs', + 'testall'])), + (("-g", "--use-config",), dict(dest="config", + help="use named config from local.json", + metavar=None, + default="default", + cmds=['test', 'run', 'xpi', 'testex', + 'testpkgs', 'testall'])), + (("", "--templatedir",), dict(dest="templatedir", + help="XULRunner app/ext. template", + metavar=None, + default=None, + cmds=['run', 'xpi'])), + (("", "--package-path",), dict(dest="packagepath", action="append", + help="extra directories for package search", + metavar=None, + default=[], + cmds=['run', 'xpi', 'test'])), + (("", "--extra-packages",), dict(dest="extra_packages", + help=("extra packages to include, " + "comma-separated. Default is " + "'addon-sdk'."), + metavar=None, + default="addon-sdk", + cmds=['run', 'xpi', 'test', 'testex', + 'testpkgs', 'testall', + 'testcfx'])), + (("", "--pkgdir",), dict(dest="pkgdir", + help=("package dir containing " + "package.json; default is " + "current directory"), + metavar=None, + default=None, + cmds=['run', 'xpi', 'test'])), + (("", "--static-args",), dict(dest="static_args", + help="extra harness options as JSON", + type="json", + metavar=None, + default="{}", + cmds=['run', 'xpi'])), + (("", "--parseable",), dict(dest="parseable", + help="display test output in a parseable format", + action="store_true", + default=False, + cmds=['run', 'test', 'testex', 'testpkgs', + 'testaddons', 'testall'])), + ] + ), + + ("Experimental Command-Specific Options", [ + (("-a", "--app",), dict(dest="app", + help=("app to run: firefox (default), fennec, " + "fennec-on-device, xulrunner or " + "thunderbird"), + metavar=None, + type="choice", + choices=["firefox", + "fennec-on-device", "thunderbird", + "xulrunner"], + default="firefox", + cmds=['test', 'run', 'testex', 'testpkgs', + 'testall'])), + (("-o", "--overload-modules",), dict(dest="overload_modules", + help=("Overload JS modules integrated into" + " Firefox with the one from your SDK" + " repository"), + action="store_true", + default=False, + cmds=['run', 'test', 'testex', 'testpkgs', + 'testall'])), + (("", "--strip-sdk",), dict(dest="bundle_sdk", + help=("Do not ship SDK modules in the xpi"), + action="store_false", + default=False, + cmds=['run', 'test', 'testex', 'testpkgs', + 'testall', 'xpi'])), + (("", "--force-use-bundled-sdk",), dict(dest="force_use_bundled_sdk", + help=("When --strip-sdk isn't passed, " + "force using sdk modules shipped in " + "the xpi instead of firefox ones"), + action="store_true", + default=False, + cmds=['run', 'test', 'testex', 'testpkgs', + 'testall', 'xpi'])), + (("", "--no-run",), dict(dest="no_run", + help=("Instead of launching the " + "application, just show the command " + "for doing so. Use this to launch " + "the application in a debugger like " + "gdb."), + action="store_true", + default=False, + cmds=['run', 'test'])), + (("", "--no-quit",), dict(dest="no_quit", + help=("Prevent from killing Firefox when" + "running tests"), + action="store_true", + default=False, + cmds=['run', 'test'])), + (("", "--no-strip-xpi",), dict(dest="no_strip_xpi", + help="retain unused modules in XPI", + action="store_true", + default=False, + cmds=['xpi'])), + (("", "--force-mobile",), dict(dest="enable_mobile", + help="Force compatibility with Firefox Mobile", + action="store_true", + default=False, + cmds=['run', 'test', 'xpi', 'testall'])), + (("", "--mobile-app",), dict(dest="mobile_app_name", + help=("Name of your Android application to " + "use. Possible values: 'firefox', " + "'firefox_beta', 'fennec_aurora', " + "'fennec' (for nightly)."), + metavar=None, + default=None, + cmds=['run', 'test', 'testall'])), + (("", "--harness-option",), dict(dest="extra_harness_option_args", + help=("Extra properties added to " + "harness-options.json"), + action="append", + metavar="KEY=VALUE", + default=[], + cmds=['xpi'])), + (("", "--stop-on-error",), dict(dest="stopOnError", + help="Stop running tests after the first failure", + action="store_true", + metavar=None, + default=False, + cmds=['test', 'testex', 'testpkgs'])), + (("", "--check-memory",), dict(dest="check_memory", + help="attempts to detect leaked compartments after a test run", + action="store_true", + default=False, + cmds=['test', 'testpkgs', 'testaddons', + 'testall'])), + (("", "--output-file",), dict(dest="output_file", + help="Where to put the finished .xpi", + default=None, + cmds=['xpi'])), + (("", "--abort-on-missing-module",), dict(dest="abort_on_missing", + help="Abort if required module is missing", + action="store_true", + default=False, + cmds=['test', 'run', 'xpi', 'testpkgs'])), + (("", "--no-connections",), dict(dest="no_connections", + help="disable/enable remote connections (on for cfx run only by default)", + type="choice", + choices=["on", "off", "default"], + default="default", + cmds=['test', 'run', 'testpkgs', + 'testall', 'testaddons', 'testex'])), + ] + ), + + ("Internal Command-Specific Options", [ + (("", "--addons",), dict(dest="addons", + help=("paths of addons to install, " + "comma-separated"), + metavar=None, + default=None, + cmds=['test', 'run', 'testex', 'testpkgs', + 'testall'])), + (("", "--test-runner-pkg",), dict(dest="test_runner_pkg", + help=("name of package " + "containing test runner " + "program (default is " + "test-harness)"), + default="addon-sdk", + cmds=['test', 'testex', 'testpkgs', + 'testall'])), + # --keydir was removed in 1.0b5, but we keep it around in the options + # parser to make life easier for frontends like FlightDeck which + # might still pass it. It can go away once the frontends are updated. + (("", "--keydir",), dict(dest="keydir", + help=("obsolete, ignored"), + metavar=None, + default=None, + cmds=['test', 'run', 'xpi', 'testex', + 'testpkgs', 'testall'])), + (("", "--e10s",), dict(dest="enable_e10s", + help="enable remote windows", + action="store_true", + default=False, + cmds=['test', 'run', 'testex', 'testpkgs', + 'testaddons', 'testcfx', 'testall'])), + (("", "--logfile",), dict(dest="logfile", + help="log console output to file", + metavar=None, + default=None, + cmds=['run', 'test', 'testex', 'testpkgs'])), + # TODO: This should default to true once our memory debugging + # issues are resolved; see bug 592774. + (("", "--profile-memory",), dict(dest="profileMemory", + help=("profile memory usage " + "(default is false)"), + type="int", + action="store", + default=0, + cmds=['test', 'testex', 'testpkgs', + 'testall'])), + ] + ), + ) + +def find_parent_package(cur_dir): + tail = True + while tail: + if os.path.exists(os.path.join(cur_dir, 'package.json')): + return cur_dir + cur_dir, tail = os.path.split(cur_dir) + return None + +def check_json(option, opt, value): + # We return the parsed JSON here; see bug 610816 for background on why. + try: + return json.loads(value) + except ValueError: + raise optparse.OptionValueError("Option %s must be JSON." % opt) + +class CfxOption(optparse.Option): + TYPES = optparse.Option.TYPES + ('json',) + TYPE_CHECKER = copy(optparse.Option.TYPE_CHECKER) + TYPE_CHECKER['json'] = check_json + +def parse_args(arguments, global_options, usage, version, parser_groups, + defaults=None): + parser = optparse.OptionParser(usage=usage.strip(), option_class=CfxOption, + version=version) + + def name_cmp(a, b): + # a[0] = name sequence + # a[0][0] = short name (possibly empty string) + # a[0][1] = long name + names = [] + for seq in (a, b): + names.append(seq[0][0][1:] if seq[0][0] else seq[0][1][2:]) + return cmp(*names) + + global_options.sort(name_cmp) + for names, opts in global_options: + parser.add_option(*names, **opts) + + for group_name, options in parser_groups: + group = optparse.OptionGroup(parser, group_name) + options.sort(name_cmp) + for names, opts in options: + if 'cmds' in opts: + cmds = opts['cmds'] + del opts['cmds'] + cmds.sort() + if not 'help' in opts: + opts['help'] = "" + opts['help'] += " (%s)" % ", ".join(cmds) + group.add_option(*names, **opts) + parser.add_option_group(group) + + if defaults: + parser.set_defaults(**defaults) + + (options, args) = parser.parse_args(args=arguments) + + if not args: + parser.print_help() + parser.exit() + + return (options, args) + +# all tests emit progress messages to stderr, not stdout. (the mozrunner +# console output goes to stderr and is hard to change, and +# unittest.TextTestRunner prefers stderr, so we send everything else there +# too, to keep all the messages in order) + +def test_all(env_root, defaults): + fail = False + + starttime = time.time() + + if not defaults['filter']: + print >>sys.stderr, "Testing cfx..." + sys.stderr.flush() + result = test_cfx(env_root, defaults['verbose']) + if result.failures or result.errors: + fail = True + + if not fail or not defaults.get("stopOnError"): + print >>sys.stderr, "Testing all examples..." + sys.stderr.flush() + + try: + test_all_examples(env_root, defaults) + except SystemExit, e: + fail = (e.code != 0) or fail + + if not fail or not defaults.get("stopOnError"): + print >>sys.stderr, "Testing all unit-test addons..." + sys.stderr.flush() + + try: + test_all_testaddons(env_root, defaults) + except SystemExit, e: + fail = (e.code != 0) or fail + + if not fail or not defaults.get("stopOnError"): + print >>sys.stderr, "Testing all packages..." + sys.stderr.flush() + try: + test_all_packages(env_root, defaults) + except SystemExit, e: + fail = (e.code != 0) or fail + + print >>sys.stderr, "Total time for all tests: %f seconds" % (time.time() - starttime) + + if fail: + print >>sys.stderr, "Some tests were unsuccessful." + sys.exit(1) + print >>sys.stderr, "All tests were successful. Ship it!" + sys.exit(0) + +def test_cfx(env_root, verbose): + import cuddlefish.tests + + # tests write to stderr. flush everything before and after to avoid + # confusion later. + sys.stdout.flush(); sys.stderr.flush() + olddir = os.getcwd() + os.chdir(env_root) + retval = cuddlefish.tests.run(verbose) + os.chdir(olddir) + sys.stdout.flush(); sys.stderr.flush() + return retval + +def test_all_testaddons(env_root, defaults): + addons_dir = os.path.join(env_root, "test", "addons") + addons = [dirname for dirname in os.listdir(addons_dir) + if os.path.isdir(os.path.join(addons_dir, dirname))] + addons.sort() + fail = False + for dirname in addons: + # apply the filter + if (not defaults['filter'].split(":")[0] in dirname): + continue + + print >>sys.stderr, "Testing %s..." % dirname + sys.stderr.flush() + try: + run(arguments=["testrun", + "--pkgdir", + os.path.join(addons_dir, dirname)], + defaults=defaults, + env_root=env_root) + except SystemExit, e: + fail = (e.code != 0) or fail + if fail and defaults.get("stopOnError"): + break + + if fail: + print >>sys.stderr, "Some test addons tests were unsuccessful." + sys.exit(-1) + +def test_all_examples(env_root, defaults): + examples_dir = os.path.join(env_root, "examples") + examples = [dirname for dirname in os.listdir(examples_dir) + if os.path.isdir(os.path.join(examples_dir, dirname))] + examples.sort() + fail = False + for dirname in examples: + if (not defaults['filter'].split(":")[0] in dirname): + continue + + print >>sys.stderr, "Testing %s..." % dirname + sys.stderr.flush() + try: + run(arguments=["test", + "--pkgdir", + os.path.join(examples_dir, dirname)], + defaults=defaults, + env_root=env_root) + except SystemExit, e: + fail = (e.code != 0) or fail + if fail and defaults.get("stopOnError"): + break + + if fail: + print >>sys.stderr, "Some examples tests were unsuccessful." + sys.exit(-1) + +def test_all_packages(env_root, defaults): + packages_dir = os.path.join(env_root, "packages") + if os.path.isdir(packages_dir): + packages = [dirname for dirname in os.listdir(packages_dir) + if os.path.isdir(os.path.join(packages_dir, dirname))] + else: + packages = [] + packages.append(env_root) + packages.sort() + print >>sys.stderr, "Testing all available packages: %s." % (", ".join(packages)) + sys.stderr.flush() + fail = False + for dirname in packages: + print >>sys.stderr, "Testing %s..." % dirname + sys.stderr.flush() + try: + run(arguments=["test", + "--pkgdir", + os.path.join(packages_dir, dirname)], + defaults=defaults, + env_root=env_root) + except SystemExit, e: + fail = (e.code != 0) or fail + if fail and defaults.get('stopOnError'): + break + if fail: + print >>sys.stderr, "Some package tests were unsuccessful." + sys.exit(-1) + +def get_config_args(name, env_root): + local_json = os.path.join(env_root, "local.json") + if not (os.path.exists(local_json) and + os.path.isfile(local_json)): + if name == "default": + return [] + else: + print >>sys.stderr, "File does not exist: %s" % local_json + sys.exit(1) + local_json = packaging.load_json_file(local_json) + if 'configs' not in local_json: + print >>sys.stderr, "'configs' key not found in local.json." + sys.exit(1) + if name not in local_json.configs: + if name == "default": + return [] + else: + print >>sys.stderr, "No config found for '%s'." % name + sys.exit(1) + config = local_json.configs[name] + if type(config) != list: + print >>sys.stderr, "Config for '%s' must be a list of strings." % name + sys.exit(1) + return config + +def initializer(env_root, args, out=sys.stdout, err=sys.stderr): + from templates import PACKAGE_JSON, TEST_MAIN_JS + from preflight import create_jid + path = os.getcwd() + addon = os.path.basename(path) + # if more than two arguments + if len(args) > 2: + print >>err, 'Too many arguments.' + return {"result":1} + if len(args) == 2: + path = os.path.join(path,args[1]) + try: + os.mkdir(path) + print >>out, '*', args[1], 'package directory created' + except OSError: + print >>out, '*', args[1], 'already exists, testing if directory is empty' + # avoid clobbering existing files, but we tolerate things like .git + existing = [fn for fn in os.listdir(path) if not fn.startswith(".")] + if existing: + print >>err, 'This command must be run in an empty directory.' + return {"result":1} + for d in ['lib','data','test']: + os.mkdir(os.path.join(path,d)) + print >>out, '*', d, 'directory created' + jid = create_jid() + print >>out, '* generated jID automatically:', jid + open(os.path.join(path,'package.json'),'w').write(PACKAGE_JSON % {'name':addon.lower(), + 'title':addon, + 'id':jid }) + print >>out, '* package.json written' + open(os.path.join(path,'test','test-main.js'),'w').write(TEST_MAIN_JS) + print >>out, '* test/test-main.js written' + open(os.path.join(path,'lib','main.js'),'w').write('') + print >>out, '* lib/main.js written' + if len(args) == 1: + print >>out, '\nYour sample add-on is now ready.' + print >>out, 'Do "cfx test" to test it and "cfx run" to try it. Have fun!' + else: + print >>out, '\nYour sample add-on is now ready in the \'' + args[1] + '\' directory.' + print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it. Have fun!' + return {"result":0, "jid":jid} + +def buildJID(target_cfg): + if "id" in target_cfg: + jid = target_cfg["id"] + else: + import uuid + jid = str(uuid.uuid4()) + if not ("@" in jid or jid.startswith("{")): + jid = jid + "@jetpack" + return jid + +def run(arguments=sys.argv[1:], target_cfg=None, pkg_cfg=None, + defaults=None, env_root=os.environ.get('CUDDLEFISH_ROOT'), + stdout=sys.stdout): + versions = get_versions() + sdk_version = versions["version"] + display_version = "Add-on SDK %s (%s)" % (sdk_version, versions["full"]) + parser_kwargs = dict(arguments=arguments, + global_options=global_options, + parser_groups=parser_groups, + usage=usage, + version=display_version, + 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 == "testpkgs": + test_all_packages(env_root, defaults=options.__dict__) + return + elif command == "testaddons": + test_all_testaddons(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": + if options.filter: + print >>sys.stderr, "The filter option is not valid with the testcfx command" + return + test_cfx(env_root, options.verbose) + return + elif command not in ["xpi", "test", "run", "testrun"]: + print >>sys.stderr, "Unknown command: %s" % command + print >>sys.stderr, "Try using '--help' for assistance." + sys.exit(1) + + 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 + inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory', + 'no_quit', 'abort_on_missing'] + enforce_timeouts = False + + if command == "xpi": + use_main = True + elif command == "test": + if 'tests' not in target_cfg: + target_cfg['tests'] = [] + inherited_options.extend(['iterations', 'filter', 'profileMemory', + 'stopOnError']) + enforce_timeouts = True + elif command == "run": + use_main = True + elif command == "testrun": + use_main = True + enforce_timeouts = True + else: + assert 0, "shouldn't get here" + + 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 + + # TODO: Consider keeping a cache of dynamic UUIDs, based + # on absolute filesystem pathname, in the root directory + # or something. + if command in ('xpi', 'run', 'testrun'): + 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" + + jid = buildJID(target_cfg) + + 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, ModuleNotFoundError, \ + BadChromeMarkerError + # 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 path 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 == "addon-sdk" + assert pkg_cfg.packages["addon-sdk"].loader == "lib/sdk/loader/cuddlefish.js" + cuddlefish_js_path = os.path.join(pkg_cfg.packages["addon-sdk"].root_dir, + "lib", "sdk", "loader", "cuddlefish.js") + loader_modules = [("addon-sdk", "lib", "sdk/loader/cuddlefish", cuddlefish_js_path)] + scan_tests = command == "test" + + try: + manifest = build_manifest(target_cfg, pkg_cfg, deps, scan_tests, + None, loader_modules, + abort_on_missing=options.abort_on_missing) + except ModuleNotFoundError, e: + print str(e) + sys.exit(1) + except BadChromeMarkerError, e: + # An error had already been displayed on stderr in manifest code + sys.exit(1) + 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, + include_dep_tests=options.dep_tests, + is_running_tests=(command == "test") + ) + + harness_options = { + 'jetpackID': jid, + 'staticArgs': options.static_args, + 'name': target, + } + + harness_options.update(build) + + # When cfx is run from sdk root directory, we will strip sdk modules and + # override them with local modules. + # So that integration tools will continue to work and use local modules + if os.getcwd() == env_root: + options.bundle_sdk = True + options.force_use_bundled_sdk = False + options.overload_modules = True + + if options.pkgdir == env_root: + options.bundle_sdk = True + options.overload_modules = True + + extra_environment = {} + if command == "test": + # This should be contained in the test runner package. + harness_options['main'] = 'sdk/test/runner' + harness_options['mainPath'] = 'sdk/test/runner' + else: + harness_options['main'] = target_cfg.get('main') + harness_options['mainPath'] = manifest.top_path + extra_environment["CFX_COMMAND"] = command + + for option in inherited_options: + harness_options[option] = getattr(options, option) + + harness_options['metadata'] = packaging.get_metadata(pkg_cfg, used_deps) + + 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) + elif os.path.exists(os.path.join(options.pkgdir, "app-extension")): + app_extension_dir = os.path.join(options.pkgdir, "app-extension") + else: + mydir = os.path.dirname(os.path.abspath(__file__)) + app_extension_dir = os.path.join(mydir, "../../app-extension") + + # Do not add entries for SDK modules + harness_options['manifest'] = manifest.get_harness_options_manifest(False) + + # Gives an hint to tell if sdk modules are bundled or not + harness_options['is-sdk-bundled'] = options.bundle_sdk or options.no_strip_xpi + + if options.force_use_bundled_sdk: + if not harness_options['is-sdk-bundled']: + print >>sys.stderr, ("--force-use-bundled-sdk " + "can't be used if sdk isn't bundled.") + sys.exit(1) + if options.overload_modules: + print >>sys.stderr, ("--force-use-bundled-sdk and --overload-modules " + "can't be used at the same time.") + sys.exit(1) + # Pass a flag in order to force using sdk modules shipped in the xpi + harness_options['force-use-bundled-sdk'] = True + + from cuddlefish.rdf import gen_manifest, RDFUpdate + + manifest_rdf = gen_manifest(template_root_dir=app_extension_dir, + target_cfg=target_cfg, + jid=jid, + update_url=options.update_url, + bootstrap=True, + enable_mobile=options.enable_mobile) + + if command == "xpi" and options.update_link: + if not options.update_link.startswith("https"): + raise optparse.OptionValueError("--update-link must start with 'https': %s" % 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 = None + if command == "xpi": + used_files = set(manifest.get_used_files(options.bundle_sdk)) + + if options.no_strip_xpi: + used_files = None # disables the filter, includes all files + + if command == 'xpi': + from cuddlefish.xpi import build_xpi + # 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.no_connections == "default": + if command == "run": + no_connections = False + else: + no_connections = True + elif options.no_connections == "on": + no_connections = True + else: + no_connections = False + + 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) + + try: + retval = run_app(harness_root_dir=app_extension_dir, + manifest_rdf=manifest_rdf, + harness_options=harness_options, + app_type=options.app, + binary=options.binary, + profiledir=options.profiledir, + verbose=options.verbose, + parseable=options.parseable, + enforce_timeouts=enforce_timeouts, + logfile=options.logfile, + addons=options.addons, + args=options.cmdargs, + extra_environment=extra_environment, + norun=options.no_run, + noquit=options.no_quit, + used_files=used_files, + enable_mobile=options.enable_mobile, + mobile_app_name=options.mobile_app_name, + env_root=env_root, + is_running_tests=(command == "test"), + overload_modules=options.overload_modules, + bundle_sdk=options.bundle_sdk, + pkgdir=options.pkgdir, + enable_e10s=enable_e10s, + no_connections=no_connections) + except ValueError, e: + print "" + print "A given cfx option has an inappropriate value:" + print >>sys.stderr, " " + " \n ".join(str(e).split("\n")) + retval = -1 + except Exception, e: + if str(e).startswith(MOZRUNNER_BIN_NOT_FOUND): + print >>sys.stderr, MOZRUNNER_BIN_NOT_FOUND_HELP.strip() + retval = -1 + else: + raise + sys.exit(retval) diff --git a/addon-sdk/source/python-lib/cuddlefish/_version.py b/addon-sdk/source/python-lib/cuddlefish/_version.py new file mode 100644 index 000000000..6f12874d2 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/_version.py @@ -0,0 +1,174 @@ + +# This file helps to compute a version number in source trees obtained from +# git-archive tarball (such as those provided by githubs download-from-tag +# feature). Distribution tarballs (build by setup.py sdist) and build +# directories (produced by setup.py build) will contain a much shorter file +# that just contains the computed version number. + +# This file is released into the public domain. Generated by versioneer-0.6 +# (https://github.com/warner/python-versioneer) + +# these strings will be replaced by git during git-archive +git_refnames = "$Format:%d$" +git_full = "$Format:%H$" + + +import subprocess + +def run_command(args, cwd=None, verbose=False): + try: + # remember shell=False, so use git.cmd on windows, not just git + p = subprocess.Popen(args, stdout=subprocess.PIPE, cwd=cwd) + except EnvironmentError, e: + if verbose: + print "unable to run %s" % args[0] + print e + return None + stdout = p.communicate()[0].strip() + if p.returncode != 0: + if verbose: + print "unable to run %s (error)" % args[0] + return None + return stdout + + +import sys +import re +import os.path + +def get_expanded_variables(versionfile_source): + """ + the code embedded in _version.py can just fetch the value of these + variables. When used from setup.py, we don't want to import + _version.py, so we do it with a regexp instead. This function is not + used from _version.py. + """ + variables = {} + try: + for line in open(versionfile_source,"r").readlines(): + if line.strip().startswith("git_refnames ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + variables["refnames"] = mo.group(1) + if line.strip().startswith("git_full ="): + mo = re.search(r'=\s*"(.*)"', line) + if mo: + variables["full"] = mo.group(1) + except EnvironmentError: + pass + return variables + +def versions_from_expanded_variables(variables, tag_prefix): + refnames = variables["refnames"].strip() + if refnames.startswith("$Format"): + return {} # unexpanded, so not in an unpacked git-archive tarball + refs = set([r.strip() for r in refnames.strip("()").split(",")]) + for ref in list(refs): + if not re.search(r'\d', ref): + refs.discard(ref) + # Assume all version tags have a digit. git's %d expansion + # behaves like git log --decorate=short and strips out the + # refs/heads/ and refs/tags/ prefixes that would let us + # distinguish between branches and tags. By ignoring refnames + # without digits, we filter out many common branch names like + # "release" and "stabilization", as well as "HEAD" and "master". + for ref in sorted(refs): + # sorting will prefer e.g. "2.0" over "2.0rc1" + if ref.startswith(tag_prefix): + r = ref[len(tag_prefix):] + return { "version": r, + "full": variables["full"].strip() } + # no suitable tags, so we use the full revision id + return { "version": variables["full"].strip(), + "full": variables["full"].strip() } + +def versions_from_vcs(tag_prefix, versionfile_source, verbose=False): + """ + this runs 'git' from the root of the source tree. That either means + someone ran a setup.py command (and this code is in versioneer.py, thus + the containing directory is the root of the source tree), or someone + ran a project-specific entry point (and this code is in _version.py, + thus the containing directory is somewhere deeper in the source tree). + This only gets called if the git-archive 'subst' variables were *not* + expanded, and _version.py hasn't already been rewritten with a short + version string, meaning we're inside a checked out source tree. + """ + try: + here = os.path.abspath(__file__) + except NameError: + # some py2exe/bbfreeze/non-CPython implementations don't do __file__ + return {} # not always correct + + # versionfile_source is the relative path from the top of the source tree + # (where the .git directory might live) to this file. Invert this to find + # the root from __file__. + root = here + for i in range(len(versionfile_source.split("/"))): + root = os.path.dirname(root) + if not os.path.exists(os.path.join(root, ".git")): + return {} + + GIT = "git" + if sys.platform == "win32": + GIT = "git.cmd" + stdout = run_command([GIT, "describe", "--tags", "--dirty", "--always"], + cwd=root) + if stdout is None: + return {} + if not stdout.startswith(tag_prefix): + if verbose: + print "tag '%s' doesn't start with prefix '%s'" % (stdout, tag_prefix) + return {} + tag = stdout[len(tag_prefix):] + stdout = run_command([GIT, "rev-parse", "HEAD"], cwd=root) + if stdout is None: + return {} + full = stdout.strip() + if tag.endswith("-dirty"): + full += "-dirty" + return {"version": tag, "full": full} + + +def versions_from_parentdir(parentdir_prefix, versionfile_source, verbose=False): + try: + here = os.path.abspath(__file__) + # versionfile_source is the relative path from the top of the source + # tree (where the .git directory might live) to _version.py, when + # this is used by the runtime. Invert this to find the root from + # __file__. + root = here + for i in range(len(versionfile_source.split("/"))): + root = os.path.dirname(root) + except NameError: + # try a couple different things to handle py2exe, bbfreeze, and + # non-CPython implementations which don't do __file__. This code + # either lives in versioneer.py (used by setup.py) or _version.py + # (used by the runtime). In the versioneer.py case, sys.argv[0] will + # be setup.py, in the root of the source tree. In the _version.py + # case, we have no idea what sys.argv[0] is (some + # application-specific runner). + root = os.path.dirname(os.path.abspath(sys.argv[0])) + # Source tarballs conventionally unpack into a directory that includes + # both the project name and a version string. + dirname = os.path.basename(root) + if not dirname.startswith(parentdir_prefix): + if verbose: + print "dirname '%s' doesn't start with prefix '%s'" % (dirname, parentdir_prefix) + return None + return {"version": dirname[len(parentdir_prefix):], "full": ""} + +tag_prefix = "" +parentdir_prefix = "addon-sdk-" +versionfile_source = "python-lib/cuddlefish/_version.py" + +def get_versions(): + variables = { "refnames": git_refnames, "full": git_full } + ver = versions_from_expanded_variables(variables, tag_prefix) + if not ver: + ver = versions_from_vcs(tag_prefix, versionfile_source) + if not ver: + ver = versions_from_parentdir(parentdir_prefix, versionfile_source) + if not ver: + ver = {"version": "unknown", "full": ""} + return ver + diff --git a/addon-sdk/source/python-lib/cuddlefish/bunch.py b/addon-sdk/source/python-lib/cuddlefish/bunch.py new file mode 100644 index 000000000..5efa79f16 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/bunch.py @@ -0,0 +1,34 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Taken from Paver's paver.options module. + +class Bunch(dict): + """A dictionary that provides attribute-style access.""" + + def __repr__(self): + keys = self.keys() + keys.sort() + args = ', '.join(['%s=%r' % (key, self[key]) for key in keys]) + return '%s(%s)' % (self.__class__.__name__, args) + + def __getitem__(self, key): + item = dict.__getitem__(self, key) + if callable(item): + return item() + return item + + def __getattr__(self, name): + try: + return self[name] + except KeyError: + raise AttributeError(name) + + __setattr__ = dict.__setitem__ + + def __delattr__(self, name): + try: + del self[name] + except KeyError: + raise AttributeError(name) diff --git a/addon-sdk/source/python-lib/cuddlefish/manifest.py b/addon-sdk/source/python-lib/cuddlefish/manifest.py new file mode 100644 index 000000000..e9913be7b --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/manifest.py @@ -0,0 +1,807 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +import os, sys, re, hashlib +import simplejson as json +SEP = os.path.sep +from cuddlefish.util import filter_filenames, filter_dirnames + +# Load new layout mapping hashtable +path = os.path.join(os.environ.get('CUDDLEFISH_ROOT'), "mapping.json") +data = open(path, 'r').read() +NEW_LAYOUT_MAPPING = json.loads(data) + +def js_zipname(packagename, modulename): + return "%s-lib/%s.js" % (packagename, modulename) +def docs_zipname(packagename, modulename): + return "%s-docs/%s.md" % (packagename, modulename) +def datamap_zipname(packagename): + return "%s-data.json" % packagename +def datafile_zipname(packagename, datapath): + return "%s-data/%s" % (packagename, datapath) + +def to_json(o): + return json.dumps(o, indent=1).encode("utf-8")+"\n" + +class ModuleNotFoundError(Exception): + def __init__(self, requirement_type, requirement_name, + used_by, line_number, looked_in): + Exception.__init__(self) + self.requirement_type = requirement_type # "require" or "define" + self.requirement_name = requirement_name # string, what they require()d + self.used_by = used_by # string, full path to module which did require() + self.line_number = line_number # int, 1-indexed line number of first require() + self.looked_in = looked_in # list of full paths to potential .js files + def __str__(self): + what = "%s(%s)" % (self.requirement_type, self.requirement_name) + where = self.used_by + if self.line_number is not None: + where = "%s:%d" % (self.used_by, self.line_number) + searched = "Looked for it in:\n %s\n" % "\n ".join(self.looked_in) + return ("ModuleNotFoundError: unable to satisfy: %s from\n" + " %s:\n" % (what, where)) + searched + +class BadModuleIdentifier(Exception): + pass +class BadSection(Exception): + pass +class UnreachablePrefixError(Exception): + pass + +class ManifestEntry: + def __init__(self): + self.docs_filename = None + self.docs_hash = None + self.requirements = {} + self.datamap = None + + def get_path(self): + name = self.moduleName + + if name.endswith(".js"): + name = name[:-3] + items = [] + # Only add package name for addons, so that system module paths match + # the path from the commonjs root directory and also match the loader + # mappings. + if self.packageName != "addon-sdk": + items.append(self.packageName) + # And for the same reason, do not append `lib/`. + if self.sectionName == "tests": + items.append(self.sectionName) + items.append(name) + + return "/".join(items) + + def get_entry_for_manifest(self): + entry = { "packageName": self.packageName, + "sectionName": self.sectionName, + "moduleName": self.moduleName, + "jsSHA256": self.js_hash, + "docsSHA256": self.docs_hash, + "requirements": {}, + } + for req in self.requirements: + if isinstance(self.requirements[req], ManifestEntry): + them = self.requirements[req] # this is another ManifestEntry + entry["requirements"][req] = them.get_path() + else: + # something magic. The manifest entry indicates that they're + # allowed to require() it + entry["requirements"][req] = self.requirements[req] + assert isinstance(entry["requirements"][req], unicode) or \ + isinstance(entry["requirements"][req], str) + return entry + + def add_js(self, js_filename): + self.js_filename = js_filename + self.js_hash = hash_file(js_filename) + def add_docs(self, docs_filename): + self.docs_filename = docs_filename + self.docs_hash = hash_file(docs_filename) + def add_requirement(self, reqname, reqdata): + self.requirements[reqname] = reqdata + def add_data(self, datamap): + self.datamap = datamap + + def get_js_zipname(self): + return js_zipname(self.packagename, self.modulename) + def get_docs_zipname(self): + if self.docs_hash: + return docs_zipname(self.packagename, self.modulename) + return None + # self.js_filename + # self.docs_filename + + +def hash_file(fn): + return hashlib.sha256(open(fn,"rb").read()).hexdigest() + +def get_datafiles(datadir): + """ + yields pathnames relative to DATADIR, ignoring some files + """ + for dirpath, dirnames, filenames in os.walk(datadir): + filenames = list(filter_filenames(filenames)) + # this tells os.walk to prune the search + dirnames[:] = filter_dirnames(dirnames) + for filename in filenames: + fullname = os.path.join(dirpath, filename) + assert fullname.startswith(datadir+SEP), "%s%s not in %s" % (datadir, SEP, fullname) + yield fullname[len(datadir+SEP):] + + +class DataMap: + # one per package + def __init__(self, pkg): + self.pkg = pkg + self.name = pkg.name + self.files_to_copy = [] + datamap = {} + datadir = os.path.join(pkg.root_dir, "data") + for dataname in get_datafiles(datadir): + absname = os.path.join(datadir, dataname) + zipname = datafile_zipname(pkg.name, dataname) + datamap[dataname] = hash_file(absname) + self.files_to_copy.append( (zipname, absname) ) + self.data_manifest = to_json(datamap) + self.data_manifest_hash = hashlib.sha256(self.data_manifest).hexdigest() + self.data_manifest_zipname = datamap_zipname(pkg.name) + self.data_uri_prefix = "%s/data/" % (self.name) + +class BadChromeMarkerError(Exception): + pass + +class ModuleInfo: + def __init__(self, package, section, name, js, docs): + self.package = package + self.section = section + self.name = name + self.js = js + self.docs = docs + + def __hash__(self): + return hash( (self.package.name, self.section, self.name, + self.js, self.docs) ) + def __eq__(self, them): + if them.__class__ is not self.__class__: + return False + if ((them.package.name, them.section, them.name, them.js, them.docs) != + (self.package.name, self.section, self.name, self.js, self.docs) ): + return False + return True + + def __repr__(self): + return "ModuleInfo [%s %s %s] (%s, %s)" % (self.package.name, + self.section, + self.name, + self.js, self.docs) + +class ManifestBuilder: + def __init__(self, target_cfg, pkg_cfg, deps, extra_modules, + stderr=sys.stderr, abort_on_missing=False): + self.manifest = {} # maps (package,section,module) to ManifestEntry + self.target_cfg = target_cfg # the entry point + self.pkg_cfg = pkg_cfg # all known packages + self.deps = deps # list of package names to search + self.used_packagenames = set() + self.stderr = stderr + self.extra_modules = extra_modules + self.modules = {} # maps ModuleInfo to URI in self.manifest + self.datamaps = {} # maps package name to DataMap instance + self.files = [] # maps manifest index to (absfn,absfn) js/docs pair + self.test_modules = [] # for runtime + self.abort_on_missing = abort_on_missing # cfx eol + + def build(self, scan_tests, test_filter_re): + """ + process the top module, which recurses to process everything it reaches + """ + if "main" in self.target_cfg: + top_mi = self.find_top(self.target_cfg) + top_me = self.process_module(top_mi) + self.top_path = top_me.get_path() + self.datamaps[self.target_cfg.name] = DataMap(self.target_cfg) + if scan_tests: + mi = self._find_module_in_package("addon-sdk", "lib", "sdk/test/runner", []) + self.process_module(mi) + # also scan all test files in all packages that we use. By making + # a copy of self.used_packagenames first, we refrain from + # processing tests in packages that our own tests depend upon. If + # we're running tests for package A, and either modules in A or + # tests in A depend upon modules from package B, we *don't* want + # to run tests for package B. + test_modules = [] + dirnames = self.target_cfg["tests"] + if isinstance(dirnames, basestring): + dirnames = [dirnames] + dirnames = [os.path.join(self.target_cfg.root_dir, d) + for d in dirnames] + for d in dirnames: + for filename in os.listdir(d): + if filename.startswith("test-") and filename.endswith(".js"): + testname = filename[:-3] # require(testname) + if test_filter_re: + if not re.search(test_filter_re, testname): + continue + tmi = ModuleInfo(self.target_cfg, "tests", testname, + os.path.join(d, filename), None) + # scan the test's dependencies + tme = self.process_module(tmi) + test_modules.append( (testname, tme) ) + # also add it as an artificial dependency of unit-test-finder, so + # the runtime dynamic load can work. + test_finder = self.get_manifest_entry("addon-sdk", "lib", + "sdk/deprecated/unit-test-finder") + for (testname,tme) in test_modules: + test_finder.add_requirement(testname, tme) + # finally, tell the runtime about it, so they won't have to + # search for all tests. self.test_modules will be passed + # through the harness-options.json file in the + # .allTestModules property. + # Pass the absolute module path. + self.test_modules.append(tme.get_path()) + + # include files used by the loader + for em in self.extra_modules: + (pkgname, section, modname, js) = em + mi = ModuleInfo(self.pkg_cfg.packages[pkgname], section, modname, + js, None) + self.process_module(mi) + + + def get_module_entries(self): + return frozenset(self.manifest.values()) + def get_data_entries(self): + return frozenset(self.datamaps.values()) + + def get_used_packages(self): + used = set() + for index in self.manifest: + (package, section, module) = index + used.add(package) + return sorted(used) + + def get_used_files(self, bundle_sdk_modules): + """ + returns all .js files that we reference, plus data/ files. You will + need to add the loader, off-manifest files that it needs, and + generated metadata. + """ + for datamap in self.datamaps.values(): + for (zipname, absname) in datamap.files_to_copy: + yield absname + + for me in self.get_module_entries(): + # Only ship SDK files if we are told to do so + if me.packageName != "addon-sdk" or bundle_sdk_modules: + yield me.js_filename + + def get_harness_options_manifest(self, bundle_sdk_modules): + manifest = {} + for me in self.get_module_entries(): + path = me.get_path() + # Do not add manifest entries for system modules. + # Doesn't prevent from shipping modules. + # Shipping modules is decided in `get_used_files`. + if me.packageName != "addon-sdk" or bundle_sdk_modules: + manifest[path] = me.get_entry_for_manifest() + return manifest + + def get_manifest_entry(self, package, section, module): + index = (package, section, module) + if index not in self.manifest: + m = self.manifest[index] = ManifestEntry() + m.packageName = package + m.sectionName = section + m.moduleName = module + self.used_packagenames.add(package) + return self.manifest[index] + + def uri_name_from_path(self, pkg, fn): + # given a filename like .../pkg1/lib/bar/foo.js, and a package + # specification (with a .root_dir like ".../pkg1" and a .lib list of + # paths where .lib[0] is like "lib"), return the appropriate NAME + # that can be put into a URI like resource://JID-pkg1-lib/NAME . This + # will throw an exception if the file is outside of the lib/ + # directory, since that means we can't construct a URI that points to + # it. + # + # This should be a lot easier, and shouldn't fail when the file is in + # the root of the package. Both should become possible when the XPI + # is rearranged and our URI scheme is simplified. + fn = os.path.abspath(fn) + pkglib = pkg.lib[0] + libdir = os.path.abspath(os.path.join(pkg.root_dir, pkglib)) + # AARGH, section and name! we need to reverse-engineer a + # ModuleInfo instance that will produce a URI (in the form + # PREFIX/PKGNAME-SECTION/JS) that will map to the existing file. + # Until we fix URI generation to get rid of "sections", this is + # limited to files in the same .directories.lib as the rest of + # the package uses. So if the package's main files are in lib/, + # but the main.js is in the package root, there is no URI we can + # construct that will point to it, and we must fail. + # + # This will become much easier (and the failure case removed) + # when we get rid of sections and change the URIs to look like + # (PREFIX/PKGNAME/PATH-TO-JS). + + # AARGH 2, allowing .lib to be a list is really getting in the + # way. That needs to go away eventually too. + if not fn.startswith(libdir): + raise UnreachablePrefixError("Sorry, but the 'main' file (%s) in package %s is outside that package's 'lib' directory (%s), so I cannot construct a URI to reach it." + % (fn, pkg.name, pkglib)) + name = fn[len(libdir):].lstrip(SEP)[:-len(".js")] + return name + + + def parse_main(self, root_dir, main, check_lib_dir=None): + # 'main' can be like one of the following: + # a: ./lib/main.js b: ./lib/main c: lib/main + # we require it to be a path to the file, though, and ignore the + # .directories stuff. So just "main" is insufficient if you really + # want something in a "lib/" subdirectory. + if main.endswith(".js"): + main = main[:-len(".js")] + if main.startswith("./"): + main = main[len("./"):] + # package.json must always use "/", but on windows we'll replace that + # with "\" before using it as an actual filename + main = os.sep.join(main.split("/")) + paths = [os.path.join(root_dir, main+".js")] + if check_lib_dir is not None: + paths.append(os.path.join(root_dir, check_lib_dir, main+".js")) + return paths + + def find_top_js(self, target_cfg): + for libdir in target_cfg.lib: + for n in self.parse_main(target_cfg.root_dir, target_cfg.main, + libdir): + if os.path.exists(n): + return n + raise KeyError("unable to find main module '%s.js' in top-level package" % target_cfg.main) + + def find_top(self, target_cfg): + top_js = self.find_top_js(target_cfg) + n = os.path.join(target_cfg.root_dir, "README.md") + if os.path.exists(n): + top_docs = n + else: + top_docs = None + name = self.uri_name_from_path(target_cfg, top_js) + return ModuleInfo(target_cfg, "lib", name, top_js, top_docs) + + def process_module(self, mi): + pkg = mi.package + #print "ENTERING", pkg.name, mi.name + # mi.name must be fully-qualified + assert (not mi.name.startswith("./") and + not mi.name.startswith("../")) + # create and claim the manifest row first + me = self.get_manifest_entry(pkg.name, mi.section, mi.name) + + me.add_js(mi.js) + if mi.docs: + me.add_docs(mi.docs) + + js_lines = open(mi.js,"r").readlines() + requires, problems, locations = scan_module(mi.js,js_lines,self.stderr) + if problems: + # the relevant instructions have already been written to stderr + raise BadChromeMarkerError() + + # We update our requirements on the way out of the depth-first + # traversal of the module graph + + for reqname in sorted(requires.keys()): + # If requirement is chrome or a pseudo-module (starts with @) make + # path a requirement name. + if reqname == "chrome" or reqname.startswith("@"): + me.add_requirement(reqname, reqname) + else: + # when two modules require() the same name, do they get a + # shared instance? This is a deep question. For now say yes. + + # find_req_for() returns an entry to put in our + # 'requirements' dict, and will recursively process + # everything transitively required from here. It will also + # populate the self.modules[] cache. Note that we must + # tolerate cycles in the reference graph. + looked_in = [] # populated by subroutines + them_me = self.find_req_for(mi, reqname, looked_in, locations) + if them_me is None: + if mi.section == "tests": + # tolerate missing modules in tests, because + # test-securable-module.js, and the modules/red.js + # that it imports, both do that intentionally + continue + if reqname.endswith(".jsm"): + # ignore JSM modules + continue + if not self.abort_on_missing: + # print a warning, but tolerate missing modules + # unless cfx --abort-on-missing-module flag was set + print >>self.stderr, "Warning: missing module: %s" % reqname + me.add_requirement(reqname, reqname) + continue + lineno = locations.get(reqname) # None means define() + if lineno is None: + reqtype = "define" + else: + reqtype = "require" + err = ModuleNotFoundError(reqtype, reqname, + mi.js, lineno, looked_in) + raise err + else: + me.add_requirement(reqname, them_me) + + return me + #print "LEAVING", pkg.name, mi.name + + def find_req_for(self, from_module, reqname, looked_in, locations): + # handle a single require(reqname) statement from from_module . + # Return a uri that exists in self.manifest + # Populate looked_in with places we looked. + def BAD(msg): + return BadModuleIdentifier(msg + " in require(%s) from %s" % + (reqname, from_module)) + + if not reqname: + raise BAD("no actual modulename") + + # Allow things in tests/*.js to require both test code and real code. + # But things in lib/*.js can only require real code. + if from_module.section == "tests": + lookfor_sections = ["tests", "lib"] + elif from_module.section == "lib": + lookfor_sections = ["lib"] + else: + raise BadSection(from_module.section) + modulename = from_module.name + + #print " %s require(%s))" % (from_module, reqname) + + if reqname.startswith("./") or reqname.startswith("../"): + # 1: they want something relative to themselves, always from + # their own package + them = modulename.split("/")[:-1] + bits = reqname.split("/") + while bits[0] in (".", ".."): + if not bits: + raise BAD("no actual modulename") + if bits[0] == "..": + if not them: + raise BAD("too many ..") + them.pop() + bits.pop(0) + bits = them+bits + lookfor_pkg = from_module.package.name + lookfor_mod = "/".join(bits) + return self._get_module_from_package(lookfor_pkg, + lookfor_sections, lookfor_mod, + looked_in) + + # non-relative import. Might be a short name (requiring a search + # through "library" packages), or a fully-qualified one. + + if "/" in reqname: + # 2: PKG/MOD: find PKG, look inside for MOD + bits = reqname.split("/") + lookfor_pkg = bits[0] + lookfor_mod = "/".join(bits[1:]) + mi = self._get_module_from_package(lookfor_pkg, + lookfor_sections, lookfor_mod, + looked_in) + if mi: # caution, 0==None + return mi + else: + # 3: try finding PKG, if found, use its main.js entry point + lookfor_pkg = reqname + mi = self._get_entrypoint_from_package(lookfor_pkg, looked_in) + if mi: + return mi + + # 4: search packages for MOD or MODPARENT/MODCHILD. We always search + # their own package first, then the list of packages defined by their + # .dependencies list + from_pkg = from_module.package.name + mi = self._search_packages_for_module(from_pkg, + lookfor_sections, reqname, + looked_in) + if mi: + return mi + + # Only after we look for module in the addon itself, search for a module + # in new layout. + # First normalize require argument in order to easily find a mapping + normalized = reqname + if normalized.endswith(".js"): + normalized = normalized[:-len(".js")] + if normalized.startswith("addon-kit/"): + normalized = normalized[len("addon-kit/"):] + if normalized.startswith("api-utils/"): + normalized = normalized[len("api-utils/"):] + if normalized in NEW_LAYOUT_MAPPING: + # get the new absolute path for this module + original_reqname = reqname + reqname = NEW_LAYOUT_MAPPING[normalized] + from_pkg = from_module.package.name + + # If the addon didn't explicitely told us to ignore deprecated + # require path, warn the developer: + # (target_cfg is the package.json file) + if not "ignore-deprecated-path" in self.target_cfg: + lineno = locations.get(original_reqname) + print >>self.stderr, "Warning: Use of deprecated require path:" + print >>self.stderr, " In %s:%d:" % (from_module.js, lineno) + print >>self.stderr, " require('%s')." % original_reqname + print >>self.stderr, " New path should be:" + print >>self.stderr, " require('%s')" % reqname + + return self._search_packages_for_module(from_pkg, + lookfor_sections, reqname, + looked_in) + else: + # We weren't able to find this module, really. + return None + + def _handle_module(self, mi): + if not mi: + return None + + # we tolerate cycles in the reference graph, which means we need to + # populate the self.modules cache before recursing into + # process_module() . We must also check the cache first, so recursion + # can terminate. + if mi in self.modules: + return self.modules[mi] + + # this creates the entry + new_entry = self.get_manifest_entry(mi.package.name, mi.section, mi.name) + # and populates the cache + self.modules[mi] = new_entry + self.process_module(mi) + return new_entry + + def _get_module_from_package(self, pkgname, sections, modname, looked_in): + if pkgname not in self.pkg_cfg.packages: + return None + mi = self._find_module_in_package(pkgname, sections, modname, + looked_in) + return self._handle_module(mi) + + def _get_entrypoint_from_package(self, pkgname, looked_in): + if pkgname not in self.pkg_cfg.packages: + return None + pkg = self.pkg_cfg.packages[pkgname] + main = pkg.get("main", None) + if not main: + return None + for js in self.parse_main(pkg.root_dir, main): + looked_in.append(js) + if os.path.exists(js): + section = "lib" + name = self.uri_name_from_path(pkg, js) + docs = None + mi = ModuleInfo(pkg, section, name, js, docs) + return self._handle_module(mi) + return None + + def _search_packages_for_module(self, from_pkg, sections, reqname, + looked_in): + searchpath = [] # list of package names + searchpath.append(from_pkg) # search self first + us = self.pkg_cfg.packages[from_pkg] + if 'dependencies' in us: + # only look in dependencies + searchpath.extend(us['dependencies']) + else: + # they didn't declare any dependencies (or they declared an empty + # list, but we'll treat that as not declaring one, because it's + # easier), so look in all deps, sorted alphabetically, so + # addon-kit comes first. Note that self.deps includes all + # packages found by traversing the ".dependencies" lists in each + # package.json, starting from the main addon package, plus + # everything added by --extra-packages + searchpath.extend(sorted(self.deps)) + for pkgname in searchpath: + mi = self._find_module_in_package(pkgname, sections, reqname, + looked_in) + if mi: + return self._handle_module(mi) + return None + + def _find_module_in_package(self, pkgname, sections, name, looked_in): + # require("a/b/c") should look at ...\a\b\c.js on windows + filename = os.sep.join(name.split("/")) + # normalize filename, make sure that we do not add .js if it already has + # it. + if not filename.endswith(".js") and not filename.endswith(".json"): + filename += ".js" + + if filename.endswith(".js"): + basename = filename[:-3] + if filename.endswith(".json"): + basename = filename[:-5] + + pkg = self.pkg_cfg.packages[pkgname] + if isinstance(sections, basestring): + sections = [sections] + for section in sections: + for sdir in pkg.get(section, []): + js = os.path.join(pkg.root_dir, sdir, filename) + looked_in.append(js) + if os.path.exists(js): + docs = None + maybe_docs = os.path.join(pkg.root_dir, "docs", + basename+".md") + if section == "lib" and os.path.exists(maybe_docs): + docs = maybe_docs + return ModuleInfo(pkg, section, name, js, docs) + return None + +def build_manifest(target_cfg, pkg_cfg, deps, scan_tests, + test_filter_re=None, extra_modules=[], abort_on_missing=False): + """ + Perform recursive dependency analysis starting from entry_point, + building up a manifest of modules that need to be included in the XPI. + Each entry will map require() names to the URL of the module that will + be used to satisfy that dependency. The manifest will be used by the + runtime's require() code. + + This returns a ManifestBuilder object, with two public methods. The + first, get_module_entries(), returns a set of ManifestEntry objects, each + of which can be asked for the following: + + * its contribution to the harness-options.json '.manifest' + * the local disk name + * the name in the XPI at which it should be placed + + The second is get_data_entries(), which returns a set of DataEntry + objects, each of which has: + + * local disk name + * name in the XPI + + note: we don't build the XPI here, but our manifest is passed to the + code which does, so it knows what to copy into the XPI. + """ + + mxt = ManifestBuilder(target_cfg, pkg_cfg, deps, extra_modules, + abort_on_missing=abort_on_missing) + mxt.build(scan_tests, test_filter_re) + return mxt + + + +COMMENT_PREFIXES = ["//", "/*", "*", "dump("] + +REQUIRE_RE = r"(?<![\'\"])require\s*\(\s*[\'\"]([^\'\"]+?)[\'\"]\s*\)" + +# detect the define idiom of the form: +# define("module name", ["dep1", "dep2", "dep3"], function() {}) +# by capturing the contents of the list in a group. +DEF_RE = re.compile(r"(require|define)\s*\(\s*([\'\"][^\'\"]+[\'\"]\s*,)?\s*\[([^\]]+)\]") + +# Out of the async dependencies, do not allow quotes in them. +DEF_RE_ALLOWED = re.compile(r"^[\'\"][^\'\"]+[\'\"]$") + +def scan_requirements_with_grep(fn, lines): + requires = {} + first_location = {} + for (lineno0, line) in enumerate(lines): + for clause in line.split(";"): + clause = clause.strip() + iscomment = False + for commentprefix in COMMENT_PREFIXES: + if clause.startswith(commentprefix): + iscomment = True + if iscomment: + continue + mo = re.finditer(REQUIRE_RE, clause) + if mo: + for mod in mo: + modname = mod.group(1) + requires[modname] = {} + if modname not in first_location: + first_location[modname] = lineno0 + 1 + + # define() can happen across multiple lines, so join everyone up. + wholeshebang = "\n".join(lines) + for match in DEF_RE.finditer(wholeshebang): + # this should net us a list of string literals separated by commas + for strbit in match.group(3).split(","): + strbit = strbit.strip() + # There could be a trailing comma netting us just whitespace, so + # filter that out. Make sure that only string values with + # quotes around them are allowed, and no quotes are inside + # the quoted value. + if strbit and DEF_RE_ALLOWED.match(strbit): + modname = strbit[1:-1] + if modname not in ["exports"]: + requires[modname] = {} + # joining all the lines means we lose line numbers, so we + # can't fill first_location[] + + return requires, first_location + +CHROME_ALIASES = [ + (re.compile(r"Components\.classes"), "Cc"), + (re.compile(r"Components\.interfaces"), "Ci"), + (re.compile(r"Components\.utils"), "Cu"), + (re.compile(r"Components\.results"), "Cr"), + (re.compile(r"Components\.manager"), "Cm"), + ] +OTHER_CHROME = re.compile(r"Components\.[a-zA-Z]") + +def scan_for_bad_chrome(fn, lines, stderr): + problems = False + old_chrome = set() # i.e. "Cc" when we see "Components.classes" + old_chrome_lines = [] # list of (lineno, line.strip()) tuples + for lineno,line in enumerate(lines): + # note: this scanner is not obligated to spot all possible forms of + # chrome access. The scanner is detecting voluntary requests for + # chrome. Runtime tools will enforce allowance or denial of access. + line = line.strip() + iscomment = False + for commentprefix in COMMENT_PREFIXES: + if line.startswith(commentprefix): + iscomment = True + break + if iscomment: + continue + old_chrome_in_this_line = set() + for (regexp,alias) in CHROME_ALIASES: + if regexp.search(line): + old_chrome_in_this_line.add(alias) + if not old_chrome_in_this_line: + if OTHER_CHROME.search(line): + old_chrome_in_this_line.add("components") + old_chrome.update(old_chrome_in_this_line) + if old_chrome_in_this_line: + old_chrome_lines.append( (lineno+1, line) ) + + if old_chrome: + print >>stderr, """ +The following lines from file %(fn)s: +%(lines)s +use 'Components' to access chrome authority. To do so, you need to add a +line somewhat like the following: + + const {%(needs)s} = require("chrome"); + +Then you can use any shortcuts to its properties that you import from the +'chrome' module ('Cc', 'Ci', 'Cm', 'Cr', and 'Cu' for the 'classes', +'interfaces', 'manager', 'results', and 'utils' properties, respectively. And +`components` for `Components` object itself). +""" % { "fn": fn, "needs": ",".join(sorted(old_chrome)), + "lines": "\n".join([" %3d: %s" % (lineno,line) + for (lineno, line) in old_chrome_lines]), + } + problems = True + return problems + +def scan_module(fn, lines, stderr=sys.stderr): + filename = os.path.basename(fn) + requires, locations = scan_requirements_with_grep(fn, lines) + if filename == "cuddlefish.js": + # this is the loader: don't scan for chrome + problems = False + else: + problems = scan_for_bad_chrome(fn, lines, stderr) + return requires, problems, locations + + + +if __name__ == '__main__': + for fn in sys.argv[1:]: + requires, problems, locations = scan_module(fn, open(fn).readlines()) + print + print "---", fn + if problems: + print "PROBLEMS" + sys.exit(1) + print "requires: %s" % (",".join(sorted(requires.keys()))) + print "locations: %s" % locations diff --git a/addon-sdk/source/python-lib/cuddlefish/mobile-utils/bootstrap.js b/addon-sdk/source/python-lib/cuddlefish/mobile-utils/bootstrap.js new file mode 100644 index 000000000..a0120b0be --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/mobile-utils/bootstrap.js @@ -0,0 +1,48 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +var Cu = Components.utils; + +Cu.import("resource://gre/modules/Services.jsm"); + +var { log } = console; + +function startup(data, reason) { + // This code allow to make all stdIO work + try { + Cu.import("resource://gre/modules/ctypes.jsm"); + let libdvm = ctypes.open("libdvm.so"); + let dvmStdioConverterStartup; + // Starting with Android ICS, dalvik uses C++. + // So that the symbol isn't a simple C one + try { + dvmStdioConverterStartup = libdvm.declare("_Z24dvmStdioConverterStartupv", ctypes.default_abi, ctypes.bool); + } + catch(e) { + // Otherwise, before ICS, it was a pure C library + dvmStdioConverterStartup = libdvm.declare("dvmStdioConverterStartup", ctypes.default_abi, ctypes.void_t); + } + dvmStdioConverterStartup(); + log("MU: console redirected to adb logcat."); + } catch(e) { + Cu.reportError("MU: unable to execute jsctype hack: "+e); + } + + try { + let QuitObserver = { + observe: function (aSubject, aTopic, aData) { + Services.obs.removeObserver(QuitObserver, "quit-application"); + dump("MU: APPLICATION-QUIT\n"); + } + }; + Services.obs.addObserver(QuitObserver, "quit-application", false); + log("MU: ready to watch firefox exit."); + } catch(e) { + log("MU: unable to register quit-application observer: " + e); + } +} + +function install() {} +function shutdown() {} diff --git a/addon-sdk/source/python-lib/cuddlefish/mobile-utils/install.rdf b/addon-sdk/source/python-lib/cuddlefish/mobile-utils/install.rdf new file mode 100644 index 000000000..0b81a0e5f --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/mobile-utils/install.rdf @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + <Description about="urn:mozilla:install-manifest"> + <em:id>mobile-utils@mozilla.com</em:id> + <em:version>1.0</em:version> + <em:type>2</em:type> + <em:bootstrap>true</em:bootstrap> + + <!-- Fennec-XUL --> + <em:targetApplication> + <Description> + <em:id>{a23983c0-fd0e-11dc-95ff-0800200c9a66}</em:id> + <em:minVersion>1</em:minVersion> + <em:maxVersion>*</em:maxVersion> + </Description> + </em:targetApplication> + + <!-- Fennec-NativeUI --> + <em:targetApplication> + <Description> + <em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id> + <em:minVersion>1</em:minVersion> + <em:maxVersion>*</em:maxVersion> + </Description> + </em:targetApplication> + + <!-- Front End MetaData --> + <em:name>Mobile Addon-SDK utility addon</em:name> + <em:description>Allow better integration with cfx tool.</em:description> + <em:creator>Mozilla Corporation</em:creator> + + </Description> +</RDF> diff --git a/addon-sdk/source/python-lib/cuddlefish/packaging.py b/addon-sdk/source/python-lib/cuddlefish/packaging.py new file mode 100644 index 000000000..0c5357e8e --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/packaging.py @@ -0,0 +1,463 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import sys +import re +import copy + +import simplejson as json +from cuddlefish.bunch import Bunch + +MANIFEST_NAME = 'package.json' +DEFAULT_LOADER = 'addon-sdk' + +# Is different from root_dir when running tests +env_root = os.environ.get('CUDDLEFISH_ROOT') + +DEFAULT_PROGRAM_MODULE = 'main' + +DEFAULT_ICON = 'icon.png' +DEFAULT_ICON64 = 'icon64.png' + +METADATA_PROPS = ['name', 'description', 'keywords', 'author', 'version', + 'developers', 'translators', 'contributors', 'license', 'homepage', + 'icon', 'icon64', 'main', 'directories', 'permissions', 'preferences'] + +RESOURCE_HOSTNAME_RE = re.compile(r'^[a-z0-9_\-]+$') + +class Error(Exception): + pass + +class MalformedPackageError(Error): + pass + +class MalformedJsonFileError(Error): + pass + +class DuplicatePackageError(Error): + pass + +class PackageNotFoundError(Error): + def __init__(self, missing_package, reason): + self.missing_package = missing_package + self.reason = reason + def __str__(self): + return "%s (%s)" % (self.missing_package, self.reason) + +class BadChromeMarkerError(Error): + pass + +def validate_resource_hostname(name): + """ + Validates the given hostname for a resource: URI. + + For more information, see: + + https://bugzilla.mozilla.org/show_bug.cgi?id=566812#c13 + + Examples: + + >>> validate_resource_hostname('blarg') + + >>> validate_resource_hostname('bl arg') + Traceback (most recent call last): + ... + ValueError: Error: the name of your package contains an invalid character. + Package names can contain only lower-case letters, numbers, underscores, and dashes. + Current package name: bl arg + + >>> validate_resource_hostname('BLARG') + Traceback (most recent call last): + ... + ValueError: Error: the name of your package contains upper-case letters. + Package names can contain only lower-case letters, numbers, underscores, and dashes. + Current package name: BLARG + + >>> validate_resource_hostname('foo@bar') + Traceback (most recent call last): + ... + ValueError: Error: the name of your package contains an invalid character. + Package names can contain only lower-case letters, numbers, underscores, and dashes. + Current package name: foo@bar + """ + + # See https://bugzilla.mozilla.org/show_bug.cgi?id=568131 for details. + if not name.lower() == name: + raise ValueError("""Error: the name of your package contains upper-case letters. +Package names can contain only lower-case letters, numbers, underscores, and dashes. +Current package name: %s""" % name) + + if not RESOURCE_HOSTNAME_RE.match(name): + raise ValueError("""Error: the name of your package contains an invalid character. +Package names can contain only lower-case letters, numbers, underscores, and dashes. +Current package name: %s""" % name) + +def find_packages_with_module(pkg_cfg, name): + # TODO: Make this support more than just top-level modules. + filename = "%s.js" % name + packages = [] + for cfg in pkg_cfg.packages.itervalues(): + if 'lib' in cfg: + matches = [dirname for dirname in resolve_dirs(cfg, cfg.lib) + if os.path.exists(os.path.join(dirname, filename))] + if matches: + packages.append(cfg.name) + return packages + +def resolve_dirs(pkg_cfg, dirnames): + for dirname in dirnames: + yield resolve_dir(pkg_cfg, dirname) + +def resolve_dir(pkg_cfg, dirname): + return os.path.join(pkg_cfg.root_dir, dirname) + +def validate_permissions(perms): + if (perms.get('cross-domain-content') and + not isinstance(perms.get('cross-domain-content'), list)): + raise ValueError("Error: `cross-domain-content` permissions in \ + package.json file must be an array of strings:\n %s" % perms) + +def get_metadata(pkg_cfg, deps): + metadata = Bunch() + for pkg_name in deps: + cfg = pkg_cfg.packages[pkg_name] + metadata[pkg_name] = Bunch() + for prop in METADATA_PROPS: + if cfg.get(prop): + if prop == 'permissions': + validate_permissions(cfg[prop]) + metadata[pkg_name][prop] = cfg[prop] + return metadata + +def set_section_dir(base_json, name, base_path, dirnames, allow_root=False): + resolved = compute_section_dir(base_json, base_path, dirnames, allow_root) + if resolved: + base_json[name] = os.path.abspath(resolved) + +def compute_section_dir(base_json, base_path, dirnames, allow_root): + # PACKAGE_JSON.lib is highest priority + # then PACKAGE_JSON.directories.lib + # then lib/ (if it exists) + # then . (but only if allow_root=True) + for dirname in dirnames: + if base_json.get(dirname): + return os.path.join(base_path, base_json[dirname]) + if "directories" in base_json: + for dirname in dirnames: + if dirname in base_json.directories: + return os.path.join(base_path, base_json.directories[dirname]) + for dirname in dirnames: + if os.path.isdir(os.path.join(base_path, dirname)): + return os.path.join(base_path, dirname) + if allow_root: + return os.path.abspath(base_path) + return None + +def normalize_string_or_array(base_json, key): + if base_json.get(key): + if isinstance(base_json[key], basestring): + base_json[key] = [base_json[key]] + +def load_json_file(path): + data = open(path, 'r').read() + try: + return Bunch(json.loads(data)) + except ValueError, e: + raise MalformedJsonFileError('%s when reading "%s"' % (str(e), + path)) + +def get_config_in_dir(path): + package_json = os.path.join(path, MANIFEST_NAME) + if not (os.path.exists(package_json) and + os.path.isfile(package_json)): + raise MalformedPackageError('%s not found in "%s"' % (MANIFEST_NAME, + path)) + base_json = load_json_file(package_json) + + if 'name' not in base_json: + base_json.name = os.path.basename(path) + + # later processing steps will expect to see the following keys in the + # base_json that we return: + # + # name: name of the package + # lib: list of directories with .js files + # test: list of directories with test-*.js files + # doc: list of directories with documentation .md files + # data: list of directories with bundled arbitrary data files + # packages: ? + + if (not base_json.get('tests') and + os.path.isdir(os.path.join(path, 'test'))): + base_json['tests'] = 'test' + + set_section_dir(base_json, 'lib', path, ['lib'], True) + set_section_dir(base_json, 'tests', path, ['test', 'tests'], False) + set_section_dir(base_json, 'doc', path, ['doc', 'docs']) + set_section_dir(base_json, 'data', path, ['data']) + set_section_dir(base_json, 'packages', path, ['packages']) + set_section_dir(base_json, 'locale', path, ['locale']) + + if (not base_json.get('icon') and + os.path.isfile(os.path.join(path, DEFAULT_ICON))): + base_json['icon'] = DEFAULT_ICON + + if (not base_json.get('icon64') and + os.path.isfile(os.path.join(path, DEFAULT_ICON64))): + base_json['icon64'] = DEFAULT_ICON64 + + for key in ['lib', 'tests', 'dependencies', 'packages']: + # TODO: lib/tests can be an array?? consider interaction with + # compute_section_dir above + normalize_string_or_array(base_json, key) + + if 'main' not in base_json and 'lib' in base_json: + for dirname in base_json['lib']: + program = os.path.join(path, dirname, + '%s.js' % DEFAULT_PROGRAM_MODULE) + if os.path.exists(program): + base_json['main'] = DEFAULT_PROGRAM_MODULE + break + + base_json.root_dir = path + + if "dependencies" in base_json: + deps = base_json["dependencies"] + deps = [x for x in deps if x not in ["addon-kit", "api-utils"]] + deps.append("addon-sdk") + base_json["dependencies"] = deps + + return base_json + +def _is_same_file(a, b): + if hasattr(os.path, 'samefile'): + return os.path.samefile(a, b) + return a == b + +def build_config(root_dir, target_cfg, packagepath=[]): + dirs_to_scan = [env_root] # root is addon-sdk dir, diff from root_dir in tests + + def add_packages_from_config(pkgconfig): + if 'packages' in pkgconfig: + for package_dir in resolve_dirs(pkgconfig, pkgconfig.packages): + dirs_to_scan.append(package_dir) + + add_packages_from_config(target_cfg) + + packages_dir = os.path.join(root_dir, 'packages') + if os.path.exists(packages_dir) and os.path.isdir(packages_dir): + dirs_to_scan.append(packages_dir) + dirs_to_scan.extend(packagepath) + + packages = Bunch({target_cfg.name: target_cfg}) + + while dirs_to_scan: + packages_dir = dirs_to_scan.pop() + if os.path.exists(os.path.join(packages_dir, "package.json")): + package_paths = [packages_dir] + else: + package_paths = [os.path.join(packages_dir, dirname) + for dirname in os.listdir(packages_dir) + if not dirname.startswith('.')] + package_paths = [dirname for dirname in package_paths + if os.path.isdir(dirname)] + + for path in package_paths: + pkgconfig = get_config_in_dir(path) + if pkgconfig.name in packages: + otherpkg = packages[pkgconfig.name] + if not _is_same_file(otherpkg.root_dir, path): + raise DuplicatePackageError(path, otherpkg.root_dir) + else: + packages[pkgconfig.name] = pkgconfig + add_packages_from_config(pkgconfig) + + return Bunch(packages=packages) + +def get_deps_for_targets(pkg_cfg, targets): + visited = [] + deps_left = [[dep, None] for dep in list(targets)] + + while deps_left: + [dep, required_by] = deps_left.pop() + if dep not in visited: + visited.append(dep) + if dep not in pkg_cfg.packages: + required_reason = ("required by '%s'" % (required_by)) \ + if required_by is not None \ + else "specified as target" + raise PackageNotFoundError(dep, required_reason) + dep_cfg = pkg_cfg.packages[dep] + deps_left.extend([[i, dep] for i in dep_cfg.get('dependencies', [])]) + deps_left.extend([[i, dep] for i in dep_cfg.get('extra_dependencies', [])]) + + return visited + +def generate_build_for_target(pkg_cfg, target, deps, + include_tests=True, + include_dep_tests=False, + is_running_tests=False, + default_loader=DEFAULT_LOADER): + + build = Bunch(# Contains section directories for all packages: + packages=Bunch(), + locale=Bunch() + ) + + def add_section_to_build(cfg, section, is_code=False, + is_data=False): + if section in cfg: + dirnames = cfg[section] + if isinstance(dirnames, basestring): + # This is just for internal consistency within this + # function, it has nothing to do w/ a non-canonical + # configuration dict. + dirnames = [dirnames] + for dirname in resolve_dirs(cfg, dirnames): + # ensure that package name is valid + try: + validate_resource_hostname(cfg.name) + except ValueError, err: + print err + sys.exit(1) + # ensure that this package has an entry + if not cfg.name in build.packages: + build.packages[cfg.name] = Bunch() + # detect duplicated sections + if section in build.packages[cfg.name]: + raise KeyError("package's section already defined", + cfg.name, section) + # Register this section (lib, data, tests) + build.packages[cfg.name][section] = dirname + + def add_locale_to_build(cfg): + # Bug 730776: Ignore locales for addon-kit, that are only for unit tests + if not is_running_tests and cfg.name == "addon-sdk": + return + + path = resolve_dir(cfg, cfg['locale']) + files = os.listdir(path) + for filename in files: + fullpath = os.path.join(path, filename) + if os.path.isfile(fullpath) and filename.endswith('.properties'): + language = filename[:-len('.properties')] + + from property_parser import parse_file, MalformedLocaleFileError + try: + content = parse_file(fullpath) + except MalformedLocaleFileError, msg: + print msg[0] + sys.exit(1) + + # Merge current locales into global locale hashtable. + # Locale files only contains one big JSON object + # that act as an hastable of: + # "keys to translate" => "translated keys" + if language in build.locale: + merge = (build.locale[language].items() + + content.items()) + build.locale[language] = Bunch(merge) + else: + build.locale[language] = content + + def add_dep_to_build(dep): + dep_cfg = pkg_cfg.packages[dep] + add_section_to_build(dep_cfg, "lib", is_code=True) + add_section_to_build(dep_cfg, "data", is_data=True) + if include_tests and include_dep_tests: + add_section_to_build(dep_cfg, "tests", is_code=True) + if 'locale' in dep_cfg: + add_locale_to_build(dep_cfg) + if ("loader" in dep_cfg) and ("loader" not in build): + build.loader = "%s/%s" % (dep, + dep_cfg.loader) + + target_cfg = pkg_cfg.packages[target] + + if include_tests and not include_dep_tests: + add_section_to_build(target_cfg, "tests", is_code=True) + + for dep in deps: + add_dep_to_build(dep) + + if 'loader' not in build: + add_dep_to_build(DEFAULT_LOADER) + + if 'icon' in target_cfg: + build['icon'] = os.path.join(target_cfg.root_dir, target_cfg.icon) + del target_cfg['icon'] + + if 'icon64' in target_cfg: + build['icon64'] = os.path.join(target_cfg.root_dir, target_cfg.icon64) + del target_cfg['icon64'] + + if 'id' in target_cfg: + # NOTE: logic duplicated from buildJID() + jid = target_cfg['id'] + if not ('@' in jid or jid.startswith('{')): + jid += '@jetpack' + build['preferencesBranch'] = jid + + if 'preferences-branch' in target_cfg: + build['preferencesBranch'] = target_cfg['preferences-branch'] + + return build + +def _get_files_in_dir(path): + data = {} + files = os.listdir(path) + for filename in files: + fullpath = os.path.join(path, filename) + if os.path.isdir(fullpath): + data[filename] = _get_files_in_dir(fullpath) + else: + try: + info = os.stat(fullpath) + data[filename] = ("file", dict(size=info.st_size)) + except OSError: + pass + return ("directory", data) + +def build_pkg_index(pkg_cfg): + pkg_cfg = copy.deepcopy(pkg_cfg) + for pkg in pkg_cfg.packages: + root_dir = pkg_cfg.packages[pkg].root_dir + files = _get_files_in_dir(root_dir) + pkg_cfg.packages[pkg].files = files + try: + readme = open(root_dir + '/README.md').read() + pkg_cfg.packages[pkg].readme = readme + except IOError: + pass + del pkg_cfg.packages[pkg].root_dir + return pkg_cfg.packages + +def build_pkg_cfg(root): + pkg_cfg = build_config(root, Bunch(name='dummy')) + del pkg_cfg.packages['dummy'] + return pkg_cfg + +def call_plugins(pkg_cfg, deps): + for dep in deps: + dep_cfg = pkg_cfg.packages[dep] + dirnames = dep_cfg.get('python-lib', []) + for dirname in resolve_dirs(dep_cfg, dirnames): + sys.path.append(dirname) + module_names = dep_cfg.get('python-plugins', []) + for module_name in module_names: + module = __import__(module_name) + module.init(root_dir=dep_cfg.root_dir) + +def call_cmdline_tool(env_root, pkg_name): + pkg_cfg = build_config(env_root, Bunch(name='dummy')) + if pkg_name not in pkg_cfg.packages: + print "This tool requires the '%s' package." % pkg_name + sys.exit(1) + cfg = pkg_cfg.packages[pkg_name] + for dirname in resolve_dirs(cfg, cfg['python-lib']): + sys.path.append(dirname) + module_name = cfg.get('python-cmdline-tool') + module = __import__(module_name) + module.run() diff --git a/addon-sdk/source/python-lib/cuddlefish/preflight.py b/addon-sdk/source/python-lib/cuddlefish/preflight.py new file mode 100755 index 000000000..c92a0890d --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/preflight.py @@ -0,0 +1,77 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os, sys +import base64 +import simplejson as json + +def create_jid(): + """Return 'jid1-XYZ', where 'XYZ' is a randomly-generated string. (in the + previous jid0- series, the string securely identified a specific public + key). To get a suitable add-on ID, append '@jetpack' to this string. + """ + # per https://developer.mozilla.org/en/Install_Manifests#id all XPI id + # values must either be in the form of a 128-bit GUID (crazy braces + # and all) or in the form of an email address (crazy @ and all). + # Firefox will refuse to install an add-on with an id that doesn't + # match one of these forms. The actual regexp is at: + # http://mxr.mozilla.org/mozilla-central/source/toolkit/mozapps/extensions/internal/XPIProvider.jsm#130 + # So the JID needs an @-suffix, and the only legal punctuation is + # "-._". So we start with a base64 encoding, and replace the + # punctuation (+/) with letters (AB), losing a few bits of integrity. + + # even better: windows has a maximum path length limitation of 256 + # characters: + # http://msdn.microsoft.com/en-us/library/aa365247%28VS.85%29.aspx + # (unless all paths are prefixed with "\\?\", I kid you not). The + # typical install will put add-on code in a directory like: + # C:\Documents and Settings\<username>\Application Data\Mozilla\Firefox\Profiles\232353483.default\extensions\$JID\... + # (which is 108 chars long without the $JID). + # Then the unpacked XPI contains packaged resources like: + # resources/$JID-api-utils-lib/main.js (35 chars plus the $JID) + # + # We create a random 80 bit string, base64 encode that (with + # AB instead of +/ to be path-safe), then bundle it into + # "jid1-XYZ@jetpack". This gives us 27 characters. The resulting + # main.js will have a path length of 211 characters, leaving us 45 + # characters of margin. + # + # 80 bits is enough to generate one billion JIDs and still maintain lower + # than a one-in-a-million chance of accidental collision. (1e9 JIDs is 30 + # bits, square for the "birthday-paradox" to get 60 bits, add 20 bits for + # the one-in-a-million margin to get 80 bits) + + # if length were no issue, we'd prefer to use this: + h = os.urandom(80/8) + s = base64.b64encode(h, "AB").strip("=") + jid = "jid1-" + s + return jid + +def preflight_config(target_cfg, filename, stderr=sys.stderr): + modified = False + config = json.load(open(filename, 'r')) + + if "id" not in config: + print >>stderr, ("No 'id' in package.json: creating a new ID for you.") + jid = create_jid() + config["id"] = jid + modified = True + + if modified: + i = 0 + backup = filename + ".backup" + while os.path.exists(backup): + if i > 1000: + raise ValueError("I'm having problems finding a good name" + " for the backup file. Please move %s out" + " of the way and try again." + % (filename + ".backup")) + backup = filename + ".backup-%d" % i + i += 1 + os.rename(filename, backup) + new_json = json.dumps(config, indent=4) + open(filename, 'w').write(new_json+"\n") + return False, True + + return True, False diff --git a/addon-sdk/source/python-lib/cuddlefish/prefs.py b/addon-sdk/source/python-lib/cuddlefish/prefs.py new file mode 100644 index 000000000..9192eb72c --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/prefs.py @@ -0,0 +1,239 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEFAULT_COMMON_PREFS = { + # allow debug output via dump to be printed to the system console + # (setting it here just in case, even though PlainTextConsole also + # sets this preference) + 'browser.dom.window.dump.enabled': True, + # warn about possibly incorrect code + 'javascript.options.showInConsole': True, + + # Allow remote connections to the debugger + 'devtools.debugger.remote-enabled' : True, + + 'extensions.sdk.console.logLevel': 'info', + + 'extensions.checkCompatibility.nightly' : False, + + # Disable extension updates and notifications. + 'extensions.update.enabled' : False, + 'lightweightThemes.update.enabled' : False, + 'extensions.update.notifyUser' : False, + + # From: + # http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l372 + # Only load extensions from the application and user profile. + # AddonManager.SCOPE_PROFILE + AddonManager.SCOPE_APPLICATION + 'extensions.enabledScopes' : 5, + # Disable metadata caching for installed add-ons by default + 'extensions.getAddons.cache.enabled' : False, + # Disable intalling any distribution add-ons + 'extensions.installDistroAddons' : False, + # Allow installing extensions dropped into the profile folder + 'extensions.autoDisableScopes' : 10, + + # shut up some warnings on `about:` page + 'app.releaseNotesURL': 'http://localhost/app-dummy/', + 'app.vendorURL': 'http://localhost/app-dummy/', +} + +DEFAULT_NO_CONNECTIONS_PREFS = { + 'toolkit.telemetry.enabled': False, + 'toolkit.telemetry.server': 'https://localhost/telemetry-dummy/', + 'app.update.auto' : False, + 'app.update.url': 'http://localhost/app-dummy/update', + # Make sure GMPInstallManager won't hit the network. + 'media.gmp-gmpopenh264.autoupdate' : False, + 'media.gmp-manager.cert.checkAttributes' : False, + 'media.gmp-manager.cert.requireBuiltIn' : False, + 'media.gmp-manager.url' : 'http://localhost/media-dummy/gmpmanager', + 'media.gmp-manager.url.override': 'http://localhost/dummy-gmp-manager.xml', + 'media.gmp-manager.updateEnabled': False, + 'browser.aboutHomeSnippets.updateUrl': 'https://localhost/snippet-dummy', + 'browser.newtab.url' : 'about:blank', + 'browser.search.update': False, + 'browser.search.suggest.enabled' : False, + 'browser.safebrowsing.phishing.enabled' : False, + 'browser.safebrowsing.provider.google.updateURL': 'http://localhost/safebrowsing-dummy/update', + 'browser.safebrowsing.provider.google.gethashURL': 'http://localhost/safebrowsing-dummy/gethash', + 'browser.safebrowsing.malware.reportURL': 'http://localhost/safebrowsing-dummy/malwarereport', + 'browser.selfsupport.url': 'https://localhost/selfsupport-dummy', + 'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash', + 'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update', + + # Disable app update + 'app.update.enabled' : False, + 'app.update.staging.enabled': False, + + # Disable about:newtab content fetch and ping + 'browser.newtabpage.directory.source': 'data:application/json,{"jetpack":1}', + 'browser.newtabpage.directory.ping': '', + + # Point update checks to a nonexistent local URL for fast failures. + 'extensions.update.url' : 'http://localhost/extensions-dummy/updateURL', + 'extensions.update.background.url': 'http://localhost/extensions-dummy/updateBackgroundURL', + 'extensions.blocklist.url' : 'http://localhost/extensions-dummy/blocklistURL', + # Make sure opening about:addons won't hit the network. + 'extensions.webservice.discoverURL' : 'http://localhost/extensions-dummy/discoveryURL', + 'extensions.getAddons.maxResults': 0, + + # Disable webapp updates. Yes, it is supposed to be an integer. + 'browser.webapps.checkForUpdates': 0, + + # Location services + 'geo.wifi.uri': 'http://localhost/location-dummy/locationURL', + 'browser.search.geoip.url': 'http://localhost/location-dummy/locationURL', + + # Tell the search service we are running in the US. This also has the + # desired side-effect of preventing our geoip lookup. + 'browser.search.isUS' : True, + 'browser.search.countryCode' : 'US', + + 'geo.wifi.uri' : 'http://localhost/extensions-dummy/geowifiURL', + 'geo.wifi.scan' : False, + + # We don't want to hit the real Firefox Accounts server for tests. We don't + # actually need a functioning FxA server, so just set it to something that + # resolves and accepts requests, even if they all fail. + 'identity.fxaccounts.auth.uri': 'http://localhost/fxa-dummy/' +} + +DEFAULT_FENNEC_PREFS = { + 'browser.console.showInPanel': True, + 'browser.firstrun.show.uidiscovery': False +} + +# When launching a temporary new Firefox profile, use these preferences. +DEFAULT_FIREFOX_PREFS = { + 'browser.startup.homepage' : 'about:blank', + 'startup.homepage_welcome_url' : 'about:blank', + 'devtools.browsertoolbox.panel': 'jsdebugger', + 'devtools.chrome.enabled' : True, + + # From: + # http://hg.mozilla.org/mozilla-central/file/1dd81c324ac7/build/automation.py.in#l388 + # Make url-classifier updates so rare that they won't affect tests. + 'urlclassifier.updateinterval' : 172800, + # Point the url-classifier to a nonexistent local URL for fast failures. + 'browser.safebrowsing.provider.google.gethashURL' : 'http://localhost/safebrowsing-dummy/gethash', + 'browser.safebrowsing.provider.google.updateURL' : 'http://localhost/safebrowsing-dummy/update', + 'browser.safebrowsing.provider.mozilla.gethashURL': 'http://localhost/safebrowsing-dummy/gethash', + 'browser.safebrowsing.provider.mozilla.updateURL': 'http://localhost/safebrowsing-dummy/update', +} + +# When launching a temporary new Thunderbird profile, use these preferences. +# Note that these were taken from: +# http://dxr.mozilla.org/comm-central/source/mail/test/mozmill/runtest.py +DEFAULT_THUNDERBIRD_PREFS = { + # say no to slow script warnings + 'dom.max_chrome_script_run_time': 200, + 'dom.max_script_run_time': 0, + # do not ask about being the default mail client + 'mail.shell.checkDefaultClient': False, + # disable non-gloda indexing daemons + 'mail.winsearch.enable': False, + 'mail.winsearch.firstRunDone': True, + 'mail.spotlight.enable': False, + 'mail.spotlight.firstRunDone': True, + # disable address books for undisclosed reasons + 'ldap_2.servers.osx.position': 0, + 'ldap_2.servers.oe.position': 0, + # disable the first use junk dialog + 'mailnews.ui.junk.firstuse': False, + # other unknown voodoo + # -- dummied up local accounts to stop the account wizard + 'mail.account.account1.server' : "server1", + 'mail.account.account2.identities' : "id1", + 'mail.account.account2.server' : "server2", + 'mail.accountmanager.accounts' : "account1,account2", + 'mail.accountmanager.defaultaccount' : "account2", + 'mail.accountmanager.localfoldersserver' : "server1", + 'mail.identity.id1.fullName' : "Tinderbox", + 'mail.identity.id1.smtpServer' : "smtp1", + 'mail.identity.id1.useremail' : "tinderbox@invalid.com", + 'mail.identity.id1.valid' : True, + 'mail.root.none-rel' : "[ProfD]Mail", + 'mail.root.pop3-rel' : "[ProfD]Mail", + 'mail.server.server1.directory-rel' : "[ProfD]Mail/Local Folders", + 'mail.server.server1.hostname' : "Local Folders", + 'mail.server.server1.name' : "Local Folders", + 'mail.server.server1.type' : "none", + 'mail.server.server1.userName' : "nobody", + 'mail.server.server2.check_new_mail' : False, + 'mail.server.server2.directory-rel' : "[ProfD]Mail/tinderbox", + 'mail.server.server2.download_on_biff' : True, + 'mail.server.server2.hostname' : "tinderbox", + 'mail.server.server2.login_at_startup' : False, + 'mail.server.server2.name' : "tinderbox@invalid.com", + 'mail.server.server2.type' : "pop3", + 'mail.server.server2.userName' : "tinderbox", + 'mail.smtp.defaultserver' : "smtp1", + 'mail.smtpserver.smtp1.hostname' : "tinderbox", + 'mail.smtpserver.smtp1.username' : "tinderbox", + 'mail.smtpservers' : "smtp1", + 'mail.startup.enabledMailCheckOnce' : True, + 'mailnews.start_page_override.mstone' : "ignore", +} + +DEFAULT_TEST_PREFS = { + 'browser.console.showInPanel': True, + 'browser.startup.page': 0, + 'browser.firstrun.show.localepicker': False, + 'browser.firstrun.show.uidiscovery': False, + 'browser.ui.layout.tablet': 0, + 'dom.disable_open_during_load': False, + 'dom.experimental_forms': True, + 'dom.forms.number': True, + 'dom.forms.color': True, + 'dom.max_script_run_time': 0, + 'hangmonitor.timeout': 0, + 'dom.max_chrome_script_run_time': 0, + 'dom.popup_maximum': -1, + 'dom.send_after_paint_to_content': True, + 'dom.successive_dialog_time_limit': 0, + 'browser.shell.checkDefaultBrowser': False, + 'shell.checkDefaultClient': False, + 'browser.warnOnQuit': False, + 'accessibility.typeaheadfind.autostart': False, + 'browser.EULA.override': True, + 'gfx.color_management.force_srgb': True, + 'network.manage-offline-status': False, + # Disable speculative connections so they aren't reported as leaking when they're hanging around. + 'network.http.speculative-parallel-limit': 0, + 'test.mousescroll': True, + # Need to client auth test be w/o any dialogs + 'security.default_personal_cert': 'Select Automatically', + 'network.http.prompt-temp-redirect': False, + 'security.warn_viewing_mixed': False, + 'extensions.defaultProviders.enabled': True, + 'datareporting.policy.dataSubmissionPolicyBypassNotification': True, + 'layout.css.report_errors': True, + 'layout.css.grid.enabled': True, + 'layout.spammy_warnings.enabled': False, + 'dom.mozSettings.enabled': True, + # Make sure the disk cache doesn't get auto disabled + 'network.http.bypass-cachelock-threshold': 200000, + # Always use network provider for geolocation tests + # so we bypass the OSX dialog raised by the corelocation provider + 'geo.provider.testing': True, + # Background thumbnails in particular cause grief, and disabling thumbnails + # in general can't hurt - we re-enable them when tests need them. + 'browser.pagethumbnails.capturing_disabled': True, + # Indicate that the download panel has been shown once so that whichever + # download test runs first doesn't show the popup inconsistently. + 'browser.download.panel.shown': True, + # Assume the about:newtab page's intro panels have been shown to not depend on + # which test runs first and happens to open about:newtab + 'browser.newtabpage.introShown': True, + # Disable useragent updates. + 'general.useragent.updates.enabled': False, + 'media.eme.enabled': True, + 'media.eme.apiVisible': True, + # Don't forceably kill content processes after a timeout + 'dom.ipc.tabs.shutdownTimeoutSecs': 0, + 'general.useragent.locale': "en-US", + 'intl.locale.matchOS': "en-US", + 'dom.indexedDB.experimental': True +} diff --git a/addon-sdk/source/python-lib/cuddlefish/property_parser.py b/addon-sdk/source/python-lib/cuddlefish/property_parser.py new file mode 100644 index 000000000..20068a264 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/property_parser.py @@ -0,0 +1,111 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import re +import codecs + +class MalformedLocaleFileError(Exception): + pass + +def parse_file(path): + return parse(read_file(path), path) + +def read_file(path): + try: + return codecs.open( path, "r", "utf-8" ).readlines() + except UnicodeDecodeError, e: + raise MalformedLocaleFileError( + 'Following locale file is not a valid ' + + 'UTF-8 file: %s\n%s"' % (path, str(e))) + +COMMENT = re.compile(r'\s*#') +EMPTY = re.compile(r'^\s+$') +KEYVALUE = re.compile(r"\s*([^=:]+)(=|:)\s*(.*)") + +def parse(lines, path=None): + lines = iter(lines) + lineNo = 1 + pairs = dict() + for line in lines: + if COMMENT.match(line) or EMPTY.match(line) or len(line) == 0: + continue + m = KEYVALUE.match(line) + if not m: + raise MalformedLocaleFileError( + 'Following locale file is not a valid .properties file: %s\n' + 'Line %d is incorrect:\n%s' % (path, lineNo, line)) + + # All spaces are strip. Spaces at the beginning are stripped + # by the regular expression. We have to strip spaces at the end. + key = m.group(1).rstrip() + val = m.group(3).rstrip() + val = val.encode('raw-unicode-escape').decode('raw-unicode-escape') + + # `key` can be empty when key is only made of spaces + if not key: + raise MalformedLocaleFileError( + 'Following locale file is not a valid .properties file: %s\n' + 'Key is invalid on line %d is incorrect:\n%s' % + (path, lineNo, line)) + + # Multiline value: keep reading lines, while lines end with backslash + # and strip spaces at the beginning of lines except the last line + # that doesn't end up with backslash, we strip all spaces for this one. + if val.endswith("\\"): + val = val[:-1] + try: + # remove spaces before/after and especially the \n at EOL + line = lines.next().strip() + while line.endswith("\\"): + val += line[:-1].lstrip() + line = lines.next() + lineNo += 1 + val += line.strip() + except StopIteration: + raise MalformedLocaleFileError( + 'Following locale file is not a valid .properties file: %s\n' + 'Unexpected EOF in multiline sequence at line %d:\n%s' % + (path, lineNo, line)) + # Save this new pair + pairs[key] = val + lineNo += 1 + + normalize_plural(path, pairs) + return pairs + +# Plural forms in properties files are defined like this: +# key = other form +# key[one] = one form +# key[...] = ... +# Parse them and merge each key into one object containing all forms: +# key: { +# other: "other form", +# one: "one form", +# ...: ... +# } +PLURAL_FORM = re.compile(r'^(.*)\[(zero|one|two|few|many|other)\]$') +def normalize_plural(path, pairs): + for key in list(pairs.keys()): + m = PLURAL_FORM.match(key) + if not m: + continue + main_key = m.group(1) + plural_form = m.group(2) + # Allows not specifying a generic key (i.e a key without [form]) + if not main_key in pairs: + pairs[main_key] = {} + # Ensure that we always have the [other] form + if not main_key + "[other]" in pairs: + raise MalformedLocaleFileError( + 'Following locale file is not a valid UTF-8 file: %s\n' + 'This plural form doesn\'t have a matching `%s[other]` form:\n' + '%s\n' + 'You have to defined following key:\n%s' + % (path, main_key, key, main_key)) + # convert generic form into an object if it is still a string + if isinstance(pairs[main_key], unicode): + pairs[main_key] = {"other": pairs[main_key]} + # then, add this new plural form + pairs[main_key][plural_form] = pairs[key] + del pairs[key] diff --git a/addon-sdk/source/python-lib/cuddlefish/rdf.py b/addon-sdk/source/python-lib/cuddlefish/rdf.py new file mode 100644 index 000000000..2964897c1 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/rdf.py @@ -0,0 +1,214 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import xml.dom.minidom +import StringIO + +RDF_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +EM_NS = "http://www.mozilla.org/2004/em-rdf#" + +class RDF(object): + def __str__(self): + # real files have an .encoding attribute and use it when you + # write() unicode into them: they read()/write() unicode and + # put encoded bytes in the backend file. StringIO objects + # read()/write() unicode and put unicode in the backing store, + # so we must encode the output of getvalue() to get a + # bytestring. (cStringIO objects are weirder: they effectively + # have .encoding hardwired to "ascii" and put only bytes in + # the backing store, so we can't use them here). + # + # The encoding= argument to dom.writexml() merely sets the XML header's + # encoding= attribute. It still writes unencoded unicode to the output file, + # so we have to encode it for real afterwards. + # + # Also see: https://bugzilla.mozilla.org/show_bug.cgi?id=567660 + + buf = StringIO.StringIO() + self.dom.writexml(buf, encoding="utf-8") + return buf.getvalue().encode('utf-8') + +class RDFUpdate(RDF): + def __init__(self): + impl = xml.dom.minidom.getDOMImplementation() + self.dom = impl.createDocument(RDF_NS, "RDF", None) + self.dom.documentElement.setAttribute("xmlns", RDF_NS) + self.dom.documentElement.setAttribute("xmlns:em", EM_NS) + + def _make_node(self, name, value, parent): + elem = self.dom.createElement(name) + elem.appendChild(self.dom.createTextNode(value)) + parent.appendChild(elem) + return elem + + def add(self, manifest, update_link): + desc = self.dom.createElement("Description") + desc.setAttribute( + "about", + "urn:mozilla:extension:%s" % manifest.get("em:id") + ) + self.dom.documentElement.appendChild(desc) + + updates = self.dom.createElement("em:updates") + desc.appendChild(updates) + + seq = self.dom.createElement("Seq") + updates.appendChild(seq) + + li = self.dom.createElement("li") + seq.appendChild(li) + + li_desc = self.dom.createElement("Description") + li.appendChild(li_desc) + + self._make_node("em:version", manifest.get("em:version"), + li_desc) + + apps = manifest.dom.documentElement.getElementsByTagName( + "em:targetApplication" + ) + + for app in apps: + target_app = self.dom.createElement("em:targetApplication") + li_desc.appendChild(target_app) + + ta_desc = self.dom.createElement("Description") + target_app.appendChild(ta_desc) + + for name in ["em:id", "em:minVersion", "em:maxVersion"]: + elem = app.getElementsByTagName(name)[0] + self._make_node(name, elem.firstChild.nodeValue, ta_desc) + + self._make_node("em:updateLink", update_link, ta_desc) + +class RDFManifest(RDF): + def __init__(self, path): + self.dom = xml.dom.minidom.parse(path) + + def set(self, property, value): + elements = self.dom.documentElement.getElementsByTagName(property) + if not elements: + raise ValueError("Element with value not found: %s" % property) + if not elements[0].firstChild: + elements[0].appendChild(self.dom.createTextNode(value)) + else: + elements[0].firstChild.nodeValue = value + + def get(self, property, default=None): + elements = self.dom.documentElement.getElementsByTagName(property) + if not elements: + return default + return elements[0].firstChild.nodeValue + + def remove(self, property): + elements = self.dom.documentElement.getElementsByTagName(property) + if not elements: + return True + else: + for i in elements: + i.parentNode.removeChild(i); + + return True; + +def gen_manifest(template_root_dir, target_cfg, jid, + update_url=None, bootstrap=True, enable_mobile=False): + install_rdf = os.path.join(template_root_dir, "install.rdf") + manifest = RDFManifest(install_rdf) + dom = manifest.dom + + manifest.set("em:id", jid) + manifest.set("em:version", + target_cfg.get('version', '1.0')) + manifest.set("em:name", + target_cfg.get('title', target_cfg.get('fullName', target_cfg['name']))) + manifest.set("em:description", + target_cfg.get("description", "")) + manifest.set("em:creator", + target_cfg.get("author", "")) + manifest.set("em:bootstrap", str(bootstrap).lower()) + # XPIs remain packed by default, but package.json can override that. The + # RDF format accepts "true" as True, anything else as False. We expect + # booleans in the .json file, not strings. + manifest.set("em:unpack", "true" if target_cfg.get("unpack") else "false") + + if target_cfg.get('hasEmbeddedWebExtension', False): + elem = dom.createElement("em:hasEmbeddedWebExtension"); + elem.appendChild(dom.createTextNode("true")) + dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem) + + for translator in target_cfg.get("translators", [ ]): + elem = dom.createElement("em:translator"); + elem.appendChild(dom.createTextNode(translator)) + dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem) + + for developer in target_cfg.get("developers", [ ]): + elem = dom.createElement("em:developer"); + elem.appendChild(dom.createTextNode(developer)) + dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem) + + for contributor in target_cfg.get("contributors", [ ]): + elem = dom.createElement("em:contributor"); + elem.appendChild(dom.createTextNode(contributor)) + dom.documentElement.getElementsByTagName("Description")[0].appendChild(elem) + + if update_url: + manifest.set("em:updateURL", update_url) + else: + manifest.remove("em:updateURL") + + if target_cfg.get("preferences"): + manifest.set("em:optionsType", "2") + + # workaround until bug 971249 is fixed + # https://bugzilla.mozilla.org/show_bug.cgi?id=971249 + manifest.set("em:optionsURL", "data:text/xml,<placeholder/>") + + # workaround for workaround, for testing simple-prefs-regression + if (os.path.exists(os.path.join(template_root_dir, "options.xul"))): + manifest.remove("em:optionsURL") + else: + manifest.remove("em:optionsType") + manifest.remove("em:optionsURL") + + if enable_mobile: + target_app = dom.createElement("em:targetApplication") + dom.documentElement.getElementsByTagName("Description")[0].appendChild(target_app) + + ta_desc = dom.createElement("Description") + target_app.appendChild(ta_desc) + + elem = dom.createElement("em:id") + elem.appendChild(dom.createTextNode("{aa3c5121-dab2-40e2-81ca-7ea25febc110}")) + ta_desc.appendChild(elem) + + elem = dom.createElement("em:minVersion") + elem.appendChild(dom.createTextNode("26.0")) + ta_desc.appendChild(elem) + + elem = dom.createElement("em:maxVersion") + elem.appendChild(dom.createTextNode("30.0a1")) + ta_desc.appendChild(elem) + + if target_cfg.get("homepage"): + manifest.set("em:homepageURL", target_cfg.get("homepage")) + else: + manifest.remove("em:homepageURL") + + return manifest + +if __name__ == "__main__": + print "Running smoke test." + root = os.path.join(os.path.dirname(__file__), '../../app-extension') + manifest = gen_manifest(root, {'name': 'test extension'}, + 'fakeid', 'http://foo.com/update.rdf') + update = RDFUpdate() + update.add(manifest, "https://foo.com/foo.xpi") + exercise_str = str(manifest) + str(update) + for tagname in ["em:targetApplication", "em:version", "em:id"]: + if not len(update.dom.getElementsByTagName(tagname)): + raise Exception("tag does not exist: %s" % tagname) + if not update.dom.getElementsByTagName(tagname)[0].firstChild: + raise Exception("tag has no children: %s" % tagname) + print "Success!" diff --git a/addon-sdk/source/python-lib/cuddlefish/runner.py b/addon-sdk/source/python-lib/cuddlefish/runner.py new file mode 100644 index 000000000..7f337cc03 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/runner.py @@ -0,0 +1,767 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import sys +import time +import tempfile +import atexit +import shlex +import subprocess +import re +import shutil + +import mozrunner +from cuddlefish.prefs import DEFAULT_COMMON_PREFS +from cuddlefish.prefs import DEFAULT_FIREFOX_PREFS +from cuddlefish.prefs import DEFAULT_THUNDERBIRD_PREFS +from cuddlefish.prefs import DEFAULT_FENNEC_PREFS +from cuddlefish.prefs import DEFAULT_NO_CONNECTIONS_PREFS +from cuddlefish.prefs import DEFAULT_TEST_PREFS + +# Used to remove noise from ADB output +CLEANUP_ADB = re.compile(r'^(I|E)/(stdout|stderr|GeckoConsole)\s*\(\s*\d+\):\s*(.*)$') +# Used to filter only messages send by `console` module +FILTER_ONLY_CONSOLE_FROM_ADB = re.compile(r'^I/(stdout|stderr)\s*\(\s*\d+\):\s*((info|warning|error|debug): .*)$') + +# Used to detect the currently running test +PARSEABLE_TEST_NAME = re.compile(r'TEST-START \| ([^\n]+)\n') + +# Maximum time we'll wait for tests to finish, in seconds. +# The purpose of this timeout is to recover from infinite loops. It should be +# longer than the amount of time any test run takes, including those on slow +# machines running slow (debug) versions of Firefox. +RUN_TIMEOUT = 5400 #1.5 hours (1.5 * 60 * 60 sec) + +# Maximum time we'll wait for tests to emit output, in seconds. +# The purpose of this timeout is to recover from hangs. It should be longer +# than the amount of time any test takes to report results. +OUTPUT_TIMEOUT = 300 #five minutes (60 * 5 sec) + +def follow_file(filename): + """ + Generator that yields the latest unread content from the given + file, or None if no new content is available. + + For example: + + >>> f = open('temp.txt', 'w') + >>> f.write('hello') + >>> f.flush() + >>> tail = follow_file('temp.txt') + >>> tail.next() + 'hello' + >>> tail.next() is None + True + >>> f.write('there') + >>> f.flush() + >>> tail.next() + 'there' + >>> f.close() + >>> os.remove('temp.txt') + """ + + last_pos = 0 + last_size = 0 + while True: + newstuff = None + if os.path.exists(filename): + size = os.stat(filename).st_size + if size > last_size: + last_size = size + f = open(filename, 'r') + f.seek(last_pos) + newstuff = f.read() + last_pos = f.tell() + f.close() + yield newstuff + +# subprocess.check_output only appeared in python2.7, so this code is taken +# from python source code for compatibility with py2.5/2.6 +class CalledProcessError(Exception): + def __init__(self, returncode, cmd, output=None): + self.returncode = returncode + self.cmd = cmd + self.output = output + def __str__(self): + return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) + +def check_output(*popenargs, **kwargs): + if 'stdout' in kwargs: + raise ValueError('stdout argument not allowed, it will be overridden.') + process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) + output, unused_err = process.communicate() + retcode = process.poll() + if retcode: + cmd = kwargs.get("args") + if cmd is None: + cmd = popenargs[0] + raise CalledProcessError(retcode, cmd, output=output) + return output + + +class FennecProfile(mozrunner.Profile): + preferences = {} + names = ['fennec'] + +FENNEC_REMOTE_PATH = '/mnt/sdcard/jetpack-profile' + +class RemoteFennecRunner(mozrunner.Runner): + profile_class = FennecProfile + + names = ['fennec'] + + _INTENT_PREFIX = 'org.mozilla.' + + _adb_path = None + + def __init__(self, binary=None, **kwargs): + # Check that we have a binary set + if not binary: + raise ValueError("You have to define `--binary` option set to the " + "path to your ADB executable.") + # Ensure that binary refer to a valid ADB executable + output = subprocess.Popen([binary], stdout=subprocess.PIPE, + stderr=subprocess.PIPE).communicate() + output = "".join(output) + if not ("Android Debug Bridge" in output): + raise ValueError("`--binary` option should be the path to your " + "ADB executable.") + self.binary = binary + + mobile_app_name = kwargs['cmdargs'][0] + self.profile = kwargs['profile'] + self._adb_path = binary + + # This pref has to be set to `false` otherwise, we do not receive + # output of adb commands! + subprocess.call([self._adb_path, "shell", + "setprop log.redirect-stdio false"]) + + # Android apps are launched by their "intent" name, + # Automatically detect already installed firefox by using `pm` program + # or use name given as cfx `--mobile-app` argument. + intents = self.getIntentNames() + if not intents: + raise ValueError("Unable to find any Firefox " + "application on your device.") + elif mobile_app_name: + if not mobile_app_name in intents: + raise ValueError("Unable to find Firefox application " + "with intent name '%s'\n" + "Available ones are: %s" % + (mobile_app_name, ", ".join(intents))) + self._intent_name = self._INTENT_PREFIX + mobile_app_name + else: + if "firefox" in intents: + self._intent_name = self._INTENT_PREFIX + "firefox" + elif "firefox_beta" in intents: + self._intent_name = self._INTENT_PREFIX + "firefox_beta" + elif "firefox_nightly" in intents: + self._intent_name = self._INTENT_PREFIX + "firefox_nightly" + else: + self._intent_name = self._INTENT_PREFIX + intents[0] + + print "Launching mobile application with intent name " + self._intent_name + + # First try to kill firefox if it is already running + pid = self.getProcessPID(self._intent_name) + if pid != None: + print "Killing running Firefox instance ..." + subprocess.call([self._adb_path, "shell", + "am force-stop " + self._intent_name]) + time.sleep(7) + # It appears recently that the PID still exists even after + # Fennec closes, so removing this error still allows the tests + # to pass as the new Fennec instance is able to start. + # Leaving error in but commented out for now. + # + #if self.getProcessPID(self._intent_name) != None: + # raise Exception("Unable to automatically kill running Firefox" + + # " instance. Please close it manually before " + + # "executing cfx.") + + print "Pushing the addon to your device" + + # Create a clean empty profile on the sd card + subprocess.call([self._adb_path, "shell", "rm -r " + FENNEC_REMOTE_PATH]) + subprocess.call([self._adb_path, "shell", "mkdir " + FENNEC_REMOTE_PATH]) + + # Push the profile folder created by mozrunner to the device + # (we can't simply use `adb push` as it doesn't copy empty folders) + localDir = self.profile.profile + remoteDir = FENNEC_REMOTE_PATH + for root, dirs, files in os.walk(localDir, followlinks='true'): + relRoot = os.path.relpath(root, localDir) + # Note about os.path usage below: + # Local files may be using Windows `\` separators but + # remote are always `/`, so we need to convert local ones to `/` + for file in files: + localFile = os.path.join(root, file) + remoteFile = remoteDir.replace("/", os.sep) + if relRoot != ".": + remoteFile = os.path.join(remoteFile, relRoot) + remoteFile = os.path.join(remoteFile, file) + remoteFile = "/".join(remoteFile.split(os.sep)) + subprocess.Popen([self._adb_path, "push", localFile, remoteFile], + stderr=subprocess.PIPE).wait() + for dir in dirs: + targetDir = remoteDir.replace("/", os.sep) + if relRoot != ".": + targetDir = os.path.join(targetDir, relRoot) + targetDir = os.path.join(targetDir, dir) + targetDir = "/".join(targetDir.split(os.sep)) + # `-p` option is not supported on all devices! + subprocess.call([self._adb_path, "shell", "mkdir " + targetDir]) + + @property + def command(self): + """Returns the command list to run.""" + return [self._adb_path, + "shell", + "am start " + + "-a android.activity.MAIN " + + "-n " + self._intent_name + "/" + self._intent_name + ".App " + + "--es args \"-profile " + FENNEC_REMOTE_PATH + "\"" + ] + + def start(self): + subprocess.call(self.command) + + def getProcessPID(self, processName): + p = subprocess.Popen([self._adb_path, "shell", "ps"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + line = p.stdout.readline() + while line: + columns = line.split() + pid = columns[1] + name = columns[-1] + line = p.stdout.readline() + if processName in name: + return pid + return None + + def getIntentNames(self): + p = subprocess.Popen([self._adb_path, "shell", "pm list packages"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + names = [] + for line in p.stdout.readlines(): + line = re.sub("(^package:)|\s", "", line) + if self._INTENT_PREFIX in line: + names.append(line.replace(self._INTENT_PREFIX, "")) + return names + + +class XulrunnerAppProfile(mozrunner.Profile): + preferences = {} + names = [] + +class XulrunnerAppRunner(mozrunner.Runner): + """ + Runner for any XULRunner app. Can use a Firefox binary in XULRunner + mode to execute the app, or can use XULRunner itself. Expects the + app's application.ini to be passed in as one of the items in + 'cmdargs' in the constructor. + + This class relies a lot on the particulars of mozrunner.Runner's + implementation, and does some unfortunate acrobatics to get around + some of the class' limitations/assumptions. + """ + + profile_class = XulrunnerAppProfile + + # This is a default, and will be overridden in the instance if + # Firefox is used in XULRunner mode. + names = ['xulrunner'] + + # Default location of XULRunner on OS X. + __DARWIN_PATH = "/Library/Frameworks/XUL.framework/xulrunner-bin" + __LINUX_PATH = "/usr/bin/xulrunner" + + # What our application.ini's path looks like if it's part of + # an "installed" XULRunner app on OS X. + __DARWIN_APP_INI_SUFFIX = '.app/Contents/Resources/application.ini' + + def __init__(self, binary=None, **kwargs): + if sys.platform == 'darwin' and binary and binary.endswith('.app'): + # Assume it's a Firefox app dir. + binary = os.path.join(binary, 'Contents/MacOS/firefox-bin') + + self.__app_ini = None + self.__real_binary = binary + + mozrunner.Runner.__init__(self, **kwargs) + + # See if we're using a genuine xulrunner-bin from the XULRunner SDK, + # or if we're being asked to use Firefox in XULRunner mode. + self.__is_xulrunner_sdk = 'xulrunner' in self.binary + + if sys.platform == 'linux2' and not self.env.get('LD_LIBRARY_PATH'): + self.env['LD_LIBRARY_PATH'] = os.path.dirname(self.binary) + + newargs = [] + for item in self.cmdargs: + if 'application.ini' in item: + self.__app_ini = item + else: + newargs.append(item) + self.cmdargs = newargs + + if not self.__app_ini: + raise ValueError('application.ini not found in cmdargs') + if not os.path.exists(self.__app_ini): + raise ValueError("file does not exist: '%s'" % self.__app_ini) + + if (sys.platform == 'darwin' and + self.binary == self.__DARWIN_PATH and + self.__app_ini.endswith(self.__DARWIN_APP_INI_SUFFIX)): + # If the application.ini is in an app bundle, then + # it could be inside an "installed" XULRunner app. + # If this is the case, use the app's actual + # binary instead of the XUL framework's, so we get + # a proper app icon, etc. + new_binary = '/'.join(self.__app_ini.split('/')[:-2] + + ['MacOS', 'xulrunner']) + if os.path.exists(new_binary): + self.binary = new_binary + + @property + def command(self): + """Returns the command list to run.""" + + if self.__is_xulrunner_sdk: + return [self.binary, self.__app_ini, '-profile', + self.profile.profile] + else: + return [self.binary, '-app', self.__app_ini, '-profile', + self.profile.profile] + + def __find_xulrunner_binary(self): + if sys.platform == 'darwin': + if os.path.exists(self.__DARWIN_PATH): + return self.__DARWIN_PATH + if sys.platform == 'linux2': + if os.path.exists(self.__LINUX_PATH): + return self.__LINUX_PATH + return None + + def find_binary(self): + # This gets called by the superclass constructor. It will + # always get called, even if a binary was passed into the + # constructor, because we want to have full control over + # what the exact setting of self.binary is. + + if not self.__real_binary: + self.__real_binary = self.__find_xulrunner_binary() + if not self.__real_binary: + dummy_profile = {} + runner = mozrunner.FirefoxRunner(profile=dummy_profile) + self.__real_binary = runner.find_binary() + self.names = runner.names + return self.__real_binary + +def set_overloaded_modules(env_root, app_type, addon_id, preferences, overloads): + # win32 file scheme needs 3 slashes + desktop_file_scheme = "file://" + if not env_root.startswith("/"): + desktop_file_scheme = desktop_file_scheme + "/" + + pref_prefix = "extensions.modules." + addon_id + ".path" + + # Set preferences that will map require prefix to a given path + for name, path in overloads.items(): + if len(name) == 0: + prefName = pref_prefix + else: + prefName = pref_prefix + "." + name + if app_type == "fennec-on-device": + # For testing on device, we have to copy overloaded files from fs + # to the device and use device path instead of local fs path. + # Actual copy of files if done after the call to Profile constructor + preferences[prefName] = "file://" + \ + FENNEC_REMOTE_PATH + "/overloads/" + name + else: + preferences[prefName] = desktop_file_scheme + \ + path.replace("\\", "/") + "/" + +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 + else: + preferences['browser.tabs.remote.autostart'] = False + preferences['browser.tabs.remote.autostart.1'] = False + preferences['browser.tabs.remote.autostart.2'] = False + + # 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) + 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 = "Jetpack startup" + + 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: + if not noquit: + runner.stop() + raise + else: + runner.wait(10) + # double kill - hack for bugs 942111, 1006043.. + try: + runner.stop() + except: + pass + 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 diff --git a/addon-sdk/source/python-lib/cuddlefish/templates.py b/addon-sdk/source/python-lib/cuddlefish/templates.py new file mode 100644 index 000000000..be6110e7a --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/templates.py @@ -0,0 +1,32 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#Template used by test-main.js +TEST_MAIN_JS = '''\ +var main = require("./main"); + +exports["test main"] = function(assert) { + assert.pass("Unit test running!"); +}; + +exports["test main async"] = function(assert, done) { + assert.pass("async Unit test running!"); + done(); +}; + +require("sdk/test").run(exports); +''' + +#Template used by package.json +PACKAGE_JSON = '''\ +{ + "name": "%(name)s", + "title": "%(title)s", + "id": "%(id)s", + "description": "a basic add-on", + "author": "", + "license": "MPL-2.0", + "version": "0.1" +} +''' diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/__init__.py b/addon-sdk/source/python-lib/cuddlefish/tests/__init__.py new file mode 100644 index 000000000..60e7e88b3 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/__init__.py @@ -0,0 +1,52 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import unittest +import doctest +import glob + +env_root = os.environ['CUDDLEFISH_ROOT'] + +def get_tests(): + import cuddlefish + import cuddlefish.tests + + tests = [] + packages = [cuddlefish, cuddlefish.tests] + for package in packages: + path = os.path.abspath(package.__path__[0]) + pynames = glob.glob(os.path.join(path, '*.py')) + for filename in pynames: + basename = os.path.basename(filename) + module_name = os.path.splitext(basename)[0] + full_name = "%s.%s" % (package.__name__, module_name) + module = __import__(full_name, fromlist=[package.__name__]) + + loader = unittest.TestLoader() + suite = loader.loadTestsFromModule(module) + for test in suite: + tests.append(test) + + finder = doctest.DocTestFinder() + doctests = finder.find(module) + for test in doctests: + if len(test.examples) > 0: + tests.append(doctest.DocTestCase(test)) + + return tests + +def run(verbose=False): + if verbose: + verbosity = 2 + else: + verbosity = 1 + + tests = get_tests() + suite = unittest.TestSuite(tests) + runner = unittest.TextTestRunner(verbosity=verbosity) + return runner.run(suite) + +if __name__ == '__main__': + run() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon.png diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/explicit-icon64.png diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js new file mode 100644 index 000000000..b7e0a1d5e --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/lib/main.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json new file mode 100644 index 000000000..8d56d74ba --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/explicit-icon/package.json @@ -0,0 +1,5 @@ +{ + "loader": "lib/main.js", + "icon": "explicit-icon.png", + "icon64": "explicit-icon64.png" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon.png diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/icon64.png diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js new file mode 100644 index 000000000..b7e0a1d5e --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/lib/main.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json new file mode 100644 index 000000000..3f0e2419f --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/implicit-icon/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/main.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js new file mode 100644 index 000000000..b7e0a1d5e --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/lib/main.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json new file mode 100644 index 000000000..3f0e2419f --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588119-files/packages/no-icon/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/main.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js new file mode 100644 index 000000000..b7e0a1d5e --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/lib/bar-loader.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json new file mode 100644 index 000000000..e83a9d422 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/bar/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/bar-loader.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js new file mode 100644 index 000000000..b7e0a1d5e --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/lib/foo-loader.js @@ -0,0 +1,4 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json new file mode 100644 index 000000000..4648df67e --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-588661-files/packages/foo/package.json @@ -0,0 +1,4 @@ +{ + "loader": "lib/foo-loader.js", + "dependencies": ["bar"] +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md new file mode 100644 index 000000000..54518d38d --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/docs/main.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +minimal docs diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js new file mode 100644 index 000000000..aeda0e7fb --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = function(options, callbacks) { + console.log("minimal"); + callbacks.quit(); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json new file mode 100644 index 000000000..45d409a74 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-611495-files/jspath-one/package.json @@ -0,0 +1,5 @@ +{ + "name": "jspath-one", + "author": "Jon Smith", + "description": "A package w/ a main module; can be built into an extension." +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md new file mode 100644 index 000000000..c92ddb880 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/doc/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js new file mode 100644 index 000000000..2daeb3512 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/lib/foo-loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json new file mode 100644 index 000000000..ccc61b29d --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/foo-loader.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js new file mode 100644 index 000000000..bd0cfa96f --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/commonjs-naming/test/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md new file mode 100644 index 000000000..c92ddb880 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/docs/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js new file mode 100644 index 000000000..2daeb3512 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/lib/foo-loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json new file mode 100644 index 000000000..ccc61b29d --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/foo-loader.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js new file mode 100644 index 000000000..bd0cfa96f --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-614712-files/packages/original-naming/tests/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md new file mode 100644 index 000000000..c92ddb880 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/doc/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js new file mode 100644 index 000000000..2daeb3512 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/foo.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js new file mode 100644 index 000000000..2daeb3512 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/lib/loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json new file mode 100644 index 000000000..c2d22aa6f --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/package.json @@ -0,0 +1,3 @@ +{ + "loader": "lib/loader.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js new file mode 100644 index 000000000..bd0cfa96f --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-lib/test/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/locale/emptyFolder b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/locale/emptyFolder new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/locale/emptyFolder diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-locale/package.json @@ -0,0 +1 @@ +{} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md new file mode 100644 index 000000000..c92ddb880 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/doc/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js new file mode 100644 index 000000000..2daeb3512 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/foo.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js new file mode 100644 index 000000000..2daeb3512 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json new file mode 100644 index 000000000..100249fed --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/package.json @@ -0,0 +1,3 @@ +{ + "loader": "loader.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js new file mode 100644 index 000000000..bd0cfa96f --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/default-root/test/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js new file mode 100644 index 000000000..2daeb3512 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/foo.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js new file mode 100644 index 000000000..2daeb3512 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/alt-lib/loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md new file mode 100644 index 000000000..c92ddb880 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/doc/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json new file mode 100644 index 000000000..f79250e6e --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/package.json @@ -0,0 +1,4 @@ +{ + "directories": { "lib": "./alt-lib" }, + "loader": "alt-lib/loader.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js new file mode 100644 index 000000000..bd0cfa96f --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-dir-lib/test/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js new file mode 100644 index 000000000..2daeb3512 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/foo.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js new file mode 100644 index 000000000..2daeb3512 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/alt2-lib/loader.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md new file mode 100644 index 000000000..c92ddb880 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/doc/foo.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +I am documentation for foo. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json new file mode 100644 index 000000000..b017baa86 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/package.json @@ -0,0 +1,4 @@ +{ + "lib": "./alt2-lib", + "loader": "alt2-lib/loader.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js new file mode 100644 index 000000000..bd0cfa96f --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-652227-files/packages/explicit-lib/test/test-foo.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.testThing = function(test) { + test.assertEqual(2, 1 + 1); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md b/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md new file mode 100644 index 000000000..54518d38d --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/docs/main.md @@ -0,0 +1,5 @@ +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +minimal docs diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js new file mode 100644 index 000000000..aeda0e7fb --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = function(options, callbacks) { + console.log("minimal"); + callbacks.quit(); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json new file mode 100644 index 000000000..de868f776 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-669274-files/packages/extra-options/package.json @@ -0,0 +1,6 @@ +{ + "name": "extra-options", + "author": "Jon Smith", + "description": "A package w/ a main module; can be built into an extension.", + "loader": "lib/main.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-1-pack/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-1-pack/package.json new file mode 100644 index 000000000..afa13e9e5 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-1-pack/package.json @@ -0,0 +1,10 @@ +{ + "name": "empty", + "license": "MPL-2.0", + "author": "", + "version": "0.1", + "title": "empty", + "id": "jid1-80fr8b6qeRlQSQ", + "unpack": false, + "description": "test unpack= support" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-2-unpack/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-2-unpack/package.json new file mode 100644 index 000000000..b1f76edcd --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-2-unpack/package.json @@ -0,0 +1,10 @@ +{ + "name": "empty", + "license": "MPL-2.0", + "author": "", + "version": "0.1", + "title": "empty", + "id": "jid1-80fr8b6qeRlQSQ", + "unpack": true, + "description": "test unpack= support" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-3-pack/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-3-pack/package.json new file mode 100644 index 000000000..e3dd9fb72 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-715340-files/pkg-3-pack/package.json @@ -0,0 +1,9 @@ +{ + "name": "empty", + "license": "MPL-2.0", + "author": "", + "version": "0.1", + "title": "empty", + "id": "jid1-80fr8b6qeRlQSQ", + "description": "test unpack= support" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/fullName/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/fullName/package.json new file mode 100644 index 000000000..2525a1497 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/fullName/package.json @@ -0,0 +1,9 @@ +{ + "name": "empty", + "license": "MPL-2.0", + "author": "", + "version": "0.1", + "fullName": "a long fullName", + "id": "jid1-80123", + "description": "test addon name fallback to 'fullName' key" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/none/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/none/package.json new file mode 100644 index 000000000..b05025c72 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/none/package.json @@ -0,0 +1,9 @@ +{ + "name": "a long none", + "license": "MPL-2.0", + "author": "", + "version": "0.1", + + "id": "jid1-80123", + "description": "test addon name falls back all the way to the 'name' key" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/title/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/title/package.json new file mode 100644 index 000000000..634817b7a --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/bug-906359-files/title/package.json @@ -0,0 +1,9 @@ +{ + "name": "empty", + "license": "MPL-2.0", + "author": "", + "version": "0.1", + "title": "a long title", + "id": "jid1-80123", + "description": "test addon name comes from the 'title' key" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js new file mode 100644 index 000000000..ad54ae76d --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar-e10s-adapter.js @@ -0,0 +1,11 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +if (this.sendMessage) { +} else { + require('bar'); + + exports.register = function(process) { + }; +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js new file mode 100644 index 000000000..fe9e4fb72 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/bar.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +require('chrome'); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js new file mode 100644 index 000000000..55633d167 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/lib/foo.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +require('bar'); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/e10s-adapter-files/packages/foo/package.json @@ -0,0 +1 @@ +{} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/lib/main.js new file mode 100644 index 000000000..e32a30f9c --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/lib/main.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = "'main' mainly reigns in main(.js)"; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/package.json new file mode 100644 index 000000000..98e4b85aa --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/five/package.json @@ -0,0 +1,3 @@ +{ "name": "five", + "main": "./lib/main" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js new file mode 100644 index 000000000..7e1ce7ea0 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/lib/misc.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = 42; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json new file mode 100644 index 000000000..3010fae71 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/package.json @@ -0,0 +1,4 @@ +{ "name": "four-a", + "directories": {"lib": "lib"}, + "main": "./topfiles/main.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js new file mode 100644 index 000000000..7e1ce7ea0 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four-deps/four-a/topfiles/main.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = 42; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/lib/main.js new file mode 100644 index 000000000..b95f7bd53 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/lib/main.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var a = require("four-a"); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/package.json new file mode 100644 index 000000000..53180b9f6 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/four/package.json @@ -0,0 +1,3 @@ +{ "name": "four", + "main": "main" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/main.js new file mode 100644 index 000000000..08b9245c5 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/main.js @@ -0,0 +1,9 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var panel = require("sdk/panel"); +var two = require("two.js"); +var a = require("./two"); +var b = require("sdk/tabs.js"); +var c = require("./subdir/three"); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js new file mode 100644 index 000000000..b594f3c68 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/subdir/three.js @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.foo = 1; +var main = require("../main"); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/two.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/two.js new file mode 100644 index 000000000..976521958 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/lib/two.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +// this ought to find our sibling, not packages/development-mode/lib/main.js +var main = require("main"); +exports.foo = 1; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/package.json new file mode 100644 index 000000000..edd2b17c8 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/one/package.json @@ -0,0 +1,4 @@ +{ "name": "one", + "id": "jid1@jetpack", + "main": "main" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/data/text.data b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/data/text.data new file mode 100644 index 000000000..1269488f7 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/data/text.data @@ -0,0 +1 @@ +data diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js new file mode 100644 index 000000000..b1d68d3a0 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/main.js @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var self = require("sdk/self"); // trigger inclusion of data +exports.main = function () { console.log("main"); }; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js new file mode 100644 index 000000000..a7b1c1449 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/lib/unused.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.unused = "just pretend I'm not here"; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/package.json new file mode 100644 index 000000000..922c77dfc --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/seven/package.json @@ -0,0 +1,4 @@ +{ + "name": "seven", + "id": "jid7" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js new file mode 100644 index 000000000..ada31ef8c --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/lib/unused.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.unused = "I am."; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/package.json new file mode 100644 index 000000000..906b24927 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/package.json @@ -0,0 +1,3 @@ +{ "name": "six", + "main": "./unreachable" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/unreachable.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/unreachable.js new file mode 100644 index 000000000..e8b229cde --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/six/unreachable.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = "I am outside lib/ and cannot be reached, yet"; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js new file mode 100644 index 000000000..54e4b7efb --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = 42; +require("./subdir/subfile"); +require("sdk/self"); // trigger inclusion of our data/ directory + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js new file mode 100644 index 000000000..aec24d01b --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/subdir/subfile.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = "I should be included in a subdir"; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js new file mode 100644 index 000000000..36c4a4e2b --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/lib/unused.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = "unused, linker should not include me in the XPI"; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/locale/fr-FR.properties b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/locale/fr-FR.properties new file mode 100644 index 000000000..980ac46c6 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/locale/fr-FR.properties @@ -0,0 +1,5 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +Yes= Oui diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json new file mode 100644 index 000000000..6b796fc33 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-a/package.json @@ -0,0 +1,3 @@ +{ "name": "three-a", + "main": "./lib/main.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js new file mode 100644 index 000000000..7e1ce7ea0 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/lib/main.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = 42; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/locale/fr-FR.properties b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/locale/fr-FR.properties new file mode 100644 index 000000000..c1bf1465f --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/locale/fr-FR.properties @@ -0,0 +1,6 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +No= Non +one= un diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json new file mode 100644 index 000000000..c0ff5ee0e --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-b/package.json @@ -0,0 +1,3 @@ +{ "name": "three-b", + "main": "./lib/main" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js new file mode 100644 index 000000000..7e1ce7ea0 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/main.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = 42; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js new file mode 100644 index 000000000..587849615 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/lib/sub/foo.js @@ -0,0 +1,6 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.foo = "you found me down here"; + diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/locale/fr-FR.properties b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/locale/fr-FR.properties new file mode 100644 index 000000000..dac3f133b --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/locale/fr-FR.properties @@ -0,0 +1,9 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +No= Nein +What?= Quoi? +plural=other +plural[one]=one +uft8_value=é diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json new file mode 100644 index 000000000..169c9146b --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three-deps/three-c/package.json @@ -0,0 +1,3 @@ +{ "name": "three-c", + "main": "lib/main" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/msg.txt b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/msg.txt new file mode 100644 index 000000000..3b18e512d --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/msg.txt @@ -0,0 +1 @@ +hello world diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/subdir/submsg.txt b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/subdir/submsg.txt new file mode 100644 index 000000000..d2cfe80de --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/data/subdir/submsg.txt @@ -0,0 +1 @@ +hello subdir diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/lib/main.js new file mode 100644 index 000000000..4f5944374 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +var a = require("three-a"); +var b = require("three-b"); +var c = require("three-c"); +var c3 = require("three-c/sub/foo"); diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/package.json new file mode 100644 index 000000000..cbfbc5bb2 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/package.json @@ -0,0 +1,3 @@ +{ "name": "three", + "main": "main" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/nontest.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/nontest.js new file mode 100644 index 000000000..edbc08ef2 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/nontest.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// dummy diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-one.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-one.js new file mode 100644 index 000000000..edbc08ef2 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-one.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// dummy diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-two.js b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-two.js new file mode 100644 index 000000000..edbc08ef2 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/linker-files/three/tests/test-two.js @@ -0,0 +1,5 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// dummy diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/doc/main.md diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me new file mode 100644 index 000000000..014242c92 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/ignore_me @@ -0,0 +1,3 @@ +The docs processor should tolerate (by ignoring) random non-.js files in lib +directories, such as those left around by editors, version-control systems, +or OS metadata like .DS_Store . This file exercises that tolerance. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js new file mode 100644 index 000000000..026487257 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = function(options, callbacks) { + console.log("1 + 1 =", require("bar-module").add(1, 1)); + callbacks.quit(); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too new file mode 100644 index 000000000..066f9b55d --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/lib/surprise.js/ignore_me_too @@ -0,0 +1,2 @@ +The docs processor should also ignore directories named *.js, and their +contents. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json new file mode 100644 index 000000000..afb5698e9 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/aardvark/package.json @@ -0,0 +1,7 @@ +{ + "author": "Jon Smith", + "description": "A package w/ a main module; can be built into an extension.", + "keywords": ["potato"], + "version": "1.0", + "dependencies": ["addon-sdk", "barbeque"] +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js new file mode 100644 index 000000000..026487257 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = function(options, callbacks) { + console.log("1 + 1 =", require("bar-module").add(1, 1)); + callbacks.quit(); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json new file mode 100644 index 000000000..9684581da --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/anteater_files/package.json @@ -0,0 +1,8 @@ +{ + "name": "anteater", + "author": "Jon Smith", + "description": "A package w/ a main module; can be built into an extension.", + "keywords": ["potato"], + "version": "1.0", + "dependencies": ["addon-sdk", "barbeque"] +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js new file mode 100644 index 000000000..361846d27 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/lib/loader.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This module will be imported by the XPCOM harness/boostrapper +// via Components.utils.import() and is responsible for creating a +// CommonJS module loader. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json new file mode 100644 index 000000000..64eb065be --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/api-utils/package.json @@ -0,0 +1,5 @@ +{ + "description": "A foundational package that provides a CommonJS module loader implementation.", + "keywords": ["potato", "jetpack-low-level"], + "loader": "lib/loader.js" +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js new file mode 100644 index 000000000..ff982ae20 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/lib/bar-module.js @@ -0,0 +1,7 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.add = function add(a, b) { + return a + b; +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json new file mode 100644 index 000000000..62e3c12d7 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/barbeque/package.json @@ -0,0 +1,4 @@ +{ + "keywords": ["potato", "jetpack-low-level"], + "description": "A package used by 'aardvark' as a library." +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js new file mode 100644 index 000000000..aeda0e7fb --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/lib/main.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = function(options, callbacks) { + console.log("minimal"); + callbacks.quit(); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json new file mode 100644 index 000000000..530f3c247 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/minimal/package.json @@ -0,0 +1,4 @@ +{ + "author": "Jon Smith", + "description": "A package w/ a main module; can be built into an extension." +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/docs/third_party.md b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/docs/third_party.md new file mode 100644 index 000000000..35152f601 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/docs/third_party.md @@ -0,0 +1 @@ +hello, I'm a third party.
\ No newline at end of file diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/lib/third-party.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/lib/third-party.js new file mode 100644 index 000000000..026487257 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/lib/third-party.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +exports.main = function(options, callbacks) { + console.log("1 + 1 =", require("bar-module").add(1, 1)); + callbacks.quit(); +}; diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/package.json b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/package.json new file mode 100644 index 000000000..afb5698e9 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/packages/third_party/package.json @@ -0,0 +1,7 @@ +{ + "author": "Jon Smith", + "description": "A package w/ a main module; can be built into an extension.", + "keywords": ["potato"], + "version": "1.0", + "dependencies": ["addon-sdk", "barbeque"] +} diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js new file mode 100644 index 000000000..a20bf3fe6 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/static-files/xpi-template/components/harness.js @@ -0,0 +1,8 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file contains XPCOM code that bootstraps an SDK-based add-on +// by loading its harness-options.json, registering all its resource +// directories, executing its loader, and then executing its program's +// main() function. diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py new file mode 100644 index 000000000..5ececfea6 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_init.py @@ -0,0 +1,211 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os, unittest, shutil +import zipfile +from StringIO import StringIO +from cuddlefish import initializer +from cuddlefish.templates import TEST_MAIN_JS, PACKAGE_JSON + +tests_path = os.path.abspath(os.path.dirname(__file__)) + +class TestInit(unittest.TestCase): + + def run_init_in_subdir(self, dirname, f, *args, **kwargs): + top = os.path.abspath(os.getcwd()) + basedir = os.path.abspath(os.path.join(".test_tmp",self.id(),dirname)) + if os.path.isdir(basedir): + assert basedir.startswith(top) + shutil.rmtree(basedir) + os.makedirs(basedir) + try: + os.chdir(basedir) + return f(basedir, *args, **kwargs) + finally: + os.chdir(top) + + def do_test_init(self,basedir): + # Let's init the addon, no error admitted + f = open(".ignoreme","w") + f.write("stuff") + f.close() + + out, err = StringIO(), StringIO() + init_run = initializer(None, ["init"], out, err) + out, err = out.getvalue(), err.getvalue() + self.assertEqual(init_run["result"], 0) + self.assertTrue("* lib directory created" in out) + self.assertTrue("* data directory created" in out) + self.assertTrue("Have fun!" in out) + self.assertEqual(err,"") + self.assertTrue(len(os.listdir(basedir))>0) + main_js = os.path.join(basedir,"lib","main.js") + package_json = os.path.join(basedir,"package.json") + test_main_js = os.path.join(basedir,"test","test-main.js") + self.assertTrue(os.path.exists(main_js)) + self.assertTrue(os.path.exists(package_json)) + self.assertTrue(os.path.exists(test_main_js)) + self.assertEqual(open(main_js,"r").read(),"") + self.assertEqual(open(package_json,"r").read() % {"id":"tmp_addon_id" }, + PACKAGE_JSON % {"name":"tmp_addon_sample", + "title": "tmp_addon_SAMPLE", + "id":init_run["jid"] }) + self.assertEqual(open(test_main_js,"r").read(),TEST_MAIN_JS) + + # Let's check that the addon is initialized + out, err = StringIO(), StringIO() + init_run = initializer(None, ["init"], out, err) + out, err = out.getvalue(), err.getvalue() + self.failIfEqual(init_run["result"],0) + self.assertTrue("This command must be run in an empty directory." in err) + + def test_initializer(self): + self.run_init_in_subdir("tmp_addon_SAMPLE",self.do_test_init) + + def do_test_args(self, basedir): + # check that running it with spurious arguments will fail + out,err = StringIO(), StringIO() + init_run = initializer(None, ["init", "specified-dirname", "extra-arg"], out, err) + out, err = out.getvalue(), err.getvalue() + self.failIfEqual(init_run["result"], 0) + self.assertTrue("Too many arguments" in err) + + def test_args(self): + self.run_init_in_subdir("tmp_addon_sample", self.do_test_args) + + def _test_existing_files(self, basedir): + f = open("pay_attention_to_me","w") + f.write("stuff") + f.close() + out,err = StringIO(), StringIO() + rc = initializer(None, ["init"], out, err) + out, err = out.getvalue(), err.getvalue() + self.assertEqual(rc["result"], 1) + self.failUnless("This command must be run in an empty directory" in err, + err) + self.failIf(os.path.exists("lib")) + + def test_existing_files(self): + self.run_init_in_subdir("existing_files", self._test_existing_files) + + def test_init_subdir(self): + parent = os.path.abspath(os.path.join(".test_tmp", self.id())) + basedir = os.path.join(parent, "init-basedir") + if os.path.exists(parent): + shutil.rmtree(parent) + os.makedirs(parent) + + # if the basedir exists and is not empty, init should refuse + os.makedirs(basedir) + f = open(os.path.join(basedir, "boo"), "w") + f.write("stuff") + f.close() + out, err = StringIO(), StringIO() + rc = initializer(None, ["init", basedir], out, err) + out, err = out.getvalue(), err.getvalue() + self.assertEqual(rc["result"], 1) + self.assertTrue("testing if directory is empty" in out, out) + self.assertTrue("This command must be run in an empty directory." in err, + err) + + # a .dotfile should be tolerated + os.rename(os.path.join(basedir, "boo"), os.path.join(basedir, ".phew")) + out, err = StringIO(), StringIO() + rc = initializer(None, ["init", basedir], out, err) + out, err = out.getvalue(), err.getvalue() + self.assertEqual(rc["result"], 0) + self.assertTrue("* data directory created" in out, out) + self.assertTrue("Have fun!" in out) + self.assertEqual(err,"") + self.assertTrue(os.listdir(basedir)) + main_js = os.path.join(basedir,"lib","main.js") + package_json = os.path.join(basedir,"package.json") + self.assertTrue(os.path.exists(main_js)) + self.assertTrue(os.path.exists(package_json)) + shutil.rmtree(basedir) + + # init should create directories that don't exist already + out, err = StringIO(), StringIO() + rc = initializer(None, ["init", basedir], out, err) + out, err = out.getvalue(), err.getvalue() + self.assertEqual(rc["result"], 0) + self.assertTrue("* data directory created" in out) + self.assertTrue("Have fun!" in out) + self.assertEqual(err,"") + self.assertTrue(os.listdir(basedir)) + main_js = os.path.join(basedir,"lib","main.js") + package_json = os.path.join(basedir,"package.json") + self.assertTrue(os.path.exists(main_js)) + self.assertTrue(os.path.exists(package_json)) + + +class TestCfxQuits(unittest.TestCase): + + def run_cfx(self, addon_path, command): + old_cwd = os.getcwd() + os.chdir(addon_path) + import sys + old_stdout = sys.stdout + old_stderr = sys.stderr + sys.stdout = out = StringIO() + sys.stderr = err = StringIO() + rc = 0 + try: + import cuddlefish + args = list(command) + # Pass arguments given to cfx so that cfx can find firefox path + # if --binary option is given: + args.extend(sys.argv[1:]) + cuddlefish.run(arguments=args) + except SystemExit, e: + if "code" in e: + rc = e.code + elif "args" in e and len(e.args)>0: + rc = e.args[0] + else: + rc = 0 + finally: + sys.stdout = old_stdout + sys.stderr = old_stderr + os.chdir(old_cwd) + out.flush() + err.flush() + return rc, out.getvalue(), err.getvalue() + + # this method doesn't exists in python 2.5, + # implements our own + def assertIn(self, member, container): + """Just like self.assertTrue(a in b), but with a nicer default message.""" + if member not in container: + standardMsg = '"%s" not found in "%s"' % (member, + container) + self.fail(standardMsg) + + def test_cfx_init(self): + # Create an empty test directory + addon_path = os.path.abspath(os.path.join(".test_tmp", "test-cfx-init")) + if os.path.isdir(addon_path): + shutil.rmtree(addon_path) + os.makedirs(addon_path) + + # Fake a call to cfx init + old_cwd = os.getcwd() + os.chdir(addon_path) + out, err = StringIO(), StringIO() + rc = initializer(None, ["init"], out, err) + os.chdir(old_cwd) + out, err = out.getvalue(), err.getvalue() + self.assertEqual(rc["result"], 0) + self.assertTrue("Have fun!" in out) + self.assertEqual(err,"") + + # run cfx test + rc, out, err = self.run_cfx(addon_path, ["test"]) + self.assertEqual(rc, 0) + self.assertIn("6 of 6 tests passed.", err) + self.assertIn("Program terminated successfully.", err) + + +if __name__ == "__main__": + unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_licenses.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_licenses.py new file mode 100644 index 000000000..574812ada --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_licenses.py @@ -0,0 +1,100 @@ + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import unittest +import os.path + +parent = os.path.dirname +test_dir = parent(os.path.abspath(__file__)) +sdk_root = parent(parent(parent(test_dir))) + +def from_sdk_top(fn): + return os.path.abspath(os.path.join(sdk_root, fn)) + +MPL2_URL = "http://mozilla.org/MPL/2.0/" + +# These files all come with their own license headers +skip = [ + "python-lib/cuddlefish/_version.py", # generated, public domain + "doc/static-files/js/jquery.js", # MIT/GPL dual + "examples/annotator/data/jquery-1.4.2.min.js", # MIT/GPL dual + "examples/reddit-panel/data/jquery-1.4.4.min.js", # MIT/GPL dual + "examples/library-detector/data/library-detector.js", # MIT + "python-lib/mozrunner/killableprocess.py", # MIT? BSDish? + "python-lib/mozrunner/winprocess.py", # MIT + "packages/api-utils/tests/test-querystring.js", # MIT + "packages/api-utils/lib/promise.js", # MIT + "packages/api-utils/tests/test-promise.js", # MIT + "examples/actor-repl/README.md", # It's damn readme file + "examples/actor-repl/data/codemirror-compressed.js", # MIT + "examples/actor-repl/data/codemirror.css", # MIT + ] +absskip = [from_sdk_top(os.path.join(*fn.split("/"))) for fn in skip] + +class Licenses(unittest.TestCase): + def test(self): + # Examine most SDK files to check if they've got an MPL2 license + # header. We exclude some files that are known to include different + # licenses. + self.missing = [] + self.scan_file(from_sdk_top(os.path.join("python-lib", "jetpack_sdk_env.py"))) + self.scan(os.path.join("python-lib", "cuddlefish"), [".js", ".py"], + skipdirs=["sdk-docs"], # test_generate.py makes this + ) + self.scan(os.path.join("python-lib", "mozrunner"), [".py"]) + self.scan("lib", [".js", ".jsm", ".css"], + skipdirs=[ + "diffpatcher", # MIT + "method", # MIT + "child_process", # MPL 1.1/GPL 2.0/LGPL 2.1 + "fs" # MIT + ]) + self.scan("test", [".js", ".jsm", ".css", ".html"], + skipdirs=[ + "buffers", # MIT + "querystring", # MIT + "path" # MIT + ]) + self.scan("modules", [".js", ".jsm"]) + self.scan("examples", [".js", ".css", ".html", ".md"]) + self.scan("bin", [".bat", ".ps1", ".js"]) + for fn in [os.path.join("bin", "activate"), + os.path.join("bin", "cfx"), + os.path.join("bin", "integration-scripts", "buildbot-run-cfx-helper"), + os.path.join("bin", "integration-scripts", "integration-check"), + ]: + self.scan_file(from_sdk_top(fn)) + + if self.missing: + print + print "The following files are missing an MPL2 header:" + for fn in sorted(self.missing): + print " "+fn + self.fail("%d files are missing an MPL2 header" % len(self.missing)) + + def scan(self, start, extensions=[], skipdirs=[]): + # scan a whole subdirectory + start = from_sdk_top(start) + for root, dirs, files in os.walk(start): + for d in skipdirs: + if d in dirs: + dirs.remove(d) + for fn in files: + ext = os.path.splitext(fn)[1] + if extensions and ext not in extensions: + continue + absfn = os.path.join(root, fn) + if absfn in absskip: + continue + self.scan_file(absfn) + + def scan_file(self, fn): + # scan a single file + if not MPL2_URL in open(fn, "r").read(): + relfile = fn[len(sdk_root)+1:] + self.missing.append(relfile) + +if __name__ == '__main__': + unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py new file mode 100755 index 000000000..7a6fd12e4 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_linker.py @@ -0,0 +1,247 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os.path +import shutil +import zipfile +from StringIO import StringIO +import simplejson as json +import unittest +import cuddlefish +from cuddlefish import packaging, manifest + +def up(path, generations=1): + for i in range(generations): + path = os.path.dirname(path) + return path + +ROOT = up(os.path.abspath(__file__), 4) +def get_linker_files_dir(name): + return os.path.join(up(os.path.abspath(__file__)), "linker-files", name) + +class Basic(unittest.TestCase): + def get_pkg(self, name): + d = get_linker_files_dir(name) + return packaging.get_config_in_dir(d) + + def test_deps(self): + target_cfg = self.get_pkg("one") + pkg_cfg = packaging.build_config(ROOT, target_cfg) + deps = packaging.get_deps_for_targets(pkg_cfg, ["one"]) + self.failUnlessEqual(deps, ["one"]) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-sdk"]) + self.failUnlessEqual(deps, ["addon-sdk", "one"]) + + def test_manifest(self): + target_cfg = self.get_pkg("one") + pkg_cfg = packaging.build_config(ROOT, target_cfg) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-sdk"]) + self.failUnlessEqual(deps, ["addon-sdk", "one"]) + # target_cfg.dependencies is not provided, so we'll search through + # all known packages (everything in 'deps'). + m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) + m = m.get_harness_options_manifest(False) + + def assertReqIs(modname, reqname, path): + reqs = m["one/%s" % modname]["requirements"] + self.failUnlessEqual(reqs[reqname], path) + + assertReqIs("main", "sdk/panel", "sdk/panel") + assertReqIs("main", "two.js", "one/two") + assertReqIs("main", "./two", "one/two") + assertReqIs("main", "sdk/tabs.js", "sdk/tabs") + assertReqIs("main", "./subdir/three", "one/subdir/three") + assertReqIs("two", "main", "one/main") + assertReqIs("subdir/three", "../main", "one/main") + + target_cfg.dependencies = [] + + try: + # this should now work, as we ignore missing modules by default + m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) + m = m.get_harness_options_manifest(False) + + assertReqIs("main", "sdk/panel", "sdk/panel") + # note that with "addon-sdk" dependency present, + # "sdk/tabs.js" mapped to "sdk/tabs", but without, + # we just get the default (identity) mapping + assertReqIs("main", "sdk/tabs.js", "sdk/tabs.js") + except Exception, e: + self.fail("Must not throw from build_manifest() if modules are missing") + + # now, because .dependencies *is* provided, we won't search 'deps', + # and stop_on_missing is True, we'll get a link error + self.assertRaises(manifest.ModuleNotFoundError, + manifest.build_manifest, + target_cfg, pkg_cfg, deps, scan_tests=False, + abort_on_missing=True) + + def test_main_in_deps(self): + target_cfg = self.get_pkg("three") + package_path = [get_linker_files_dir("three-deps")] + pkg_cfg = packaging.build_config(ROOT, target_cfg, + packagepath=package_path) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-sdk"]) + self.failUnlessEqual(deps, ["addon-sdk", "three"]) + m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) + m = m.get_harness_options_manifest(False) + def assertReqIs(modname, reqname, path): + reqs = m["three/%s" % modname]["requirements"] + self.failUnlessEqual(reqs[reqname], path) + assertReqIs("main", "three-a", "three-a/main") + assertReqIs("main", "three-b", "three-b/main") + assertReqIs("main", "three-c", "three-c/main") + + def test_relative_main_in_top(self): + target_cfg = self.get_pkg("five") + package_path = [] + pkg_cfg = packaging.build_config(ROOT, target_cfg, + packagepath=package_path) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-sdk"]) + self.failUnlessEqual(deps, ["addon-sdk", "five"]) + # all we care about is that this next call doesn't raise an exception + m = manifest.build_manifest(target_cfg, pkg_cfg, deps, scan_tests=False) + m = m.get_harness_options_manifest(False) + reqs = m["five/main"]["requirements"] + self.failUnlessEqual(reqs, {}); + + def test_unreachable_relative_main_in_top(self): + target_cfg = self.get_pkg("six") + package_path = [] + pkg_cfg = packaging.build_config(ROOT, target_cfg, + packagepath=package_path) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-sdk"]) + self.failUnlessEqual(deps, ["addon-sdk", "six"]) + self.assertRaises(manifest.UnreachablePrefixError, + manifest.build_manifest, + target_cfg, pkg_cfg, deps, scan_tests=False) + + def test_unreachable_in_deps(self): + target_cfg = self.get_pkg("four") + package_path = [get_linker_files_dir("four-deps")] + pkg_cfg = packaging.build_config(ROOT, target_cfg, + packagepath=package_path) + deps = packaging.get_deps_for_targets(pkg_cfg, + [target_cfg.name, "addon-sdk"]) + self.failUnlessEqual(deps, ["addon-sdk", "four"]) + self.assertRaises(manifest.UnreachablePrefixError, + manifest.build_manifest, + target_cfg, pkg_cfg, deps, scan_tests=False) + +class Contents(unittest.TestCase): + + def run_in_subdir(self, dirname, f, *args, **kwargs): + top = os.path.abspath(os.getcwd()) + basedir = os.path.abspath(os.path.join(".test_tmp",self.id(),dirname)) + if os.path.isdir(basedir): + assert basedir.startswith(top) + shutil.rmtree(basedir) + os.makedirs(basedir) + try: + os.chdir(basedir) + return f(basedir, *args, **kwargs) + finally: + os.chdir(top) + + def assertIn(self, what, inside_what): + self.failUnless(what in inside_what, inside_what) + + def test_jetpackID(self): + # this uses "id": "jid7", to which a @jetpack should be appended + seven = get_linker_files_dir("seven") + def _test(basedir): + stdout = StringIO() + shutil.copytree(seven, "seven") + os.chdir("seven") + try: + # regrettably, run() always finishes with sys.exit() + cuddlefish.run(["xpi", "--no-strip-xpi"], + stdout=stdout) + except SystemExit, e: + self.failUnlessEqual(e.args[0], 0) + zf = zipfile.ZipFile("seven.xpi", "r") + hopts = json.loads(zf.read("harness-options.json")) + self.failUnlessEqual(hopts["jetpackID"], "jid7@jetpack") + self.run_in_subdir("x", _test) + + def test_jetpackID_suffix(self): + # this uses "id": "jid1@jetpack", so no suffix should be appended + one = get_linker_files_dir("one") + def _test(basedir): + stdout = StringIO() + shutil.copytree(one, "one") + os.chdir("one") + try: + # regrettably, run() always finishes with sys.exit() + cuddlefish.run(["xpi", "--no-strip-xpi"], + stdout=stdout) + except SystemExit, e: + self.failUnlessEqual(e.args[0], 0) + zf = zipfile.ZipFile("one.xpi", "r") + hopts = json.loads(zf.read("harness-options.json")) + self.failUnlessEqual(hopts["jetpackID"], "jid1@jetpack") + self.run_in_subdir("x", _test) + + def test_strip_default(self): + seven = get_linker_files_dir("seven") + # now run 'cfx xpi' in that directory, except put the generated .xpi + # elsewhere + def _test(basedir): + stdout = StringIO() + shutil.copytree(seven, "seven") + os.chdir("seven") + try: + # regrettably, run() always finishes with sys.exit() + cuddlefish.run(["xpi"], # --strip-xpi is now the default + stdout=stdout) + except SystemExit, e: + self.failUnlessEqual(e.args[0], 0) + zf = zipfile.ZipFile("seven.xpi", "r") + names = zf.namelist() + # problem found in bug 664840 was that an addon + # without an explicit tests/ directory would copy all files from + # the package into a bogus JID-PKGNAME-tests/ directory, so check + # for that + testfiles = [fn for fn in names if "seven/tests" in fn] + self.failUnlessEqual([], testfiles) + # another problem was that data files were being stripped from + # the XPI. Note that data/ is only supposed to be included if a + # module that actually gets used does a require("self") . + self.assertIn("resources/seven/data/text.data", + names) + self.failIf("seven/lib/unused.js" + in names, names) + self.run_in_subdir("x", _test) + + def test_no_strip(self): + seven = get_linker_files_dir("seven") + def _test(basedir): + stdout = StringIO() + shutil.copytree(seven, "seven") + os.chdir("seven") + try: + # regrettably, run() always finishes with sys.exit() + cuddlefish.run(["xpi", "--no-strip-xpi"], + stdout=stdout) + except SystemExit, e: + self.failUnlessEqual(e.args[0], 0) + zf = zipfile.ZipFile("seven.xpi", "r") + names = zf.namelist() + self.assertIn("resources/addon-sdk/lib/sdk/loader/cuddlefish.js", names) + testfiles = [fn for fn in names if "seven/tests" in fn] + self.failUnlessEqual([], testfiles) + self.assertIn("resources/seven/data/text.data", + names) + self.failUnless("resources/seven/lib/unused.js" + in names, names) + self.run_in_subdir("x", _test) + + +if __name__ == '__main__': + unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_manifest.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_manifest.py new file mode 100644 index 000000000..1bced1234 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_manifest.py @@ -0,0 +1,257 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +import unittest +from StringIO import StringIO +from cuddlefish.manifest import scan_module + +class Extra: + def failUnlessKeysAre(self, d, keys): + self.failUnlessEqual(sorted(d.keys()), sorted(keys)) + +class Require(unittest.TestCase, Extra): + def scan(self, text): + lines = StringIO(text).readlines() + requires, problems, locations = scan_module("fake.js", lines) + self.failUnlessEqual(problems, False) + return requires + + def scan_locations(self, text): + lines = StringIO(text).readlines() + requires, problems, locations = scan_module("fake.js", lines) + self.failUnlessEqual(problems, False) + return requires, locations + + def test_modules(self): + mod = """var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + mod = """var foo = require(\"one\");""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + mod = """var foo=require( 'one' ) ; """ + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + mod = """var foo = require('o'+'ne'); // tricky, denied""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, []) + + mod = """require('one').immediately.do().stuff();""" + requires, locations = self.scan_locations(mod) + self.failUnlessKeysAre(requires, ["one"]) + self.failUnlessEqual(locations, {"one": 1}) + + # these forms are commented out, and thus ignored + + mod = """// var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, []) + + mod = """/* var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, []) + + mod = """ * var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, []) + + mod = """ ' var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + mod = """ \" var foo = require('one');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + # multiple requires + + mod = """const foo = require('one'); + const foo = require('two');""" + requires, locations = self.scan_locations(mod) + self.failUnlessKeysAre(requires, ["one", "two"]) + self.failUnlessEqual(locations["one"], 1) + self.failUnlessEqual(locations["two"], 2) + + mod = """const foo = require('repeated'); + const bar = require('repeated'); + const baz = require('repeated');""" + requires, locations = self.scan_locations(mod) + self.failUnlessKeysAre(requires, ["repeated"]) + self.failUnlessEqual(locations["repeated"], 1) # first occurrence + + mod = """const foo = require('one'); const foo = require('two');""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one", "two"]) + + # define calls + + mod = """define('one', ['two', 'numbers/three'], function(t, th) {});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["two", "numbers/three"]) + + mod = """define( + ['odd', + "numbers/four"], function() {});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["odd", "numbers/four"]) + + mod = """define(function(require, exports, module) { + var a = require("some/module/a"), + b = require('b/v1'); + exports.a = a; + //This is a fakeout: require('bad'); + /* And another var bad = require('bad2'); */ + require('foo').goFoo(); + });""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["some/module/a", "b/v1", "foo"]) + + mod = """define ( + "foo", + ["bar"], function (bar) { + var me = require("me"); + } + )""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["bar", "me"]) + + mod = """define(['se' + 'ven', 'eight', nine], function () {});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["eight"]) + + # async require calls + + mod = """require(['one'], function(one) {var o = require("one");});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one"]) + + mod = """require([ 'one' ], function(one) {var t = require("two");});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["one", "two"]) + + mod = """require ( ['two', 'numbers/three'], function(t, th) {});""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["two", "numbers/three"]) + + mod = """require ( + ["bar", "fa" + 'ke' ], function (bar) { + var me = require("me"); + // require("bad").doBad(); + } + )""" + requires = self.scan(mod) + self.failUnlessKeysAre(requires, ["bar", "me"]) + +def scan2(text, fn="fake.js"): + stderr = StringIO() + lines = StringIO(text).readlines() + requires, problems, locations = scan_module(fn, lines, stderr) + stderr.seek(0) + return requires, problems, stderr.readlines() + +class Chrome(unittest.TestCase, Extra): + + def test_ignore_loader(self): + # we specifically ignore the loader itself + mod = """let {Cc,Ci} = require('chrome');""" + requires, problems, err = scan2(mod, "blah/cuddlefish.js") + self.failUnlessKeysAre(requires, ["chrome"]) + self.failUnlessEqual(problems, False) + self.failUnlessEqual(err, []) + + def test_chrome(self): + mod = """let {Cc,Ci} = require('chrome');""" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, ["chrome"]) + self.failUnlessEqual(problems, False) + self.failUnlessEqual(err, []) + + mod = """var foo = require('foo'); + let {Cc,Ci} = require('chrome');""" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, ["foo", "chrome"]) + self.failUnlessEqual(problems, False) + self.failUnlessEqual(err, []) + + mod = """let c = require('chrome');""" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, ["chrome"]) + self.failUnlessEqual(problems, False) + self.failUnlessEqual(err, []) + + mod = """var foo = require('foo'); + let c = require('chrome');""" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, ["foo", "chrome"]) + self.failUnlessEqual(problems, False) + self.failUnlessEqual(err, []) + + def test_not_chrome(self): + # from bug 596595 + mod = r'soughtLines: new RegExp("^\\s*(\\[[0-9 .]*\\])?\\s*\\(\\((EE|WW)\\)|.* [Cc]hipsets?: \\)|\\s*Backtrace")' + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, []) + self.failUnlessEqual((problems,err), (False, [])) + + def test_not_chrome2(self): + # from bug 655788 + mod = r"var foo = 'some stuff Cr';" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, []) + self.failUnlessEqual((problems,err), (False, [])) + +class BadChrome(unittest.TestCase, Extra): + def test_bad_alias(self): + # using Components.* gets you an error, with a message that teaches + # you the correct approach. + mod = """let Cc = Components.classes; + let Cu = Components.utils; + """ + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, []) + self.failUnlessEqual(problems, True) + self.failUnlessEqual(err[1], "The following lines from file fake.js:\n") + self.failUnlessEqual(err[2], " 1: let Cc = Components.classes;\n") + self.failUnlessEqual(err[3], " 2: let Cu = Components.utils;\n") + self.failUnlessEqual(err[4], "use 'Components' to access chrome authority. To do so, you need to add a\n") + self.failUnlessEqual(err[5], "line somewhat like the following:\n") + self.failUnlessEqual(err[7], ' const {Cc,Cu} = require("chrome");\n') + self.failUnlessEqual(err[9], "Then you can use any shortcuts to its properties that you import from the\n") + + def test_bad_misc(self): + # If it looks like you're using something that doesn't have an alias, + # the warning also suggests a better way. + mod = """if (Components.isSuccessCode(foo)) + """ + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, []) + self.failUnlessEqual(problems, True) + self.failUnlessEqual(err[1], "The following lines from file fake.js:\n") + self.failUnlessEqual(err[2], " 1: if (Components.isSuccessCode(foo))\n") + self.failUnlessEqual(err[3], "use 'Components' to access chrome authority. To do so, you need to add a\n") + self.failUnlessEqual(err[4], "line somewhat like the following:\n") + self.failUnlessEqual(err[6], ' const {components} = require("chrome");\n') + self.failUnlessEqual(err[8], "Then you can use any shortcuts to its properties that you import from the\n") + + def test_chrome_components(self): + # Bug 636145/774636: We no longer tolerate usages of "Components", + # even when adding `require("chrome")` to your module. + mod = """require("chrome"); + var ios = Components.classes['@mozilla.org/network/io-service;1'];""" + requires, problems, err = scan2(mod) + self.failUnlessKeysAre(requires, ["chrome"]) + self.failUnlessEqual(problems, True) + self.failUnlessEqual(err[1], "The following lines from file fake.js:\n") + self.failUnlessEqual(err[2], " 2: var ios = Components.classes['@mozilla.org/network/io-service;1'];\n") + self.failUnlessEqual(err[3], "use 'Components' to access chrome authority. To do so, you need to add a\n") + self.failUnlessEqual(err[4], "line somewhat like the following:\n") + self.failUnlessEqual(err[6], ' const {Cc} = require("chrome");\n') + self.failUnlessEqual(err[8], "Then you can use any shortcuts to its properties that you import from the\n") + +if __name__ == '__main__': + unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_packaging.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_packaging.py new file mode 100644 index 000000000..6944dc394 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_packaging.py @@ -0,0 +1,117 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import unittest + +from cuddlefish import packaging +from cuddlefish.bunch import Bunch + +tests_path = os.path.abspath(os.path.dirname(__file__)) +static_files_path = os.path.join(tests_path, 'static-files') + +def get_configs(pkg_name, dirname='static-files'): + root_path = os.path.join(tests_path, dirname) + pkg_path = os.path.join(root_path, 'packages', pkg_name) + if not (os.path.exists(pkg_path) and os.path.isdir(pkg_path)): + raise Exception('path does not exist: %s' % pkg_path) + target_cfg = packaging.get_config_in_dir(pkg_path) + pkg_cfg = packaging.build_config(root_path, target_cfg) + deps = packaging.get_deps_for_targets(pkg_cfg, [pkg_name]) + build = packaging.generate_build_for_target( + pkg_cfg=pkg_cfg, + target=pkg_name, + deps=deps, + is_running_tests=True, + ) + return Bunch(target_cfg=target_cfg, pkg_cfg=pkg_cfg, build=build) + +class PackagingTests(unittest.TestCase): + def test_bug_588661(self): + configs = get_configs('foo', 'bug-588661-files') + self.assertEqual(configs.build.loader, + 'foo/lib/foo-loader.js') + + def test_bug_614712(self): + configs = get_configs('commonjs-naming', 'bug-614712-files') + packages = configs.pkg_cfg.packages + base = os.path.join(tests_path, 'bug-614712-files', 'packages') + self.assertEqual(packages['original-naming'].tests, + [os.path.join(base, 'original-naming', 'tests')]) + self.assertEqual(packages['commonjs-naming'].tests, + [os.path.join(base, 'commonjs-naming', 'test')]) + + def test_basic(self): + configs = get_configs('aardvark') + packages = configs.pkg_cfg.packages + + self.assertTrue('addon-sdk' in packages) + self.assertTrue('aardvark' in packages) + self.assertTrue('addon-sdk' in packages.aardvark.dependencies) + self.assertEqual(packages['addon-sdk'].loader, 'lib/sdk/loader/cuddlefish.js') + self.assertTrue(packages.aardvark.main == 'main') + self.assertTrue(packages.aardvark.version == "1.0") + +class PackagePath(unittest.TestCase): + def test_packagepath(self): + root_path = os.path.join(tests_path, 'static-files') + pkg_path = os.path.join(root_path, 'packages', 'minimal') + target_cfg = packaging.get_config_in_dir(pkg_path) + pkg_cfg = packaging.build_config(root_path, target_cfg) + base_packages = set(pkg_cfg.packages.keys()) + ppath = [os.path.join(tests_path, 'bug-611495-files')] + pkg_cfg2 = packaging.build_config(root_path, target_cfg, packagepath=ppath) + all_packages = set(pkg_cfg2.packages.keys()) + self.assertEqual(sorted(["jspath-one"]), + sorted(all_packages - base_packages)) + +class Directories(unittest.TestCase): + # for bug 652227 + packages_path = os.path.join(tests_path, "bug-652227-files", "packages") + def get_config(self, pkg_name): + pkg_path = os.path.join(tests_path, "bug-652227-files", "packages", + pkg_name) + return packaging.get_config_in_dir(pkg_path) + + def test_explicit_lib(self): + # package.json provides .lib + p = self.get_config('explicit-lib') + self.assertEqual(os.path.abspath(p.lib[0]), + os.path.abspath(os.path.join(self.packages_path, + "explicit-lib", + "alt2-lib"))) + + def test_directories_lib(self): + # package.json provides .directories.lib + p = self.get_config('explicit-dir-lib') + self.assertEqual(os.path.abspath(p.lib[0]), + os.path.abspath(os.path.join(self.packages_path, + "explicit-dir-lib", + "alt-lib"))) + + def test_lib(self): + # package.json is empty, but lib/ exists + p = self.get_config("default-lib") + self.assertEqual(os.path.abspath(p.lib[0]), + os.path.abspath(os.path.join(self.packages_path, + "default-lib", + "lib"))) + + def test_root(self): + # package.json is empty, no lib/, so files are in root + p = self.get_config('default-root') + self.assertEqual(os.path.abspath(p.lib[0]), + os.path.abspath(os.path.join(self.packages_path, + "default-root"))) + + def test_locale(self): + # package.json is empty, but locale/ exists and should be used + p = self.get_config("default-locale") + self.assertEqual(os.path.abspath(p.locale), + os.path.abspath(os.path.join(self.packages_path, + "default-locale", + "locale"))) + +if __name__ == "__main__": + unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_preflight.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_preflight.py new file mode 100644 index 000000000..571b7911b --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_preflight.py @@ -0,0 +1,147 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +import os, shutil +import simplejson as json +import unittest +import hashlib +import base64 +from cuddlefish import preflight +from StringIO import StringIO + +class Util(unittest.TestCase): + def get_basedir(self): + return os.path.join(".test_tmp", self.id()) + def make_basedir(self): + basedir = self.get_basedir() + if os.path.isdir(basedir): + here = os.path.abspath(os.getcwd()) + assert os.path.abspath(basedir).startswith(here) # safety + shutil.rmtree(basedir) + os.makedirs(basedir) + return basedir + + def test_base62(self): + for i in range(1000): + h = hashlib.sha1(str(i)).digest() + s1 = base64.b64encode(h, "AB").strip("=") + s2 = base64.b64encode(h).strip("=").replace("+","A").replace("/","B") + self.failUnlessEqual(s1, s2) + + def write(self, config): + basedir = self.get_basedir() + fn = os.path.join(basedir, "package.json") + open(fn,"w").write(config) + def read(self): + basedir = self.get_basedir() + fn = os.path.join(basedir, "package.json") + return open(fn,"r").read() + + def get_cfg(self): + cfg = json.loads(self.read()) + if "name" not in cfg: + # the cfx parser always provides a name, even if package.json + # doesn't contain one + cfg["name"] = "pretend name" + return cfg + + def parse(self, keydata): + fields = {} + fieldnames = [] + for line in keydata.split("\n"): + if line.strip(): + k,v = line.split(":", 1) + k = k.strip() ; v = v.strip() + fields[k] = v + fieldnames.append(k) + return fields, fieldnames + + def test_preflight(self): + basedir = self.make_basedir() + fn = os.path.join(basedir, "package.json") + + # empty config is not ok: need id (name is automatically supplied) + config_orig = "{}" + self.write(config_orig) + out = StringIO() + cfg = self.get_cfg() + config_was_ok, modified = preflight.preflight_config(cfg, fn, + stderr=out) + self.failUnlessEqual(config_was_ok, False) + self.failUnlessEqual(modified, True) + backup_fn = os.path.join(basedir, "package.json.backup") + config_backup = open(backup_fn,"r").read() + self.failUnlessEqual(config_backup, config_orig) + config = json.loads(self.read()) + self.failIf("name" in config) + self.failUnless("id" in config) + self.failUnless(config["id"].startswith("jid1-"), config["id"]) + self.failUnlessEqual(out.getvalue().strip(), + "No 'id' in package.json: creating a new ID for you.") + os.unlink(backup_fn) + + # just a name? we add the id + config_orig = '{"name": "my-awesome-package"}' + self.write(config_orig) + out = StringIO() + cfg = self.get_cfg() + config_was_ok, modified = preflight.preflight_config(cfg, fn, + stderr=out) + self.failUnlessEqual(config_was_ok, False) + self.failUnlessEqual(modified, True) + backup_fn = os.path.join(basedir, "package.json.backup") + config_backup = open(backup_fn,"r").read() + self.failUnlessEqual(config_backup, config_orig) + config = json.loads(self.read()) + self.failUnlessEqual(config["name"], "my-awesome-package") + self.failUnless("id" in config) + self.failUnless(config["id"].startswith("jid1-"), config["id"]) + jid = str(config["id"]) + self.failUnlessEqual(out.getvalue().strip(), + "No 'id' in package.json: creating a new ID for you.") + os.unlink(backup_fn) + + # name and valid id? great! ship it! + config2 = '{"name": "my-awesome-package", "id": "%s"}' % jid + self.write(config2) + out = StringIO() + cfg = self.get_cfg() + config_was_ok, modified = preflight.preflight_config(cfg, fn, + stderr=out) + self.failUnlessEqual(config_was_ok, True) + self.failUnlessEqual(modified, False) + config2a = self.read() + self.failUnlessEqual(config2a, config2) + self.failUnlessEqual(out.getvalue().strip(), "") + + # name and anonymous ID? without asking to see its papers, ship it + config3 = '{"name": "my-old-skool-package", "id": "anonid0-deadbeef"}' + self.write(config3) + out = StringIO() + cfg = self.get_cfg() + config_was_ok, modified = preflight.preflight_config(cfg, fn, + stderr=out) + self.failUnlessEqual(config_was_ok, True) + self.failUnlessEqual(modified, False) + config3a = self.read() + self.failUnlessEqual(config3a, config3) + self.failUnlessEqual(out.getvalue().strip(), "") + + # name and old-style ID? with nostalgic trepidation, ship it + config4 = '{"name": "my-old-skool-package", "id": "foo@bar.baz"}' + self.write(config4) + out = StringIO() + cfg = self.get_cfg() + config_was_ok, modified = preflight.preflight_config(cfg, fn, + stderr=out) + self.failUnlessEqual(config_was_ok, True) + self.failUnlessEqual(modified, False) + config4a = self.read() + self.failUnlessEqual(config4a, config4) + self.failUnlessEqual(out.getvalue().strip(), "") + + +if __name__ == '__main__': + unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_property_parser.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_property_parser.py new file mode 100644 index 000000000..4988f8ef5 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_property_parser.py @@ -0,0 +1,93 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import unittest + +from cuddlefish.property_parser import parse, MalformedLocaleFileError + +class TestParser(unittest.TestCase): + + def test_parse(self): + lines = [ + # Comments are striped only if `#` is the first non-space character + "sharp=#can be in value", + "# comment", + "#key=value", + " # comment2", + + "keyWithNoValue=", + "valueWithSpaces= ", + "valueWithMultilineSpaces= \\", + " \\", + " ", + + # All spaces before/after are striped + " key = value ", + "key2=value2", + # Keys can contain '%' + "%s key=%s value", + + # Accept empty lines + "", + " ", + + # Multiline string must use backslash at end of lines + "multi=line\\", "value", + # With multiline string, left spaces are stripped ... + "some= spaces\\", " are\\ ", " stripped ", + # ... but not right spaces, except the last line! + "but=not \\", "all of \\", " them ", + + # Explicit [other] plural definition + "explicitPlural[one] = one", + "explicitPlural[other] = other", + + # Implicit [other] plural definition + "implicitPlural[one] = one", + "implicitPlural = other", # This key is the [other] one + ] + # Ensure that all lines end with a `\n` + # And that strings are unicode ones (parser code relies on it) + lines = [unicode(l + "\n") for l in lines] + pairs = parse(lines) + expected = { + "sharp": "#can be in value", + + "key": "value", + "key2": "value2", + "%s key": "%s value", + + "keyWithNoValue": "", + "valueWithSpaces": "", + "valueWithMultilineSpaces": "", + + "multi": "linevalue", + "some": "spacesarestripped", + "but": "not all of them", + + "implicitPlural": { + "one": "one", + "other": "other" + }, + "explicitPlural": { + "one": "one", + "other": "other" + }, + } + self.assertEqual(pairs, expected) + + def test_exceptions(self): + self.failUnlessRaises(MalformedLocaleFileError, parse, + ["invalid line with no key value"]) + self.failUnlessRaises(MalformedLocaleFileError, parse, + ["plural[one]=plural with no [other] value"]) + self.failUnlessRaises(MalformedLocaleFileError, parse, + ["multiline with no last empty line=\\"]) + self.failUnlessRaises(MalformedLocaleFileError, parse, + ["=no key"]) + self.failUnlessRaises(MalformedLocaleFileError, parse, + [" =only spaces in key"]) + +if __name__ == "__main__": + unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_rdf.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_rdf.py new file mode 100644 index 000000000..5d9254e4e --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_rdf.py @@ -0,0 +1,54 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import unittest +import xml.dom.minidom +import os.path + +from cuddlefish import rdf, packaging + +parent = os.path.dirname +test_dir = parent(os.path.abspath(__file__)) +template_dir = os.path.join(parent(test_dir), "../../app-extension") + +class RDFTests(unittest.TestCase): + def testBug567660(self): + obj = rdf.RDF() + data = u'\u2026'.encode('utf-8') + x = '<?xml version="1.0" encoding="utf-8"?><blah>%s</blah>' % data + obj.dom = xml.dom.minidom.parseString(x) + self.assertEqual(obj.dom.documentElement.firstChild.nodeValue, + u'\u2026') + self.assertEqual(str(obj).replace("\n",""), x.replace("\n","")) + + def failUnlessIn(self, substring, s, msg=""): + if substring not in s: + self.fail("(%s) substring '%s' not in string '%s'" + % (msg, substring, s)) + + def testUnpack(self): + basedir = os.path.join(test_dir, "bug-715340-files") + for n in ["pkg-1-pack", "pkg-2-unpack", "pkg-3-pack"]: + cfg = packaging.get_config_in_dir(os.path.join(basedir, n)) + m = rdf.gen_manifest(template_dir, cfg, jid="JID") + if n.endswith("-pack"): + # these ones should remain packed + self.failUnlessEqual(m.get("em:unpack"), "false") + self.failUnlessIn("<em:unpack>false</em:unpack>", str(m), n) + else: + # and these should be unpacked + self.failUnlessEqual(m.get("em:unpack"), "true") + self.failUnlessIn("<em:unpack>true</em:unpack>", str(m), n) + + def testTitle(self): + basedir = os.path.join(test_dir, 'bug-906359-files') + for n in ['title', 'fullName', 'none']: + cfg = packaging.get_config_in_dir(os.path.join(basedir, n)) + m = rdf.gen_manifest(template_dir, cfg, jid='JID') + self.failUnlessEqual(m.get('em:name'), 'a long ' + n) + self.failUnlessIn('<em:name>a long ' + n + '</em:name>', str(m), n) + + +if __name__ == '__main__': + unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_runner.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_runner.py new file mode 100644 index 000000000..26583abd1 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_runner.py @@ -0,0 +1,27 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +def xulrunner_app_runner_doctests(): + """ + >>> import sys + >>> from cuddlefish import runner + >>> runner.XulrunnerAppRunner(binary='foo') + Traceback (most recent call last): + ... + Exception: Binary path does not exist foo + + >>> runner.XulrunnerAppRunner(binary=sys.executable) + Traceback (most recent call last): + ... + ValueError: application.ini not found in cmdargs + + >>> runner.XulrunnerAppRunner(binary=sys.executable, + ... cmdargs=['application.ini']) + Traceback (most recent call last): + ... + ValueError: file does not exist: 'application.ini' + """ + + pass diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_util.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_util.py new file mode 100644 index 000000000..aa636a48c --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_util.py @@ -0,0 +1,22 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +import unittest +from cuddlefish.manifest import filter_filenames, filter_dirnames + +class Filter(unittest.TestCase): + def test_filter_filenames(self): + names = ["foo", "bar.js", "image.png", + ".hidden", "foo~", ".foo.swp", "bar.js.swp"] + self.failUnlessEqual(sorted(filter_filenames(names)), + sorted(["foo", "bar.js", "image.png"])) + + def test_filter_dirnames(self): + names = ["subdir", "data", ".git", ".hg", ".svn", "defaults"] + self.failUnlessEqual(sorted(filter_dirnames(names)), + sorted(["subdir", "data", "defaults"])) + +if __name__ == '__main__': + unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_version.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_version.py new file mode 100644 index 000000000..814c57c89 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_version.py @@ -0,0 +1,28 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import unittest +import shutil + +from cuddlefish._version import get_versions + +class Version(unittest.TestCase): + def get_basedir(self): + return os.path.join(".test_tmp", self.id()) + def make_basedir(self): + basedir = self.get_basedir() + if os.path.isdir(basedir): + here = os.path.abspath(os.getcwd()) + assert os.path.abspath(basedir).startswith(here) # safety + shutil.rmtree(basedir) + os.makedirs(basedir) + return basedir + + def test_current_version(self): + # the SDK should be able to determine its own version. We don't care + # what it is, merely that it can be computed. + version = get_versions()["version"] + self.failUnless(isinstance(version, str), (version, type(version))) + self.failUnless(len(version) > 0, version) diff --git a/addon-sdk/source/python-lib/cuddlefish/tests/test_xpi.py b/addon-sdk/source/python-lib/cuddlefish/tests/test_xpi.py new file mode 100644 index 000000000..bbefee3b2 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_xpi.py @@ -0,0 +1,310 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import unittest +import zipfile +import pprint +import shutil + +import simplejson as json +from cuddlefish import xpi, packaging, manifest, buildJID +from cuddlefish.tests import test_packaging +from test_linker import up + +import xml.etree.ElementTree as ElementTree + +xpi_template_path = os.path.join(test_packaging.static_files_path, + 'xpi-template') + +fake_manifest = '<RDF><!-- Extension metadata is here. --></RDF>' + + +class Bug588119Tests(unittest.TestCase): + def makexpi(self, pkg_name): + self.xpiname = "%s.xpi" % pkg_name + create_xpi(self.xpiname, pkg_name, 'bug-588119-files') + self.xpi = zipfile.ZipFile(self.xpiname, 'r') + options = self.xpi.read('harness-options.json') + self.xpi_harness_options = json.loads(options) + + def setUp(self): + self.xpiname = None + self.xpi = None + + def tearDown(self): + if self.xpi: + self.xpi.close() + if self.xpiname and os.path.exists(self.xpiname): + os.remove(self.xpiname) + + def testPackageWithImplicitIcon(self): + self.makexpi('implicit-icon') + assert 'icon.png' in self.xpi.namelist() + + def testPackageWithImplicitIcon64(self): + self.makexpi('implicit-icon') + assert 'icon64.png' in self.xpi.namelist() + + def testPackageWithExplicitIcon(self): + self.makexpi('explicit-icon') + assert 'icon.png' in self.xpi.namelist() + + def testPackageWithExplicitIcon64(self): + self.makexpi('explicit-icon') + assert 'icon64.png' in self.xpi.namelist() + + def testPackageWithNoIcon(self): + self.makexpi('no-icon') + assert 'icon.png' not in self.xpi.namelist() + + def testIconPathNotInHarnessOptions(self): + self.makexpi('implicit-icon') + assert 'icon' not in self.xpi_harness_options + + def testIcon64PathNotInHarnessOptions(self): + self.makexpi('implicit-icon') + assert 'icon64' not in self.xpi_harness_options + +class ExtraHarnessOptions(unittest.TestCase): + def setUp(self): + self.xpiname = None + self.xpi = None + + def tearDown(self): + if self.xpi: + self.xpi.close() + if self.xpiname and os.path.exists(self.xpiname): + os.remove(self.xpiname) + + def testOptions(self): + pkg_name = "extra-options" + self.xpiname = "%s.xpi" % pkg_name + create_xpi(self.xpiname, pkg_name, "bug-669274-files", + extra_harness_options={"builderVersion": "futuristic"}) + self.xpi = zipfile.ZipFile(self.xpiname, 'r') + options = self.xpi.read('harness-options.json') + hopts = json.loads(options) + self.failUnless("builderVersion" in hopts) + self.failUnlessEqual(hopts["builderVersion"], "futuristic") + + def testBadOptionName(self): + pkg_name = "extra-options" + self.xpiname = "%s.xpi" % pkg_name + self.failUnlessRaises(xpi.HarnessOptionAlreadyDefinedError, + create_xpi, + self.xpiname, pkg_name, "bug-669274-files", + extra_harness_options={"main": "already in use"}) + +class SmallXPI(unittest.TestCase): + def setUp(self): + self.root = up(os.path.abspath(__file__), 4) + def get_linker_files_dir(self, name): + return os.path.join(up(os.path.abspath(__file__)), "linker-files", name) + def get_pkg(self, name): + d = self.get_linker_files_dir(name) + return packaging.get_config_in_dir(d) + + def get_basedir(self): + return os.path.join(".test_tmp", self.id()) + def make_basedir(self): + basedir = self.get_basedir() + if os.path.isdir(basedir): + here = os.path.abspath(os.getcwd()) + assert os.path.abspath(basedir).startswith(here) # safety + shutil.rmtree(basedir) + os.makedirs(basedir) + return basedir + + 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(True)) + 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"), + os.path.join("sdk", "util", "array.js"), + os.path.join("sdk", "preferences", "service.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", + "resources/", + "resources/addon-sdk/", + "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/util/array.js", + "resources/addon-sdk/lib/sdk/net/url.js", + "resources/addon-sdk/lib/sdk/preferences/", + "resources/addon-sdk/lib/sdk/preferences/service.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" + }''')) + + +def document_dir(name): + if name in ['packages', 'xpi-template']: + dirname = os.path.join(test_packaging.static_files_path, name) + document_dir_files(dirname) + elif name == 'xpi-output': + create_xpi('test-xpi.xpi') + document_zip_file('test-xpi.xpi') + os.remove('test-xpi.xpi') + else: + raise Exception('unknown dir: %s' % name) + +def normpath(path): + """ + Make a platform-specific relative path use '/' as a separator. + """ + + return path.replace(os.path.sep, '/') + +def document_zip_file(path): + zip = zipfile.ZipFile(path, 'r') + for name in sorted(zip.namelist()): + contents = zip.read(name) + lines = contents.splitlines() + if len(lines) == 1 and name.endswith('.json') and len(lines[0]) > 75: + # Ideally we would json-decode this, but it results + # in an annoying 'u' before every string literal, + # since json decoding makes all strings unicode. + contents = eval(contents) + contents = pprint.pformat(contents) + lines = contents.splitlines() + contents = "\n ".join(lines) + print "%s:\n %s" % (normpath(name), contents) + zip.close() + +def document_dir_files(path): + filename_contents_tuples = [] + for dirpath, dirnames, filenames in os.walk(path): + relpath = dirpath[len(path)+1:] + for filename in filenames: + abspath = os.path.join(dirpath, filename) + contents = open(abspath, 'r').read() + contents = "\n ".join(contents.splitlines()) + relfilename = os.path.join(relpath, filename) + filename_contents_tuples.append((normpath(relfilename), contents)) + filename_contents_tuples.sort() + for filename, contents in filename_contents_tuples: + print "%s:" % filename + print " %s" % contents + +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, + 'jetpackID': buildJID(configs.target_cfg), } + 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) + +if __name__ == '__main__': + unittest.main() diff --git a/addon-sdk/source/python-lib/cuddlefish/util.py b/addon-sdk/source/python-lib/cuddlefish/util.py new file mode 100644 index 000000000..513495a69 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/util.py @@ -0,0 +1,23 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + + +IGNORED_FILE_PREFIXES = ["."] +IGNORED_FILE_SUFFIXES = ["~", ".swp"] +IGNORED_DIRS = [".git", ".svn", ".hg"] + +def filter_filenames(filenames, ignored_files=[".hgignore"]): + for filename in filenames: + if filename in ignored_files: + continue + if any([filename.startswith(suffix) + for suffix in IGNORED_FILE_PREFIXES]): + continue + if any([filename.endswith(suffix) + for suffix in IGNORED_FILE_SUFFIXES]): + continue + yield filename + +def filter_dirnames(dirnames): + return [dirname for dirname in dirnames if dirname not in IGNORED_DIRS] diff --git a/addon-sdk/source/python-lib/cuddlefish/version_comparator.py b/addon-sdk/source/python-lib/cuddlefish/version_comparator.py new file mode 100644 index 000000000..fee1341fd --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/version_comparator.py @@ -0,0 +1,206 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +''' + This is a really crummy, slow Python implementation of the Mozilla + platform's nsIVersionComparator interface: + + https://developer.mozilla.org/En/NsIVersionComparator + + For more information, also see: + + http://dxr.mozilla.org/mozilla-central/source/xpcom/glue/nsVersionComparator.cpp +''' + +import re +import sys + +class VersionPart(object): + ''' + Examples: + + >>> VersionPart('1') + (1, None, 0, None) + + >>> VersionPart('1pre') + (1, 'pre', 0, None) + + >>> VersionPart('1pre10') + (1, 'pre', 10, None) + + >>> VersionPart('1pre10a') + (1, 'pre', 10, 'a') + + >>> VersionPart('1+') + (2, 'pre', 0, None) + + >>> VersionPart('*').numA == sys.maxint + True + + >>> VersionPart('1') < VersionPart('2') + True + + >>> VersionPart('2') > VersionPart('1') + True + + >>> VersionPart('1') == VersionPart('1') + True + + >>> VersionPart('1pre') > VersionPart('1') + False + + >>> VersionPart('1') < VersionPart('1pre') + False + + >>> VersionPart('1pre1') < VersionPart('1pre2') + True + + >>> VersionPart('1pre10b') > VersionPart('1pre10a') + True + + >>> VersionPart('1pre10b') == VersionPart('1pre10b') + True + + >>> VersionPart('1pre10a') < VersionPart('1pre10b') + True + + >>> VersionPart('1') > VersionPart('') + True + ''' + + _int_part = re.compile('[+-]?(\d*)(.*)') + _num_chars = '0123456789+-' + + def __init__(self, part): + self.numA = 0 + self.strB = None + self.numC = 0 + self.extraD = None + + if not part: + return + + if part == '*': + self.numA = sys.maxint + else: + match = self._int_part.match(part) + self.numA = int(match.group(1)) + self.strB = match.group(2) or None + if self.strB == '+': + self.strB = 'pre' + self.numA += 1 + elif self.strB: + i = 0 + num_found = -1 + for char in self.strB: + if char in self._num_chars: + num_found = i + break + i += 1 + if num_found != -1: + match = self._int_part.match(self.strB[num_found:]) + self.numC = int(match.group(1)) + self.extraD = match.group(2) or None + self.strB = self.strB[:num_found] + + def _strcmp(self, str1, str2): + # Any string is *before* no string. + if str1 is None: + if str2 is None: + return 0 + else: + return 1 + + if str2 is None: + return -1 + + return cmp(str1, str2) + + def __cmp__(self, other): + r = cmp(self.numA, other.numA) + if r: + return r + + r = self._strcmp(self.strB, other.strB) + if r: + return r + + r = cmp(self.numC, other.numC) + if r: + return r + + return self._strcmp(self.extraD, other.extraD) + + def __repr__(self): + return repr((self.numA, self.strB, self.numC, self.extraD)) + +def compare(a, b): + ''' + Examples: + + >>> compare('1', '2') + -1 + + >>> compare('1', '1') + 0 + + >>> compare('2', '1') + 1 + + >>> compare('1.0pre1', '1.0pre2') + -1 + + >>> compare('1.0pre2', '1.0') + -1 + + >>> compare('1.0', '1.0.0') + 0 + + >>> compare('1.0.0', '1.0.0.0') + 0 + + >>> compare('1.0.0.0', '1.1pre') + -1 + + >>> compare('1.1pre', '1.1pre0') + 0 + + >>> compare('1.1pre0', '1.0+') + 0 + + >>> compare('1.0+', '1.1pre1a') + -1 + + >>> compare('1.1pre1a', '1.1pre1') + -1 + + >>> compare('1.1pre1', '1.1pre10a') + -1 + + >>> compare('1.1pre10a', '1.1pre10') + -1 + + >>> compare('1.1pre10a', '1.*') + -1 + ''' + + a_parts = a.split('.') + b_parts = b.split('.') + + if len(a_parts) < len(b_parts): + a_parts.extend([''] * (len(b_parts) - len(a_parts))) + else: + b_parts.extend([''] * (len(a_parts) - len(b_parts))) + + for a_part, b_part in zip(a_parts, b_parts): + r = cmp(VersionPart(a_part), VersionPart(b_part)) + if r: + return r + + return 0 + +if __name__ == '__main__': + import doctest + + doctest.testmod(verbose=True) diff --git a/addon-sdk/source/python-lib/cuddlefish/xpi.py b/addon-sdk/source/python-lib/cuddlefish/xpi.py new file mode 100644 index 000000000..4ac497e89 --- /dev/null +++ b/addon-sdk/source/python-lib/cuddlefish/xpi.py @@ -0,0 +1,169 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +import os +import zipfile +import simplejson as json +from cuddlefish.util import filter_filenames, filter_dirnames + +class HarnessOptionAlreadyDefinedError(Exception): + """You cannot use --harness-option on keys that already exist in + harness-options.json""" + +ZIPSEP = "/" # always use "/" in zipfiles + +def make_zipfile_path(localroot, localpath): + return ZIPSEP.join(localpath[len(localroot)+1:].split(os.sep)) + +def mkzipdir(zf, path): + dirinfo = zipfile.ZipInfo(path) + dirinfo.external_attr = int("040755", 8) << 16L + zf.writestr(dirinfo, "") + +def build_xpi(template_root_dir, manifest, xpi_path, + harness_options, limit_to=None, extra_harness_options={}, + bundle_sdk=True, pkgdir=""): + IGNORED_FILES = [".hgignore", ".DS_Store", + "application.ini", xpi_path] + IGNORED_TOP_LVL_FILES = ["install.rdf"] + + files_to_copy = {} # maps zipfile path to local-disk abspath + dirs_to_create = set() # zipfile paths, no trailing slash + + zf = zipfile.ZipFile(xpi_path, "w", zipfile.ZIP_DEFLATED) + + zf.writestr('install.rdf', str(manifest)) + + # Handle add-on icon + if 'icon' in harness_options: + zf.write(os.path.join(str(harness_options['icon'])), 'icon.png') + del harness_options['icon'] + + if 'icon64' in harness_options: + zf.write(os.path.join(str(harness_options['icon64'])), 'icon64.png') + del harness_options['icon64'] + + # chrome.manifest + if os.path.isfile(os.path.join(pkgdir, 'chrome.manifest')): + files_to_copy['chrome.manifest'] = os.path.join(pkgdir, 'chrome.manifest') + + def add_special_dir(folder): + if os.path.exists(os.path.join(pkgdir, folder)): + dirs_to_create.add(folder) + # cp -r folder + abs_dirname = os.path.join(pkgdir, folder) + for dirpath, dirnames, filenames in os.walk(abs_dirname): + goodfiles = list(filter_filenames(filenames, IGNORED_FILES)) + dirnames[:] = filter_dirnames(dirnames) + for dirname in dirnames: + arcpath = make_zipfile_path(template_root_dir, + os.path.join(dirpath, dirname)) + dirs_to_create.add(arcpath) + for filename in goodfiles: + abspath = os.path.join(dirpath, filename) + arcpath = ZIPSEP.join( + [folder, + make_zipfile_path(abs_dirname, os.path.join(dirpath, filename)), + ]) + files_to_copy[str(arcpath)] = str(abspath) + + + # chrome folder (would contain content, skin, and locale folders typically) + add_special_dir('chrome') + # optionally include a `webextension/` dir from the add-on dir. + add_special_dir('webextension') + + for dirpath, dirnames, filenames in os.walk(template_root_dir): + if template_root_dir == dirpath: + filenames = list(filter_filenames(filenames, IGNORED_TOP_LVL_FILES)) + filenames = list(filter_filenames(filenames, IGNORED_FILES)) + dirnames[:] = filter_dirnames(dirnames) + for dirname in dirnames: + arcpath = make_zipfile_path(template_root_dir, + os.path.join(dirpath, dirname)) + dirs_to_create.add(arcpath) + for filename in filenames: + abspath = os.path.join(dirpath, filename) + arcpath = make_zipfile_path(template_root_dir, abspath) + files_to_copy[arcpath] = abspath + + # `packages` attribute contains a dictionnary of dictionnary + # of all packages sections directories + for packageName in harness_options['packages']: + base_arcpath = ZIPSEP.join(['resources', packageName]) + # Eventually strip sdk files. + if not bundle_sdk and packageName == 'addon-sdk': + continue + # Always write the top directory, even if it contains no files, since + # the harness will try to access it. + dirs_to_create.add(base_arcpath) + for sectionName in harness_options['packages'][packageName]: + abs_dirname = harness_options['packages'][packageName][sectionName] + base_arcpath = ZIPSEP.join(['resources', packageName, sectionName]) + # Always write the top directory, even if it contains no files, since + # the harness will try to access it. + dirs_to_create.add(base_arcpath) + # cp -r stuff from abs_dirname/ into ZIP/resources/RESOURCEBASE/ + for dirpath, dirnames, filenames in os.walk(abs_dirname): + goodfiles = list(filter_filenames(filenames, IGNORED_FILES)) + dirnames[:] = filter_dirnames(dirnames) + for filename in goodfiles: + abspath = os.path.join(dirpath, filename) + if limit_to is not None and abspath not in limit_to: + continue # strip unused files + arcpath = ZIPSEP.join( + ['resources', + packageName, + sectionName, + make_zipfile_path(abs_dirname, + os.path.join(dirpath, filename)), + ]) + files_to_copy[str(arcpath)] = str(abspath) + del harness_options['packages'] + + locales_json_data = {"locales": []} + mkzipdir(zf, "locale/") + for language in sorted(harness_options['locale']): + locales_json_data["locales"].append(language) + locale = harness_options['locale'][language] + # Be carefull about strings, we need to always ensure working with UTF-8 + jsonStr = json.dumps(locale, indent=1, sort_keys=True, ensure_ascii=False) + info = zipfile.ZipInfo('locale/' + language + '.json') + info.external_attr = 0644 << 16L + zf.writestr(info, jsonStr.encode( "utf-8" )) + del harness_options['locale'] + + jsonStr = json.dumps(locales_json_data, ensure_ascii=True) +"\n" + info = zipfile.ZipInfo('locales.json') + info.external_attr = 0644 << 16L + zf.writestr(info, jsonStr.encode("utf-8")) + + # now figure out which directories we need: all retained files parents + for arcpath in files_to_copy: + bits = arcpath.split("/") + for i in range(1,len(bits)): + parentpath = ZIPSEP.join(bits[0:i]) + dirs_to_create.add(parentpath) + + # Create zipfile in alphabetical order, with each directory before its + # files + for name in sorted(dirs_to_create.union(set(files_to_copy))): + if name in dirs_to_create: + mkzipdir(zf, name+"/") + if name in files_to_copy: + zf.write(files_to_copy[name], name) + + # Add extra harness options + harness_options = harness_options.copy() + for key,value in extra_harness_options.items(): + if key in harness_options: + msg = "Can't use --harness-option for existing key '%s'" % key + raise HarnessOptionAlreadyDefinedError(msg) + harness_options[key] = value + + # Write harness-options.json + zf.writestr('harness-options.json', json.dumps(harness_options, indent=1, + sort_keys=True)) + + zf.close() |