""" Parser for command line arguments. """ from __future__ import absolute_import import os import os.path import optparse from . import config as _config from . import testing from . import utils from .. import resmokeconfig # Mapping of the attribute of the parsed arguments (dest) to its key as it appears in the options # YAML configuration file. Most should only be converting from snake_case to camelCase. DEST_TO_CONFIG = { "base_port": "basePort", "buildlogger_url": "buildloggerUrl", "continue_on_failure": "continueOnFailure", "dbpath_prefix": "dbpathPrefix", "dbtest_executable": "dbtest", "dry_run": "dryRun", "exclude_with_all_tags": "excludeWithAllTags", "exclude_with_any_tags": "excludeWithAnyTags", "include_with_all_tags": "includeWithAllTags", "include_with_any_tags": "includeWithAnyTags", "jobs": "jobs", "mongo_executable": "mongo", "mongod_executable": "mongod", "mongod_parameters": "mongodSetParameters", "mongos_executable": "mongos", "mongos_parameters": "mongosSetParameters", "no_journal": "nojournal", "prealloc_journal": "preallocJournal", "repeat": "repeat", "report_file": "reportFile", "seed": "seed", "shell_read_mode": "shellReadMode", "shell_write_mode": "shellWriteMode", "shuffle": "shuffle", "storage_engine": "storageEngine", "wt_coll_config": "wiredTigerCollectionConfigString", "wt_engine_config": "wiredTigerEngineConfigString", "wt_index_config": "wiredTigerIndexConfigString" } def parse_command_line(): """ Parses the command line arguments passed to resmoke.py. """ parser = optparse.OptionParser() parser.add_option("--suites", dest="suite_files", metavar="SUITE1,SUITE2", help=("Comma separated list of YAML files that each specify the configuration" " of a suite. If the file is located in the resmokeconfig/suites/" " directory, then the basename without the .yml extension can be" " specified, e.g. 'core'.")) parser.add_option("--executor", dest="executor_file", metavar="EXECUTOR", help=("A YAML file that specifies the executor configuration. If the file is" " located in the resmokeconfig/suites/ directory, then the basename" " without the .yml extension can be specified, e.g. 'core_small_oplog'." " If specified in combination with the --suites option, then the suite" " configuration takes precedence.")) parser.add_option("--log", dest="logger_file", metavar="LOGGER", help=("A YAML file that specifies the logging configuration. If the file is" " located in the resmokeconfig/suites/ directory, then the basename" " without the .yml extension can be specified, e.g. 'console'.")) parser.add_option("--options", dest="options_file", metavar="OPTIONS", help="A YAML file that specifies global options to resmoke.py.") parser.add_option("--basePort", dest="base_port", metavar="PORT", help=("The starting port number to use for mongod and mongos processes" " spawned by resmoke.py or the tests themselves. Each fixture and Job" " allocates a contiguous range of ports.")) parser.add_option("--buildloggerUrl", action="store", dest="buildlogger_url", metavar="URL", help="The root url of the buildlogger server.") parser.add_option("--continueOnFailure", action="store_true", dest="continue_on_failure", help="Executes all tests in all suites, even if some of them fail.") parser.add_option("--dbpathPrefix", dest="dbpath_prefix", metavar="PATH", help=("The directory which will contain the dbpaths of any mongod's started" " by resmoke.py or the tests themselves.")) parser.add_option("--dbtest", dest="dbtest_executable", metavar="PATH", help="The path to the dbtest executable for resmoke to use.") parser.add_option("--excludeWithAllTags", dest="exclude_with_all_tags", metavar="TAG1,TAG2", help=("Comma separated list of tags. Any jstest that contains all of the" " specified tags will be excluded from any suites that are run.")) parser.add_option("--excludeWithAnyTags", dest="exclude_with_any_tags", metavar="TAG1,TAG2", help=("Comma separated list of tags. Any jstest that contains any of the" " specified tags will be excluded from any suites that are run.")) parser.add_option("--includeWithAllTags", dest="include_with_all_tags", metavar="TAG1,TAG2", help=("Comma separated list of tags. For the jstest portion of the suite(s)," " only tests which have all of the specified tags will be run.")) parser.add_option("--includeWithAnyTags", dest="include_with_any_tags", metavar="TAG1,TAG2", help=("Comma separated list of tags. For the jstest portion of the suite(s)," " only tests which have at least one of the specified tags will be" " run.")) parser.add_option("-n", action="store_const", const="tests", dest="dry_run", help=("Output the tests that would be run.")) # TODO: add support for --dryRun=commands parser.add_option("--dryRun", type="choice", action="store", dest="dry_run", choices=("off", "tests"), metavar="MODE", help=("Instead of running the tests, output the tests that would be run" " (if MODE=tests). Defaults to MODE=%default.")) parser.add_option("-j", "--jobs", type="int", dest="jobs", metavar="JOBS", help=("The number of Job instances to use. Each instance will receive its own" " MongoDB deployment to dispatch tests to.")) parser.add_option("-l", "--listSuites", action="store_true", dest="list_suites", help="List the names of the suites available to execute.") parser.add_option("--mongo", dest="mongo_executable", metavar="PATH", help="The path to the mongo shell executable for resmoke.py to use.") parser.add_option("--mongod", dest="mongod_executable", metavar="PATH", help="The path to the mongod executable for resmoke.py to use.") parser.add_option("--mongodSetParameters", dest="mongod_parameters", metavar="{key1: value1, key2: value2, ..., keyN: valueN}", help=("Pass one or more --setParameter options to all mongod processes" " started by resmoke.py. The argument is specified as bracketed YAML -" " i.e. JSON with support for single quoted and unquoted keys.")) parser.add_option("--mongos", dest="mongos_executable", metavar="PATH", help="The path to the mongos executable for resmoke.py to use.") parser.add_option("--mongosSetParameters", dest="mongos_parameters", metavar="{key1: value1, key2: value2, ..., keyN: valueN}", help=("Pass one or more --setParameter options to all mongos processes" " started by resmoke.py. The argument is specified as bracketed YAML -" " i.e. JSON with support for single quoted and unquoted keys.")) parser.add_option("--nojournal", action="store_true", dest="no_journal", help="Disable journaling for all mongod's.") parser.add_option("--nopreallocj", action="store_const", const="off", dest="prealloc_journal", help="Disable preallocation of journal files for all mongod processes.") parser.add_option("--preallocJournal", type="choice", action="store", dest="prealloc_journal", choices=("on", "off"), metavar="ON|OFF", help=("Enable or disable preallocation of journal files for all mongod" " processes. Defaults to %default.")) parser.add_option("--repeat", type="int", dest="repeat", metavar="N", help="Repeat the given suite(s) N times, or until one fails.") parser.add_option("--reportFile", dest="report_file", metavar="REPORT", help="Write a JSON file with test status and timing information.") parser.add_option("--seed", type="int", dest="seed", metavar="SEED", help=("Seed for the random number generator. Useful in combination with the" " --shuffle option for producing a consistent test execution order.")) parser.add_option("--shellReadMode", type="choice", action="store", dest="shell_read_mode", choices=("commands", "compatibility", "legacy"), metavar="READ_MODE", help="The read mode used by the mongo shell.") parser.add_option("--shellWriteMode", type="choice", action="store", dest="shell_write_mode", choices=("commands", "compatibility", "legacy"), metavar="WRITE_MODE", help="The write mode used by the mongo shell.") parser.add_option("--shuffle", action="store_true", dest="shuffle", help="Randomize the order in which tests are executed.") parser.add_option("--storageEngine", dest="storage_engine", metavar="ENGINE", help="The storage engine used by dbtests and jstests.") parser.add_option("--wiredTigerCollectionConfigString", dest="wt_coll_config", metavar="CONFIG", help="Set the WiredTiger collection configuration setting for all mongod's.") parser.add_option("--wiredTigerEngineConfigString", dest="wt_engine_config", metavar="CONFIG", help="Set the WiredTiger engine configuration setting for all mongod's.") parser.add_option("--wiredTigerIndexConfigString", dest="wt_index_config", metavar="CONFIG", help="Set the WiredTiger index configuration setting for all mongod's.") parser.set_defaults(executor_file="with_server", logger_file="console", dry_run="off", list_suites=False, prealloc_journal="off") return parser.parse_args() def get_logging_config(values): return _get_logging_config(values.logger_file) def update_config_vars(values): options = _get_options_config(values.options_file) config = _config.DEFAULTS.copy() config.update(options) values = vars(values) for dest in values: if dest not in DEST_TO_CONFIG: continue config_var = DEST_TO_CONFIG[dest] if values[dest] is not None: config[config_var] = values[dest] _config.BASE_PORT = int(config.pop("basePort")) _config.BUILDLOGGER_URL = config.pop("buildloggerUrl") _config.DBPATH_PREFIX = _expand_user(config.pop("dbpathPrefix")) _config.DBTEST_EXECUTABLE = _expand_user(config.pop("dbtest")) _config.DRY_RUN = config.pop("dryRun") _config.EXCLUDE_WITH_ALL_TAGS = config.pop("excludeWithAllTags") _config.EXCLUDE_WITH_ANY_TAGS = config.pop("excludeWithAnyTags") _config.FAIL_FAST = not config.pop("continueOnFailure") _config.INCLUDE_WITH_ALL_TAGS = config.pop("includeWithAllTags") _config.INCLUDE_WITH_ANY_TAGS = config.pop("includeWithAnyTags") _config.JOBS = config.pop("jobs") _config.MONGO_EXECUTABLE = _expand_user(config.pop("mongo")) _config.MONGOD_EXECUTABLE = _expand_user(config.pop("mongod")) _config.MONGOD_SET_PARAMETERS = config.pop("mongodSetParameters") _config.MONGOS_EXECUTABLE = _expand_user(config.pop("mongos")) _config.MONGOS_SET_PARAMETERS = config.pop("mongosSetParameters") _config.NO_JOURNAL = config.pop("nojournal") _config.NO_PREALLOC_JOURNAL = config.pop("preallocJournal") == "off" _config.RANDOM_SEED = config.pop("seed") _config.REPEAT = config.pop("repeat") _config.REPORT_FILE = config.pop("reportFile") _config.SHELL_READ_MODE = config.pop("shellReadMode") _config.SHELL_WRITE_MODE = config.pop("shellWriteMode") _config.SHUFFLE = config.pop("shuffle") _config.STORAGE_ENGINE = config.pop("storageEngine") _config.WT_COLL_CONFIG = config.pop("wiredTigerCollectionConfigString") _config.WT_ENGINE_CONFIG = config.pop("wiredTigerEngineConfigString") _config.WT_INDEX_CONFIG = config.pop("wiredTigerIndexConfigString") if config: raise optparse.OptionValueError("Unknown option(s): %s" % (config.keys())) def get_suites(values, args): if (values.suite_files is None and not args) or (values.suite_files is not None and args): raise optparse.OptionValueError("Must specify either --suites or a list of tests") # If there are no suites specified, but there are args, assume they are jstests. if args: # No specified config, just use the following, and default the logging and executor. suite_config = _make_jstests_config(args) _ensure_executor(suite_config, values.executor_file) suite = testing.suite.Suite("", suite_config) return [suite] suite_files = values.suite_files.split(",") suites = [] for suite_filename in suite_files: suite_config = _get_suite_config(suite_filename) _ensure_executor(suite_config, values.executor_file) suite = testing.suite.Suite(suite_filename, suite_config) suites.append(suite) return suites def get_named_suites(): """ Returns the list of suites available to execute. """ # Skip "with_server" and "no_server" because they do not define any test files to run. executor_only = set(["with_server", "no_server"]) suite_names = [suite for suite in resmokeconfig.NAMED_SUITES if suite not in executor_only] suite_names.sort() return suite_names def _get_logging_config(pathname): """ Attempts to read a YAML configuration from 'pathname' that describes how resmoke.py should log the tests and fixtures. """ # Named loggers are specified as the basename of the file, without the .yml extension. if not utils.is_yaml_file(pathname) and not os.path.dirname(pathname): if pathname not in resmokeconfig.NAMED_LOGGERS: raise optparse.OptionValueError("Unknown logger '%s'" % (pathname)) pathname = resmokeconfig.NAMED_LOGGERS[pathname] # Expand 'pathname' to full path. if not utils.is_yaml_file(pathname) or not os.path.isfile(pathname): raise optparse.OptionValueError("Expected a logger YAML config, but got '%s'" % (pathname)) return utils.load_yaml_file(pathname).pop("logging") def _get_options_config(pathname): """ Attempts to read a YAML configuration from 'pathname' that describes any modifications to global options. """ if pathname is None: return {} return utils.load_yaml_file(pathname).pop("options") def _get_suite_config(pathname): """ Attempts to read a YAML configuration from 'pathname' that describes what tests to run and how to run them. """ # Named suites are specified as the basename of the file, without the .yml extension. if not utils.is_yaml_file(pathname) and not os.path.dirname(pathname): if pathname not in resmokeconfig.NAMED_SUITES: raise optparse.OptionValueError("Unknown suite '%s'" % (pathname)) pathname = resmokeconfig.NAMED_SUITES[pathname] # Expand 'pathname' to full path. if not utils.is_yaml_file(pathname) or not os.path.isfile(pathname): raise optparse.OptionValueError("Expected a suite YAML config, but got '%s'" % (pathname)) return utils.load_yaml_file(pathname) def _make_jstests_config(js_files): for pathname in js_files: if not utils.is_js_file(pathname) or not os.path.isfile(pathname): raise optparse.OptionValueError("Expected a list of JS files, but got '%s'" % (pathname)) return {"selector": {"js_test": {"roots": js_files}}} def _ensure_executor(suite_config, executor_pathname): if "executor" not in suite_config: # Named executors are specified as the basename of the file, without the .yml extension. if not utils.is_yaml_file(executor_pathname) and not os.path.dirname(executor_pathname): if executor_pathname not in resmokeconfig.NAMED_SUITES: raise optparse.OptionValueError("Unknown executor '%s'" % (executor_pathname)) executor_pathname = resmokeconfig.NAMED_SUITES[executor_pathname] if not utils.is_yaml_file(executor_pathname) or not os.path.isfile(executor_pathname): raise optparse.OptionValueError("Expected an executor YAML config, but got '%s'" % (executor_pathname)) suite_config["executor"] = utils.load_yaml_file(executor_pathname).pop("executor") def _expand_user(pathname): """ Wrapper around os.path.expanduser() to do nothing when given None. """ if pathname is None: return None return os.path.expanduser(pathname)