--- a/DebugClients/Python/coverage/cmdline.py Sat Apr 07 13:17:06 2018 +0200 +++ b/DebugClients/Python/coverage/cmdline.py Sat Apr 07 13:35:10 2018 +0200 @@ -3,6 +3,8 @@ """Command-line support for coverage.py.""" +from __future__ import print_function + import glob import optparse import os.path @@ -12,9 +14,10 @@ from coverage import env from coverage.collector import CTracer +from coverage.debug import info_formatter, info_header 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 +from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource +from coverage.results import should_fail_under class Opts(object): @@ -22,7 +25,7 @@ append = optparse.make_option( '-a', '--append', action='store_true', - help="Append coverage data to .coverage, otherwise it is started clean with each run.", + help="Append coverage data to .coverage, otherwise it starts clean each time.", ) branch = optparse.make_option( '', '--branch', action='store_true', @@ -48,7 +51,7 @@ help="Write the output files to DIR.", ) fail_under = optparse.make_option( - '', '--fail-under', action='store', metavar="MIN", type="int", + '', '--fail-under', action='store', metavar="MIN", type="float", help="Exit with a status of 2 if the total coverage is less than MIN.", ) help = optparse.make_option( @@ -216,7 +219,7 @@ class CmdOptionParser(CoverageOptionParser): """Parse one of the new-style commands for coverage.py.""" - def __init__(self, action, options=None, defaults=None, usage=None, description=None): + def __init__(self, action, options, defaults=None, usage=None, description=None): """Create an OptionParser for a coverage.py command. `action` is the slug to put into `options.action`. @@ -233,8 +236,7 @@ description=description, ) self.set_defaults(action=action, **(defaults or {})) - if options: - self.add_options(options) + self.add_options(options) self.cmd = action def __eq__(self, other): @@ -242,12 +244,14 @@ # results, and they will compare equal to objects. return (other == "<CmdOptionParser:%s>" % self.cmd) + __hash__ = None # This object doesn't need to be hashed. + def get_prog_name(self): """Override of an undocumented function in optparse.OptionParser.""" program_name = super(CmdOptionParser, self).get_prog_name() # Include the sub-command for this parser as part of the command. - return "%(command)s %(subcommand)s" % {'command': program_name, 'subcommand': self.cmd} + return "{command} {subcommand}".format(command=program_name, subcommand=self.cmd) GLOBAL_ARGS = [ @@ -274,8 +278,10 @@ 'combine': CmdOptionParser( "combine", - GLOBAL_ARGS, - usage="<path1> <path2> ... <pathN>", + [ + Opts.append, + ] + GLOBAL_ARGS, + usage="[options] <path1> <path2> ... <pathN>", description=( "Combine data from multiple coverage files collected " "with 'run -p'. The combined results are written to a single " @@ -299,7 +305,6 @@ 'erase': CmdOptionParser( "erase", GLOBAL_ARGS, - usage=" ", description="Erase previously collected coverage data.", ), @@ -318,6 +323,7 @@ Opts.include, Opts.omit, Opts.title, + Opts.skip_covered, ] + GLOBAL_ARGS, usage="[options] [modules]", description=( @@ -398,7 +404,11 @@ self.coverage = None - self.program_name = os.path.basename(sys.argv[0]) + program_path = sys.argv[0] + if program_path.endswith(os.path.sep + '__main__.py'): + # The path is the main module of a package; get that path instead. + program_path = os.path.dirname(program_path) + self.program_name = os.path.basename(program_path) if env.WINDOWS: # entry_points={'console_scripts':...} on Windows makes files # called coverage.exe, coverage3.exe, and coverage-3.5.exe. These @@ -443,10 +453,6 @@ if self.do_help(options, args, parser): return OK - # Check for conflicts and problems in the options. - 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] = '' @@ -458,7 +464,7 @@ debug = unshell_list(options.debug) # Do something. - self.coverage = self.covpkg.coverage( + self.coverage = self.covpkg.Coverage( data_suffix=options.parallel_mode, cover_pylib=options.pylib, timid=options.timid, @@ -482,9 +488,10 @@ return self.do_run(options, args) elif options.action == "combine": - self.coverage.load() + if options.append: + self.coverage.load() data_dirs = args or None - self.coverage.combine(data_dirs) + self.coverage.combine(data_dirs, strict=True) self.coverage.save() return OK @@ -509,7 +516,7 @@ elif options.action == "html": total = self.coverage.html_report( directory=options.directory, title=options.title, - **report_args) + skip_covered=options.skip_covered, **report_args) elif options.action == "xml": outfile = options.outfile total = self.coverage.xml_report(outfile=outfile, **report_args) @@ -520,20 +527,10 @@ 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 + fail_under = self.coverage.get_option("report:fail_under") + precision = self.coverage.get_option("report:precision") + if should_fail_under(total, fail_under, precision): + return FAIL_UNDER return OK @@ -541,8 +538,8 @@ """Display an error message, or the named topic.""" assert error or topic or parser if error: - print(error) - print("Use '%s help' for help." % (self.program_name,)) + print(error, file=sys.stderr) + print("Use '%s help' for help." % (self.program_name,), file=sys.stderr) elif parser: print(parser.format_help().strip()) else: @@ -591,25 +588,30 @@ return False - def args_ok(self, options, args): - """Check for conflicts and problems in the options. - - Returns True if everything is OK, or False if not. - - """ - if options.action == "run" and not args: - self.help_fn("Nothing to do.") - return False - - return True - def do_run(self, options, args): """Implementation of 'coverage run'.""" + if not args: + self.help_fn("Nothing to do.") + return ERR + 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 options.concurrency == "multiprocessing": + # Can't set other run-affecting command line options with + # multiprocessing. + for opt_name in ['branch', 'include', 'omit', 'pylib', 'source', 'timid']: + # As it happens, all of these options have no default, meaning + # they will be None if they have not been specified. + if getattr(options, opt_name) is not None: + self.help_fn( + "Options affecting multiprocessing must be specified " + "in a configuration file." + ) + return ERR + if not self.coverage.get_option("run:parallel"): if not options.append: self.coverage.erase() @@ -641,7 +643,7 @@ """Implementation of 'coverage debug'.""" if not args: - self.help_fn("What information would you like: data, sys?") + self.help_fn("What information would you like: config, data, sys?") return ERR for info in args: @@ -668,6 +670,11 @@ print(line) else: print("No data collected") + elif info == 'config': + print(info_header("config")) + config_info = self.coverage.config.__dict__.items() + for line in info_formatter(config_info): + print(" %s" % line) else: self.help_fn("Don't know what you mean by %r" % info) return ERR @@ -748,9 +755,9 @@ # An exception was caught while running the product code. The # sys.exc_info() return tuple is packed into an ExceptionDuringRun # exception. - traceback.print_exception(*err.args) + traceback.print_exception(*err.args) # pylint: disable=no-value-for-parameter status = ERR - except CoverageException as err: + except BaseCoverageException as err: # A controlled error inside coverage.py: print the message to the user. print(err) status = ERR