--- a/eric7/DebugClients/Python/coverage/cmdline.py Sun Mar 20 17:26:35 2022 +0100 +++ b/eric7/DebugClients/Python/coverage/cmdline.py Sun Mar 20 17:49:44 2022 +0100 @@ -3,7 +3,6 @@ """Command-line support for coverage.py.""" - import glob import optparse # pylint: disable=deprecated-module import os @@ -18,16 +17,22 @@ from coverage import env from coverage.collector import CTracer from coverage.config import CoverageConfig +from coverage.control import DEFAULT_DATAFILE from coverage.data import combinable_files, debug_data_file -from coverage.debug import info_formatter, info_header, short_stack +from coverage.debug import info_header, short_stack, write_formatted_info from coverage.exceptions import _BaseCoverageException, _ExceptionDuringRun, NoSource from coverage.execfile import PyRunner from coverage.results import Numbers, should_fail_under +# When adding to this file, alphabetization is important. Look for +# "alphabetize" comments throughout. class Opts: """A namespace class for individual options we'll build parsers from.""" + # Keep these entries alphabetized (roughly) by the option name as it + # appears on the command line. + append = optparse.make_option( '-a', '--append', action='store_true', help="Append coverage data to .coverage, otherwise it starts clean each time.", @@ -52,13 +57,33 @@ help="The context label to record for this coverage run.", ) contexts = optparse.make_option( - '', '--contexts', action='store', - metavar="REGEX1,REGEX2,...", + '', '--contexts', action='store', metavar="REGEX1,REGEX2,...", help=( "Only display data from lines covered in the given contexts. " + "Accepts Python regexes, which must be quoted." ), ) + combine_datafile = optparse.make_option( + '', '--data-file', action='store', metavar="DATAFILE", + help=( + "Base name of the data files to operate on. " + + "Defaults to '.coverage'. [env: COVERAGE_FILE]" + ), + ) + input_datafile = optparse.make_option( + '', '--data-file', action='store', metavar="INFILE", + help=( + "Read coverage data for report generation from this file. " + + "Defaults to '.coverage'. [env: COVERAGE_FILE]" + ), + ) + output_datafile = optparse.make_option( + '', '--data-file', action='store', metavar="OUTFILE", + help=( + "Write the recorded coverage data to this file. " + + "Defaults to '.coverage'. [env: COVERAGE_FILE]" + ), + ) debug = optparse.make_option( '', '--debug', action='store', metavar="OPTS", help="Debug options, separated by commas. [env: COVERAGE_DEBUG]", @@ -80,8 +105,7 @@ help="Ignore errors while reading source files.", ) include = optparse.make_option( - '', '--include', action='store', - metavar="PAT1,PAT2,...", + '', '--include', action='store', metavar="PAT1,PAT2,...", help=( "Include only files whose paths match one of these patterns. " + "Accepts shell-style wildcards, which must be quoted." @@ -106,23 +130,24 @@ ), ) omit = optparse.make_option( - '', '--omit', action='store', - metavar="PAT1,PAT2,...", + '', '--omit', action='store', metavar="PAT1,PAT2,...", 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", - metavar="OUTFILE", + '-o', '', action='store', dest="outfile", metavar="OUTFILE", help="Write the XML report to this file. Defaults to 'coverage.xml'", ) output_json = optparse.make_option( - '-o', '', action='store', dest="outfile", - metavar="OUTFILE", + '-o', '', action='store', dest="outfile", metavar="OUTFILE", help="Write the JSON report to this file. Defaults to 'coverage.json'", ) + output_lcov = optparse.make_option( + '-o', '', action='store', dest='outfile', metavar="OUTFILE", + help="Write the LCOV report to this file. Defaults to 'coverage.lcov'", + ) json_pretty_print = optparse.make_option( '', '--pretty-print', action='store_true', help="Format the JSON for human readers.", @@ -131,7 +156,7 @@ '-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 " + + "data file name to simplify collecting data from " + "many processes." ), ) @@ -172,8 +197,10 @@ ) sort = optparse.make_option( '--sort', action='store', metavar='COLUMN', - help="Sort the report by the named column: name, stmts, miss, branch, brpart, or cover. " + + 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,...", @@ -209,12 +236,14 @@ add_help_option=False, *args, **kwargs ) self.set_defaults( + # Keep these arguments alphabetized by their names. action=None, append=None, branch=None, concurrency=None, context=None, contexts=None, + data_file=None, debug=None, directory=None, fail_under=None, @@ -313,6 +342,11 @@ # Include the sub-command for this parser as part of the command. return f"{program_name} {self.cmd}" +# In lists of Opts, keep them alphabetized by the option names as they appear +# on the command line, since these lists determine the order of the options in +# the help output. +# +# In COMMANDS, keep the keys (command names) alphabetized. GLOBAL_ARGS = [ Opts.debug, @@ -320,11 +354,12 @@ Opts.rcfile, ] -CMDS = { +COMMANDS = { 'annotate': CmdOptionParser( "annotate", [ Opts.directory, + Opts.input_datafile, Opts.ignore_errors, Opts.include, Opts.omit, @@ -340,6 +375,7 @@ "combine", [ Opts.append, + Opts.combine_datafile, Opts.keep, Opts.quiet, ] + GLOBAL_ARGS, @@ -364,12 +400,16 @@ "'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." + "'premain' to show what is calling coverage; " + + "'pybehave' to show internal flags describing Python behavior." ), ), 'erase': CmdOptionParser( - "erase", GLOBAL_ARGS, + "erase", + [ + Opts.combine_datafile + ] + GLOBAL_ARGS, description="Erase previously collected coverage data.", ), @@ -384,6 +424,7 @@ [ Opts.contexts, Opts.directory, + Opts.input_datafile, Opts.fail_under, Opts.ignore_errors, Opts.include, @@ -408,6 +449,7 @@ "json", [ Opts.contexts, + Opts.input_datafile, Opts.fail_under, Opts.ignore_errors, Opts.include, @@ -418,13 +460,29 @@ Opts.show_contexts, ] + GLOBAL_ARGS, usage="[options] [modules]", - description="Generate a JSON report of coverage results." + description="Generate a JSON report of coverage results.", + ), + + 'lcov': CmdOptionParser( + "lcov", + [ + Opts.input_datafile, + Opts.fail_under, + Opts.ignore_errors, + Opts.include, + Opts.output_lcov, + Opts.omit, + Opts.quiet, + ] + GLOBAL_ARGS, + usage="[options] [modules]", + description="Generate an LCOV report of coverage results.", ), 'report': CmdOptionParser( "report", [ Opts.contexts, + Opts.input_datafile, Opts.fail_under, Opts.ignore_errors, Opts.include, @@ -437,7 +495,7 @@ Opts.skip_empty, ] + GLOBAL_ARGS, usage="[options] [modules]", - description="Report coverage statistics on modules." + description="Report coverage statistics on modules.", ), 'run': CmdOptionParser( @@ -447,6 +505,7 @@ Opts.branch, Opts.concurrency, Opts.context, + Opts.output_datafile, Opts.include, Opts.module, Opts.omit, @@ -456,12 +515,13 @@ Opts.timid, ] + GLOBAL_ARGS, usage="[options] <pyfile> [program options]", - description="Run a Python program, measuring code execution." + description="Run a Python program, measuring code execution.", ), 'xml': CmdOptionParser( "xml", [ + Opts.input_datafile, Opts.fail_under, Opts.ignore_errors, Opts.include, @@ -471,7 +531,7 @@ Opts.skip_empty, ] + GLOBAL_ARGS, usage="[options] [modules]", - description="Generate an XML report of coverage results." + description="Generate an XML report of coverage results.", ), } @@ -546,7 +606,7 @@ if self.global_option: parser = GlobalOptionParser() else: - parser = CMDS.get(argv[0]) + parser = COMMANDS.get(argv[0]) if not parser: show_help(f"Unknown command: {argv[0]!r}") return ERR @@ -574,6 +634,7 @@ # Do something. self.coverage = Coverage( + data_file=options.data_file or DEFAULT_DATAFILE, data_suffix=options.parallel_mode, cover_pylib=options.pylib, timid=options.timid, @@ -625,10 +686,10 @@ total = None if options.action == "report": total = self.coverage.report( + precision=options.precision, show_missing=options.show_missing, skip_covered=options.skip_covered, skip_empty=options.skip_empty, - precision=options.precision, sort=options.sort, **report_args ) @@ -637,27 +698,31 @@ elif options.action == "html": total = self.coverage.html_report( directory=options.directory, - title=options.title, + precision=options.precision, skip_covered=options.skip_covered, skip_empty=options.skip_empty, show_contexts=options.show_contexts, - precision=options.precision, + title=options.title, **report_args ) elif options.action == "xml": - outfile = options.outfile total = self.coverage.xml_report( - outfile=outfile, skip_empty=options.skip_empty, + outfile=options.outfile, + skip_empty=options.skip_empty, **report_args ) elif options.action == "json": - outfile = options.outfile total = self.coverage.json_report( - outfile=outfile, + outfile=options.outfile, pretty_print=options.pretty_print, show_contexts=options.show_contexts, **report_args - ) + ) + elif options.action == "lcov": + total = self.coverage.lcov_report( + outfile=options.outfile, + **report_args + ) else: # There are no other possible actions. raise AssertionError @@ -667,6 +732,8 @@ # 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 options.precision is not None: + self.coverage.set_option("report:precision", options.precision) fail_under = self.coverage.get_option("report:fail_under") precision = self.coverage.get_option("report:precision") @@ -698,7 +765,7 @@ if options.action == "help": if args: for a in args: - parser = CMDS.get(a) + parser = COMMANDS.get(a) if parser: show_help(parser=parser) else: @@ -777,32 +844,28 @@ """Implementation of 'coverage debug'.""" if not args: - show_help("What information would you like: config, data, sys, premain?") + show_help("What information would you like: config, data, sys, premain, pybehave?") return ERR if args[1:]: show_help("Only one topic at a time, please") return ERR - if args[0] == 'sys': - sys_info = self.coverage.sys_info() - print(info_header("sys")) - for line in info_formatter(sys_info): - print(f" {line}") - elif args[0] == 'data': + if args[0] == "sys": + write_formatted_info(print, "sys", self.coverage.sys_info()) + elif args[0] == "data": print(info_header("data")) data_file = self.coverage.config.data_file debug_data_file(data_file) for filename in combinable_files(data_file): print("-----") debug_data_file(filename) - elif args[0] == 'config': - print(info_header("config")) - config_info = sorted(self.coverage.config.__dict__.items()) - for line in info_formatter(config_info): - print(f" {line}") + elif args[0] == "config": + write_formatted_info(print, "config", self.coverage.config.debug_info()) elif args[0] == "premain": print(info_header("premain")) print(short_stack()) + elif args[0] == "pybehave": + write_formatted_info(print, "pybehave", env.debug_info()) else: show_help(f"Don't know what you mean by {args[0]!r}") return ERR @@ -852,6 +915,7 @@ help Get help on using coverage.py. html Create an HTML report. json Create a JSON report of coverage results. + lcov Create an LCOV report of coverage results. report Report coverage stats on modules. run Run a Python program and measure code execution. xml Create an XML report of coverage results.