--- a/eric7/DebugClients/Python/coverage/cmdline.py Fri Nov 19 19:28:47 2021 +0100 +++ b/eric7/DebugClients/Python/coverage/cmdline.py Sat Nov 20 16:47:38 2021 +0100 @@ -3,10 +3,10 @@ """Command-line support for coverage.py.""" -from __future__ import print_function import glob -import optparse +import optparse # pylint: disable=deprecated-module +import os import os.path import shlex import sys @@ -19,12 +19,13 @@ from coverage.collector import CTracer from coverage.data import line_counts from coverage.debug import info_formatter, info_header, short_stack +from coverage.exceptions import BaseCoverageException, ExceptionDuringRun, NoSource from coverage.execfile import PyRunner -from coverage.misc import BaseCoverageException, ExceptionDuringRun, NoSource, output_encoding -from coverage.results import should_fail_under +from coverage.misc import human_sorted +from coverage.results import Numbers, should_fail_under -class Opts(object): +class Opts: """A namespace class for individual options we'll build parsers from.""" append = optparse.make_option( @@ -46,14 +47,22 @@ '', '--concurrency', action='store', metavar="LIB", choices=CONCURRENCY_CHOICES, help=( - "Properly measure code using a concurrency library. " - "Valid values are: %s." - ) % ", ".join(CONCURRENCY_CHOICES), + "Properly measure code using a concurrency library. " + + "Valid values are: {}." + ).format(", ".join(CONCURRENCY_CHOICES)), ) context = optparse.make_option( '', '--context', action='store', metavar="LABEL", help="The context label to record for this coverage run.", ) + contexts = optparse.make_option( + '', '--contexts', action='store', + metavar="REGEX1,REGEX2,...", + help=( + "Only display data from lines covered in the given contexts. " + + "Accepts Python regexes, which must be quoted." + ), + ) debug = optparse.make_option( '', '--debug', action='store', metavar="OPTS", help="Debug options, separated by commas. [env: COVERAGE_DEBUG]", @@ -78,58 +87,36 @@ '', '--include', action='store', metavar="PAT1,PAT2,...", help=( - "Include only files whose paths match one of these patterns. " + "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', help=( - "Measure coverage even inside the Python installed library, " + "Measure coverage even inside the Python installed library, " + "which isn't done by default." ), ) - sort = optparse.make_option( - '--sort', action='store', metavar='COLUMN', - help="Sort the report by the named column: name, stmts, miss, branch, brpart, or cover. " - "Default is name." - ) show_missing = optparse.make_option( '-m', '--show-missing', action='store_true', help="Show line numbers of statements in each module that weren't executed.", ) - skip_covered = optparse.make_option( - '--skip-covered', action='store_true', - help="Skip files with 100% coverage.", - ) - no_skip_covered = optparse.make_option( - '--no-skip-covered', action='store_false', dest='skip_covered', - help="Disable --skip-covered.", - ) - skip_empty = optparse.make_option( - '--skip-empty', action='store_true', - help="Skip files with no code.", - ) - show_contexts = optparse.make_option( - '--show-contexts', action='store_true', - help="Show contexts for covered lines.", + module = optparse.make_option( + '-m', '--module', action='store_true', + help=( + "<pyfile> is an importable Python module, not a script path, " + + "to be run as 'python -m' would run it." + ), ) omit = optparse.make_option( '', '--omit', action='store', metavar="PAT1,PAT2,...", help=( - "Omit files whose paths match one of these patterns. " + "Omit files whose paths match one of these patterns. " + "Accepts shell-style wildcards, which must be quoted." ), ) - contexts = optparse.make_option( - '', '--contexts', action='store', - metavar="REGEX1,REGEX2,...", - help=( - "Only display data from lines covered in the given contexts. " - "Accepts Python regexes, which must be quoted." - ), - ) output_xml = optparse.make_option( '-o', '', action='store', dest="outfile", metavar="OUTFILE", @@ -147,41 +134,59 @@ parallel_mode = optparse.make_option( '-p', '--parallel-mode', action='store_true', help=( - "Append the machine name, process id and random number to the " - ".coverage data file name to simplify collecting data from " + "Append the machine name, process id and random number to the " + + ".coverage data file name to simplify collecting data from " + "many processes." ), ) - module = optparse.make_option( - '-m', '--module', action='store_true', - help=( - "<pyfile> is an importable Python module, not a script path, " - "to be run as 'python -m' would run it." - ), - ) precision = optparse.make_option( '', '--precision', action='store', metavar='N', type=int, help=( - "Number of digits after the decimal point to display for " + "Number of digits after the decimal point to display for " + "reported coverage percentages." ), ) + quiet = optparse.make_option( + '-q', '--quiet', action='store_true', + help="Don't print messages about what is happening.", + ) rcfile = optparse.make_option( '', '--rcfile', action='store', help=( - "Specify configuration file. " - "By default '.coveragerc', 'setup.cfg', 'tox.ini', and " + "Specify configuration file. " + + "By default '.coveragerc', 'setup.cfg', 'tox.ini', and " + "'pyproject.toml' are tried. [env: COVERAGE_RCFILE]" ), ) + show_contexts = optparse.make_option( + '--show-contexts', action='store_true', + help="Show contexts for covered lines.", + ) + skip_covered = optparse.make_option( + '--skip-covered', action='store_true', + help="Skip files with 100% coverage.", + ) + no_skip_covered = optparse.make_option( + '--no-skip-covered', action='store_false', dest='skip_covered', + help="Disable --skip-covered.", + ) + skip_empty = optparse.make_option( + '--skip-empty', action='store_true', + help="Skip files with no code.", + ) + sort = optparse.make_option( + '--sort', action='store', metavar='COLUMN', + help="Sort the report by the named column: name, stmts, miss, branch, brpart, or cover. " + + "Default is name." + ) source = optparse.make_option( '', '--source', action='store', metavar="SRC1,SRC2,...", - help="A list of packages or directories of code to be measured.", + help="A list of directories or importable names of code to measure.", ) timid = optparse.make_option( '', '--timid', action='store_true', help=( - "Use a simpler but slower trace method. Try this if you get " + "Use a simpler but slower trace method. Try this if you get " + "seemingly impossible results!" ), ) @@ -195,7 +200,7 @@ ) -class CoverageOptionParser(optparse.OptionParser, object): +class CoverageOptionParser(optparse.OptionParser): """Base OptionParser for coverage.py. Problems don't exit the program. @@ -204,7 +209,7 @@ """ def __init__(self, *args, **kwargs): - super(CoverageOptionParser, self).__init__( + super().__init__( add_help_option=False, *args, **kwargs ) self.set_defaults( @@ -213,6 +218,7 @@ branch=None, concurrency=None, context=None, + contexts=None, debug=None, directory=None, fail_under=None, @@ -222,15 +228,15 @@ keep=None, module=None, omit=None, - contexts=None, parallel_mode=None, precision=None, pylib=None, + quiet=None, rcfile=True, + show_contexts=None, show_missing=None, skip_covered=None, skip_empty=None, - show_contexts=None, sort=None, source=None, timid=None, @@ -251,7 +257,7 @@ """ try: - options, args = super(CoverageOptionParser, self).parse_args(args, options) + options, args = super().parse_args(args, options) except self.OptionParserError: return False, None, None return True, options, args @@ -266,7 +272,7 @@ """Command-line parser for coverage.py global option arguments.""" def __init__(self): - super(GlobalOptionParser, self).__init__() + super().__init__() self.add_options([ Opts.help, @@ -289,7 +295,7 @@ """ if usage: usage = "%prog " + usage - super(CmdOptionParser, self).__init__( + super().__init__( usage=usage, description=description, ) @@ -300,16 +306,16 @@ def __eq__(self, other): # A convenience equality, so that I can put strings in unit test # results, and they will compare equal to objects. - return (other == "<CmdOptionParser:%s>" % self.cmd) + return (other == f"<CmdOptionParser:{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() + program_name = super().get_prog_name() # Include the sub-command for this parser as part of the command. - return "{command} {subcommand}".format(command=program_name, subcommand=self.cmd) + return f"{program_name} {self.cmd}" GLOBAL_ARGS = [ @@ -329,7 +335,7 @@ ] + GLOBAL_ARGS, usage="[options] [modules]", description=( - "Make annotated copies of the given files, marking statements that are executed " + "Make annotated copies of the given files, marking statements that are executed " + "with > and statements that are missed with !." ), ), @@ -339,14 +345,15 @@ [ Opts.append, Opts.keep, + Opts.quiet, ] + 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 " - "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 " + "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. 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." ), ), @@ -355,12 +362,12 @@ "debug", GLOBAL_ARGS, usage="<topic>", description=( - "Display information about the internals of coverage.py, " - "for diagnosing problems. " - "Topics are: " - "'data' to show a summary of the collected data; " - "'sys' to show installation information; " - "'config' to show the configuration; " + "Display information about the internals of coverage.py, " + + "for diagnosing problems. " + + "Topics are: " + + "'data' to show a summary of the collected data; " + + "'sys' to show installation information; " + + "'config' to show the configuration; " + "'premain' to show what is calling coverage." ), ), @@ -386,6 +393,7 @@ Opts.include, Opts.omit, Opts.precision, + Opts.quiet, Opts.show_contexts, Opts.skip_covered, Opts.no_skip_covered, @@ -394,8 +402,8 @@ ] + GLOBAL_ARGS, usage="[options] [modules]", description=( - "Create an HTML report of the coverage of the files. " - "Each file gets its own page, with the source decorated to show " + "Create an HTML report of the coverage of the files. " + + "Each file gets its own page, with the source decorated to show " + "executed, excluded, and missed lines." ), ), @@ -410,6 +418,7 @@ Opts.omit, Opts.output_json, Opts.json_pretty_print, + Opts.quiet, Opts.show_contexts, ] + GLOBAL_ARGS, usage="[options] [modules]", @@ -462,6 +471,7 @@ Opts.include, Opts.omit, Opts.output_xml, + Opts.quiet, Opts.skip_empty, ] + GLOBAL_ARGS, usage="[options] [modules]", @@ -498,7 +508,7 @@ if error: print(error, file=sys.stderr) - print("Use '%s help' for help." % (program_name,), file=sys.stderr) + print(f"Use '{program_name} help' for help.", file=sys.stderr) elif parser: print(parser.format_help().strip()) print() @@ -507,14 +517,14 @@ if help_msg: print(help_msg.format(**help_params)) else: - print("Don't know topic %r" % topic) + print(f"Don't know topic {topic!r}") print("Full documentation is at {__url__}".format(**help_params)) OK, ERR, FAIL_UNDER = 0, 1, 2 -class CoverageScript(object): +class CoverageScript: """The command-line interface to coverage.py.""" def __init__(self): @@ -542,7 +552,7 @@ else: parser = CMDS.get(argv[0]) if not parser: - show_help("Unknown command: '%s'" % argv[0]) + show_help(f"Unknown command: {argv[0]!r}") return ERR argv = argv[1:] @@ -575,6 +585,7 @@ concurrency=options.concurrency, check_preimported=True, context=options.context, + messages=not options.quiet, ) if options.action == "debug": @@ -646,6 +657,9 @@ show_contexts=options.show_contexts, **report_args ) + else: + # There are no other possible actions. + raise AssertionError if total is not None: # Apply the command line fail-under options, and then use the config @@ -656,8 +670,10 @@ fail_under = self.coverage.get_option("report:fail_under") precision = self.coverage.get_option("report:precision") if should_fail_under(total, fail_under, precision): - msg = "total of {total:.{p}f} is less than fail-under={fail_under:.{p}f}".format( - total=total, fail_under=fail_under, p=precision, + msg = "total of {total} is less than fail-under={fail_under:.{p}f}".format( + total=Numbers(precision=precision).display_covered(total), + fail_under=fail_under, + p=precision, ) print("Coverage failure:", msg) return FAIL_UNDER @@ -708,7 +724,7 @@ command_line = self.coverage.get_option("run:command_line") if command_line is not None: args = shlex.split(command_line) - if args and args[0] == "-m": + if args and args[0] in {"-m", "--module"}: options.module = True args = args[1:] if not args: @@ -727,12 +743,14 @@ # they will be None if they have not been specified. if getattr(options, opt_name) is not None: show_help( - "Options affecting multiprocessing must only be specified " - "in a configuration file.\n" - "Remove --{} from the command line.".format(opt_name) + "Options affecting multiprocessing must only be specified " + + "in a configuration file.\n" + + f"Remove --{opt_name} from the command line." ) return ERR + os.environ["COVERAGE_RUN"] = "true" + runner = PyRunner(args, as_module=bool(options.module)) runner.prepare() @@ -766,22 +784,22 @@ sys_info = self.coverage.sys_info() print(info_header("sys")) for line in info_formatter(sys_info): - print(" %s" % line) + print(f" {line}") elif info == 'data': self.coverage.load() data = self.coverage.get_data() print(info_header("data")) - print("path: %s" % data.data_filename()) + print(f"path: {data.data_filename()}") if data: - print("has_arcs: %r" % data.has_arcs()) + print(f"has_arcs: {data.has_arcs()!r}") summary = line_counts(data, fullpath=True) - filenames = sorted(summary.keys()) - print("\n%d files:" % len(filenames)) + filenames = human_sorted(summary.keys()) + print(f"\n{len(filenames)} files:") for f in filenames: - line = "%s: %d lines" % (f, summary[f]) + line = f"{f}: {summary[f]} lines" plugin = data.file_tracer(f) if plugin: - line += " [%s]" % plugin + line += f" [{plugin}]" print(line) else: print("No data collected") @@ -789,12 +807,12 @@ print(info_header("config")) config_info = self.coverage.config.__dict__.items() for line in info_formatter(config_info): - print(" %s" % line) + print(f" {line}") elif info == "premain": print(info_header("premain")) print(short_stack()) else: - show_help("Don't know what you mean by %r" % info) + show_help(f"Don't know what you mean by {info!r}") return ERR return OK @@ -878,8 +896,6 @@ except BaseCoverageException as err: # A controlled error inside coverage.py: print the message to the user. msg = err.args[0] - if env.PY2: - msg = msg.encode(output_encoding()) print(msg) status = ERR except SystemExit as err: