--- a/DebugClients/Python3/coverage/cmdline.py Sun Oct 04 13:35:09 2015 +0200 +++ b/DebugClients/Python3/coverage/cmdline.py Sun Oct 04 22:37:56 2015 +0200 @@ -1,18 +1,25 @@ -"""Command-line support for Coverage.""" +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0 +# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt -import optparse, os, sys, time, traceback +"""Command-line support for coverage.py.""" -from .backward import sorted # pylint: disable=W0622 -from .execfile import run_python_file, run_python_module -from .misc import CoverageException, ExceptionDuringRun, NoSource -from .debug import info_formatter +import glob +import optparse +import os.path +import sys +import traceback + +from coverage import env +from coverage.execfile import run_python_file, run_python_module +from coverage.misc import CoverageException, ExceptionDuringRun, NoSource +from coverage.debug import info_formatter, info_header class Opts(object): """A namespace class for individual options we'll build parsers from.""" append = optparse.make_option( - '-a', '--append', action='store_false', dest="erase_first", + '-a', '--append', action='store_true', help="Append coverage data to .coverage, otherwise it is started " "clean with each run." ) @@ -20,6 +27,15 @@ '', '--branch', action='store_true', help="Measure branch coverage in addition to statement coverage." ) + CONCURRENCY_CHOICES = [ + "thread", "gevent", "greenlet", "eventlet", "multiprocessing", + ] + concurrency = optparse.make_option( + '', '--concurrency', action='store', metavar="LIB", + choices=CONCURRENCY_CHOICES, + help="Properly measure code using a concurrency library. " + "Valid values are: %s." % ", ".join(CONCURRENCY_CHOICES) + ) debug = optparse.make_option( '', '--debug', action='store', metavar="OPTS", help="Debug options, separated by commas" @@ -43,8 +59,8 @@ include = optparse.make_option( '', '--include', action='store', metavar="PAT1,PAT2,...", - help="Include files only when their filename path matches one of " - "these patterns. Usually needs quoting on the command line." + help="Include only files whose paths match one of these patterns. " + "Accepts shell-style wildcards, which must be quoted." ) pylib = optparse.make_option( '-L', '--pylib', action='store_true', @@ -56,17 +72,15 @@ help="Show line numbers of statements in each module that weren't " "executed." ) - old_omit = optparse.make_option( - '-o', '--omit', action='store', - metavar="PAT1,PAT2,...", - help="Omit files when their filename matches one of these patterns. " - "Usually needs quoting on the command line." + skip_covered = optparse.make_option( + '--skip-covered', action='store_true', + help="Skip files with 100% coverage." ) omit = optparse.make_option( '', '--omit', action='store', metavar="PAT1,PAT2,...", - help="Omit files when their filename matches one of these patterns. " - "Usually needs quoting on the command line." + help="Omit files whose paths match one of these patterns. " + "Accepts shell-style wildcards, which must be quoted." ) output_xml = optparse.make_option( '-o', '', action='store', dest="outfile", @@ -108,7 +122,7 @@ class CoverageOptionParser(optparse.OptionParser, object): - """Base OptionParser for coverage. + """Base OptionParser for coverage.py. Problems don't exit the program. Defaults are initialized for all options. @@ -120,24 +134,26 @@ add_help_option=False, *args, **kwargs ) self.set_defaults( - actions=[], + action=None, + append=None, branch=None, + concurrency=None, debug=None, directory=None, fail_under=None, help=None, ignore_errors=None, include=None, + module=None, omit=None, parallel_mode=None, - module=None, pylib=None, rcfile=True, show_missing=None, + skip_covered=None, source=None, timid=None, title=None, - erase_first=None, version=None, ) @@ -152,7 +168,7 @@ """Used to stop the optparse error handler ending the process.""" pass - def parse_args(self, args=None, options=None): + def parse_args_ok(self, args=None, options=None): """Call optparse.parse_args, but return a triple: (ok, options, args) @@ -171,70 +187,44 @@ raise self.OptionParserError -class ClassicOptionParser(CoverageOptionParser): - """Command-line parser for coverage.py classic arguments.""" +class GlobalOptionParser(CoverageOptionParser): + """Command-line parser for coverage.py global option arguments.""" def __init__(self): - super(ClassicOptionParser, self).__init__() - - self.add_action('-a', '--annotate', 'annotate') - self.add_action('-b', '--html', 'html') - self.add_action('-c', '--combine', 'combine') - self.add_action('-e', '--erase', 'erase') - self.add_action('-r', '--report', 'report') - self.add_action('-x', '--execute', 'execute') + super(GlobalOptionParser, self).__init__() self.add_options([ - Opts.directory, Opts.help, - Opts.ignore_errors, - Opts.pylib, - Opts.show_missing, - Opts.old_omit, - Opts.parallel_mode, - Opts.timid, Opts.version, ]) - def add_action(self, dash, dashdash, action_code): - """Add a specialized option that is the action to execute.""" - option = self.add_option(dash, dashdash, action='callback', - callback=self._append_action - ) - option.action_code = action_code - - def _append_action(self, option, opt_unused, value_unused, parser): - """Callback for an option that adds to the `actions` list.""" - parser.values.actions.append(option.action_code) - class CmdOptionParser(CoverageOptionParser): """Parse one of the new-style commands for coverage.py.""" def __init__(self, action, options=None, defaults=None, usage=None, - cmd=None, description=None + description=None ): - """Create an OptionParser for a coverage command. + """Create an OptionParser for a coverage.py command. - `action` is the slug to put into `options.actions`. + `action` is the slug to put into `options.action`. `options` is a list of Option's for the command. `defaults` is a dict of default value for options. `usage` is the usage string to display in help. - `cmd` is the command name, if different than `action`. `description` is the description of the command, for the help text. """ if usage: usage = "%prog " + usage super(CmdOptionParser, self).__init__( - prog="coverage %s" % (cmd or action), + prog="coverage %s" % action, usage=usage, description=description, ) - self.set_defaults(actions=[action], **(defaults or {})) + self.set_defaults(action=action, **(defaults or {})) if options: self.add_options(options) - self.cmd = cmd or action + self.cmd = action def __eq__(self, other): # A convenience equality, so that I can put strings in unit test @@ -242,8 +232,9 @@ return (other == "<CmdOptionParser:%s>" % self.cmd) GLOBAL_ARGS = [ + Opts.debug, + Opts.help, Opts.rcfile, - Opts.help, ] CMDS = { @@ -251,8 +242,8 @@ [ Opts.directory, Opts.ignore_errors, + Opts.include, Opts.omit, - Opts.include, ] + GLOBAL_ARGS, usage = "[options] [modules]", description = "Make annotated copies of the given files, marking " @@ -261,10 +252,13 @@ ), 'combine': CmdOptionParser("combine", GLOBAL_ARGS, - usage = " ", + usage = "<path1> <path2> ... <pathN>", description = "Combine data from multiple coverage files collected " "with 'run -p'. The combined results are written to a single " - "file representing the union of the data." + "file representing the union of the data. The positional " + "arguments are data files or directories containing data files. " + "If no paths are provided, data files in the default data file's " + "directory are combined." ), 'debug': CmdOptionParser("debug", GLOBAL_ARGS, @@ -290,8 +284,8 @@ Opts.directory, Opts.fail_under, Opts.ignore_errors, + Opts.include, Opts.omit, - Opts.include, Opts.title, ] + GLOBAL_ARGS, usage = "[options] [modules]", @@ -304,29 +298,28 @@ [ Opts.fail_under, Opts.ignore_errors, + Opts.include, Opts.omit, - Opts.include, Opts.show_missing, + Opts.skip_covered, ] + GLOBAL_ARGS, usage = "[options] [modules]", description = "Report coverage statistics on modules." ), - 'run': CmdOptionParser("execute", + 'run': CmdOptionParser("run", [ Opts.append, Opts.branch, - Opts.debug, + Opts.concurrency, + Opts.include, + Opts.module, + Opts.omit, Opts.pylib, Opts.parallel_mode, - Opts.module, + Opts.source, Opts.timid, - Opts.source, - Opts.omit, - Opts.include, ] + GLOBAL_ARGS, - defaults = {'erase_first': True}, - cmd = "run", usage = "[options] <pyfile> [program options]", description = "Run a Python program, measuring code execution." ), @@ -335,11 +328,10 @@ [ Opts.fail_under, Opts.ignore_errors, + Opts.include, Opts.omit, - Opts.include, Opts.output_xml, ] + GLOBAL_ARGS, - cmd = "xml", usage = "[options] [modules]", description = "Generate an XML report of coverage results." ), @@ -350,27 +342,28 @@ class CoverageScript(object): - """The command-line interface to Coverage.""" + """The command-line interface to coverage.py.""" def __init__(self, _covpkg=None, _run_python_file=None, - _run_python_module=None, _help_fn=None): + _run_python_module=None, _help_fn=None, _path_exists=None): # _covpkg is for dependency injection, so we can test this code. if _covpkg: self.covpkg = _covpkg else: - from . import coverage + import coverage self.covpkg = coverage # For dependency injection: self.run_python_file = _run_python_file or run_python_file self.run_python_module = _run_python_module or run_python_module self.help_fn = _help_fn or self.help - self.classic = False + self.path_exists = _path_exists or os.path.exists + self.global_option = False self.coverage = None def command_line(self, argv): - """The bulk of the command line interface to Coverage. + """The bulk of the command line interface to coverage.py. `argv` is the argument list to process. @@ -382,11 +375,11 @@ self.help_fn(topic='minimum_help') return OK - # The command syntax we parse depends on the first argument. Classic - # syntax always starts with an option. - self.classic = argv[0].startswith('-') - if self.classic: - parser = ClassicOptionParser() + # The command syntax we parse depends on the first argument. Global + # switch syntax always starts with an option. + self.global_option = argv[0].startswith('-') + if self.global_option: + parser = GlobalOptionParser() else: parser = CMDS.get(argv[0]) if not parser: @@ -395,7 +388,7 @@ argv = argv[1:] parser.help_fn = self.help_fn - ok, options, args = parser.parse_args(argv) + ok, options, args = parser.parse_args_ok(argv) if not ok: return ERR @@ -407,6 +400,10 @@ if not self.args_ok(options, args): return ERR + # We need to be able to import from the current directory, because + # plugins may try to, for example, to read Django settings. + sys.path[0] = '' + # Listify the list options. source = unshell_list(options.source) omit = unshell_list(options.omit) @@ -424,52 +421,74 @@ omit = omit, include = include, debug = debug, + concurrency = options.concurrency, ) - if 'debug' in options.actions: + if options.action == "debug": return self.do_debug(args) - if 'erase' in options.actions or options.erase_first: + elif options.action == "erase": self.coverage.erase() - else: - self.coverage.load() + return OK + + elif options.action == "run": + return self.do_run(options, args) - if 'execute' in options.actions: - self.do_execute(options, args) - - if 'combine' in options.actions: - self.coverage.combine() + elif options.action == "combine": + self.coverage.load() + data_dirs = args or None + self.coverage.combine(data_dirs) self.coverage.save() + return OK # Remaining actions are reporting, with some common options. report_args = dict( - morfs = args, + morfs = unglob_args(args), ignore_errors = options.ignore_errors, omit = omit, include = include, ) - if 'report' in options.actions: + self.coverage.load() + + total = None + if options.action == "report": total = self.coverage.report( - show_missing=options.show_missing, **report_args) - if 'annotate' in options.actions: + show_missing=options.show_missing, + skip_covered=options.skip_covered, **report_args) + elif options.action == "annotate": self.coverage.annotate( directory=options.directory, **report_args) - if 'html' in options.actions: + elif options.action == "html": total = self.coverage.html_report( directory=options.directory, title=options.title, **report_args) - if 'xml' in options.actions: + elif options.action == "xml": outfile = options.outfile total = self.coverage.xml_report(outfile=outfile, **report_args) - if options.fail_under is not None: - if total >= options.fail_under: - return OK - else: - return FAIL_UNDER - else: - return OK + if total is not None: + # Apply the command line fail-under options, and then use the config + # value, so we can get fail_under from the config file. + if options.fail_under is not None: + self.coverage.set_option("report:fail_under", options.fail_under) + + if self.coverage.get_option("report:fail_under"): + + # Total needs to be rounded, but be careful of 0 and 100. + if 0 < total < 1: + total = 1 + elif 99 < total < 100: + total = 99 + else: + total = round(total) + + if total >= self.coverage.get_option("report:fail_under"): + return OK + else: + return FAIL_UNDER + + return OK def help(self, error=None, topic=None, parser=None): """Display an error message, or the named topic.""" @@ -494,13 +513,13 @@ """ # Handle help. if options.help: - if self.classic: + if self.global_option: self.help_fn(topic='help') else: self.help_fn(parser=parser) return True - if "help" in options.actions: + if options.action == "help": if args: for a in args: parser = CMDS.get(a) @@ -522,67 +541,48 @@ def args_ok(self, options, args): """Check for conflicts and problems in the options. - Returns True if everything is ok, or False if not. + Returns True if everything is OK, or False if not. """ - for i in ['erase', 'execute']: - for j in ['annotate', 'html', 'report', 'combine']: - if (i in options.actions) and (j in options.actions): - self.help_fn("You can't specify the '%s' and '%s' " - "options at the same time." % (i, j)) - return False - - if not options.actions: - self.help_fn( - "You must specify at least one of -e, -x, -c, -r, -a, or -b." - ) - return False - args_allowed = ( - 'execute' in options.actions or - 'annotate' in options.actions or - 'html' in options.actions or - 'debug' in options.actions or - 'report' in options.actions or - 'xml' in options.actions - ) - if not args_allowed and args: - self.help_fn("Unexpected arguments: %s" % " ".join(args)) - return False - - if 'execute' in options.actions and not args: + if options.action == "run" and not args: self.help_fn("Nothing to do.") return False return True - def do_execute(self, options, args): + def do_run(self, options, args): """Implementation of 'coverage run'.""" - # Set the first path element properly. - old_path0 = sys.path[0] + if options.append and self.coverage.get_option("run:parallel"): + self.help_fn("Can't append to data files in parallel mode.") + return ERR + + if not self.coverage.get_option("run:parallel"): + if not options.append: + self.coverage.erase() # Run the script. self.coverage.start() code_ran = True try: - try: - if options.module: - sys.path[0] = '' - self.run_python_module(args[0], args) - else: - filename = args[0] - sys.path[0] = os.path.abspath(os.path.dirname(filename)) - self.run_python_file(filename, args) - except NoSource: - code_ran = False - raise + if options.module: + self.run_python_module(args[0], args) + else: + filename = args[0] + self.run_python_file(filename, args) + except NoSource: + code_ran = False + raise finally: self.coverage.stop() if code_ran: + if options.append: + data_file = self.coverage.get_option("run:data_file") + if self.path_exists(data_file): + self.coverage.combine(data_paths=[data_file]) self.coverage.save() - # Restore the old path - sys.path[0] = old_path0 + return OK def do_debug(self, args): """Implementation of 'coverage debug'.""" @@ -590,27 +590,35 @@ if not args: self.help_fn("What information would you like: data, sys?") return ERR + for info in args: if info == 'sys': - print("-- sys ----------------------------------------") - for line in info_formatter(self.coverage.sysinfo()): + sys_info = self.coverage.sys_info() + print(info_header("sys")) + for line in info_formatter(sys_info): print(" %s" % line) elif info == 'data': - print("-- data ---------------------------------------") self.coverage.load() - print("path: %s" % self.coverage.data.filename) - print("has_arcs: %r" % self.coverage.data.has_arcs()) - summary = self.coverage.data.summary(fullpath=True) - if summary: + data = self.coverage.data + print(info_header("data")) + print("path: %s" % self.coverage.data_files.filename) + if data: + print("has_arcs: %r" % data.has_arcs()) + summary = data.line_counts(fullpath=True) filenames = sorted(summary.keys()) print("\n%d files:" % len(filenames)) for f in filenames: - print("%s: %d lines" % (f, summary[f])) + line = "%s: %d lines" % (f, summary[f]) + plugin = data.file_tracer(f) + if plugin: + line += " [%s]" % plugin + print(line) else: print("No data collected") else: self.help_fn("Don't know what you mean by %r" % info) return ERR + return OK @@ -618,64 +626,30 @@ """Turn a command-line argument into a list.""" if not s: return None - if sys.platform == 'win32': - # When running coverage as coverage.exe, some of the behavior + if env.WINDOWS: + # When running coverage.py as coverage.exe, some of the behavior # of the shell is emulated: wildcards are expanded into a list of - # filenames. So you have to single-quote patterns on the command + # file names. So you have to single-quote patterns on the command # line, but (not) helpfully, the single quotes are included in the # argument, so we have to strip them off here. s = s.strip("'") return s.split(',') -HELP_TOPICS = { -# ------------------------- -'classic': -r"""Coverage.py version %(__version__)s -Measure, collect, and report on code coverage in Python programs. - -Usage: - -coverage -x [-p] [-L] [--timid] MODULE.py [ARG1 ARG2 ...] - Execute the module, passing the given command-line arguments, collecting - coverage data. With the -p option, include the machine name and process - id in the .coverage file name. With -L, measure coverage even inside the - Python installed library, which isn't done by default. With --timid, use a - simpler but slower trace method. - -coverage -e - Erase collected coverage data. - -coverage -c - Combine data from multiple coverage files (as created by -p option above) - and store it into a single file representing the union of the coverage. +def unglob_args(args): + """Interpret shell wildcards for platforms that need it.""" + if env.WINDOWS: + globbed = [] + for arg in args: + if '?' in arg or '*' in arg: + globbed.extend(glob.glob(arg)) + else: + globbed.append(arg) + args = globbed + return args -coverage -r [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...] - Report on the statement coverage for the given files. With the -m - option, show line numbers of the statements that weren't executed. -coverage -b -d DIR [-i] [-o DIR,...] [FILE1 FILE2 ...] - Create an HTML report of the coverage of the given files. Each file gets - its own page, with the file listing decorated to show executed, excluded, - and missed lines. - -coverage -a [-d DIR] [-i] [-o DIR,...] [FILE1 FILE2 ...] - Make annotated copies of the given files, marking statements that - are executed with > and statements that are missed with !. - --d DIR - Write output files for -b or -a to this directory. - --i Ignore errors while reporting or annotating. - --o DIR,... - Omit reporting or annotating files when their filename path starts with - a directory listed in the omit list. - e.g. coverage -i -r -o c:\python25,lib\enthought\traits - -Coverage data is saved in the file .coverage by default. Set the -COVERAGE_FILE environment variable to save it somewhere else. -""", +HELP_TOPICS = { # ------------------------- 'help': """\ Coverage.py, version %(__version__)s @@ -694,8 +668,7 @@ xml Create an XML report of coverage results. Use "coverage help <command>" for detailed help on any command. -Use "coverage help classic" for help on older command syntax. -For more information, see %(__url__)s +For full documentation, see %(__url__)s """, # ------------------------- 'minimum_help': """\ @@ -703,13 +676,14 @@ """, # ------------------------- 'version': """\ -Coverage.py, version %(__version__)s. %(__url__)s +Coverage.py, version %(__version__)s. +Documentation at %(__url__)s """, } def main(argv=None): - """The main entry point to Coverage. + """The main entry point to coverage.py. This is installed as the script entry point. @@ -717,26 +691,19 @@ if argv is None: argv = sys.argv[1:] try: - start = time.clock() status = CoverageScript().command_line(argv) - end = time.clock() - if 0: - print("time: %.3fs" % (end - start)) - except ExceptionDuringRun: + except ExceptionDuringRun as err: # An exception was caught while running the product code. The # sys.exc_info() return tuple is packed into an ExceptionDuringRun # exception. - _, err, _ = sys.exc_info() traceback.print_exception(*err.args) status = ERR - except CoverageException: + except CoverageException as err: # A controlled error inside coverage.py: print the message to the user. - _, err, _ = sys.exc_info() print(err) status = ERR - except SystemExit: + except SystemExit as err: # The user called `sys.exit()`. Exit with their argument, if any. - _, err, _ = sys.exc_info() if err.args: status = err.args[0] else: