DebugClients/Python3/coverage/cmdline.py

changeset 4489
d0d6e4ad31bd
parent 3495
fac17a82b431
child 5051
3586ebd9fac8
--- 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:

eric ide

mercurial