From cbcdedb64a7f8127cc8e2b58e10e3a36338f1827 Mon Sep 17 00:00:00 2001 From: Nikita Bloshchanevich Date: Sat, 15 Jan 2022 14:47:01 +0100 Subject: [PATCH 1/5] coverage run: add an output option (-o) Similar to coverage report/xml/json, coverage run now has a -o option to configure the file the SQLite coverage data will be stored in. The default is still ".coverage", as previously. Since data_file now needs to be specified in cmdline.py, _DEFAULT_DATAFILE needs to be exposed (to avoid hacks like constructing the argument dictionary manually). Rename it to non-undescore and create an alias for it under the previous name (just in case someone used it anyway). --- coverage/cmdline.py | 10 ++++++++++ coverage/control.py | 7 ++++--- tests/test_cmdline.py | 11 +++++++++++ 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 002a8e448..269fa3661 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -18,6 +18,7 @@ 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.exceptions import _BaseCoverageException, _ExceptionDuringRun, NoSource @@ -123,6 +124,11 @@ class Opts: metavar="OUTFILE", help="Write the JSON report to this file. Defaults to 'coverage.json'", ) + output_coverage = optparse.make_option( + '-o', '', action='store', dest="outfile", + metavar="OUTFILE", + help="Write the recorded coverage information to this file. Defaults to '.coverage'" + ) json_pretty_print = optparse.make_option( '', '--pretty-print', action='store_true', help="Format the JSON for human readers.", @@ -450,6 +456,7 @@ def get_prog_name(self): Opts.include, Opts.module, Opts.omit, + Opts.output_coverage, Opts.pylib, Opts.parallel_mode, Opts.source, @@ -572,8 +579,11 @@ def command_line(self, argv): else: concurrency = None + data_file = options.outfile if options.action in ["run", "combine"] \ + else getattr(options, "input_coverage", None) # Do something. self.coverage = Coverage( + data_file=data_file or DEFAULT_DATAFILE, data_suffix=options.parallel_mode, cover_pylib=options.pylib, timid=options.timid, diff --git a/coverage/control.py b/coverage/control.py index 99319c056..5c4371def 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -60,7 +60,8 @@ def override_config(cov, **kwargs): cov.config = original_config -_DEFAULT_DATAFILE = DefaultValue("MISSING") +DEFAULT_DATAFILE = DefaultValue("MISSING") +_DEFAULT_DATAFILE = DEFAULT_DATAFILE # Just in case, for backwards compatibility class Coverage: """Programmatic access to coverage.py. @@ -101,7 +102,7 @@ def current(cls): return None def __init__( - self, data_file=_DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None, + self, data_file=DEFAULT_DATAFILE, data_suffix=None, cover_pylib=None, auto_data=False, timid=None, branch=None, config_file=True, source=None, source_pkgs=None, omit=None, include=None, debug=None, concurrency=None, check_preimported=False, context=None, @@ -198,7 +199,7 @@ def __init__( # data_file=None means no disk file at all. data_file missing means # use the value from the config file. self._no_disk = data_file is None - if data_file is _DEFAULT_DATAFILE: + if data_file is DEFAULT_DATAFILE: data_file = None self.config = None diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 42f313f81..ae8aecc8c 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -14,6 +14,7 @@ import coverage import coverage.cmdline from coverage import env +from coverage.control import DEFAULT_DATAFILE from coverage.config import CoverageConfig from coverage.exceptions import _ExceptionDuringRun from coverage.version import __url__ @@ -53,6 +54,7 @@ class BaseCmdLineTest(CoverageTest): contexts=None, pretty_print=None, show_contexts=None, ) _defaults.Coverage( + data_file=DEFAULT_DATAFILE, cover_pylib=None, data_suffix=None, timid=None, branch=None, config_file=True, source=None, include=None, omit=None, debug=None, concurrency=None, check_preimported=True, context=None, messages=True, @@ -636,6 +638,15 @@ def test_run(self): cov.stop() cov.save() """) + self.cmd_executes("run -o output.coverage foo.py", """\ + cov = Coverage(data_file="output.coverage") + runner = PyRunner(['foo.py'], as_module=False) + runner.prepare() + cov.start() + runner.run() + cov.stop() + cov.save() + """) def test_multiprocessing_needs_config_file(self): # You can't use command-line args to add options to multiprocessing From 477fd70a2e44099bdf7c49c86157bd0ab3795cf7 Mon Sep 17 00:00:00 2001 From: Nikita Bloshchanevich Date: Sat, 15 Jan 2022 14:57:58 +0100 Subject: [PATCH 2/5] Add an input coverage option to report commands -o for coverage run is not very useful if the commands that consume coverage and generate reports/coverage (json/xml/html/combine ...) can't take that file as input, so add a -c/--input-coverage option for these commands. --- coverage/cmdline.py | 22 +++++++++++++++------- tests/test_cmdline.py | 10 ++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 269fa3661..80fabc9f7 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -129,6 +129,12 @@ class Opts: metavar="OUTFILE", help="Write the recorded coverage information to this file. Defaults to '.coverage'" ) + input_coverage = optparse.make_option( + '-c', '--input-coverage', action='store', dest="input_coverage", + metavar="INPUT", + help="Read coverage data for report generation from this file (needed if you have " + "specified -o previously). Defaults to '.coverage'" + ) json_pretty_print = optparse.make_option( '', '--pretty-print', action='store_true', help="Format the JSON for human readers.", @@ -326,6 +332,8 @@ def get_prog_name(self): Opts.rcfile, ] +REPORT_ARGS = [Opts.input_coverage] + CMDS = { 'annotate': CmdOptionParser( "annotate", @@ -334,7 +342,7 @@ def get_prog_name(self): Opts.ignore_errors, Opts.include, Opts.omit, - ] + GLOBAL_ARGS, + ] + REPORT_ARGS + GLOBAL_ARGS, usage="[options] [modules]", description=( "Make annotated copies of the given files, marking statements that are executed " + @@ -348,6 +356,7 @@ def get_prog_name(self): Opts.append, Opts.keep, Opts.quiet, + Opts.output_coverage ] + GLOBAL_ARGS, usage="[options] ... ", description=( @@ -401,7 +410,7 @@ def get_prog_name(self): Opts.no_skip_covered, Opts.skip_empty, Opts.title, - ] + GLOBAL_ARGS, + ] + REPORT_ARGS + GLOBAL_ARGS, usage="[options] [modules]", description=( "Create an HTML report of the coverage of the files. " + @@ -422,7 +431,7 @@ def get_prog_name(self): Opts.json_pretty_print, Opts.quiet, Opts.show_contexts, - ] + GLOBAL_ARGS, + ] + REPORT_ARGS + GLOBAL_ARGS, usage="[options] [modules]", description="Generate a JSON report of coverage results." ), @@ -441,7 +450,7 @@ def get_prog_name(self): Opts.skip_covered, Opts.no_skip_covered, Opts.skip_empty, - ] + GLOBAL_ARGS, + ] + REPORT_ARGS + GLOBAL_ARGS, usage="[options] [modules]", description="Report coverage statistics on modules." ), @@ -476,7 +485,7 @@ def get_prog_name(self): Opts.output_xml, Opts.quiet, Opts.skip_empty, - ] + GLOBAL_ARGS, + ] + REPORT_ARGS + GLOBAL_ARGS, usage="[options] [modules]", description="Generate an XML report of coverage results." ), @@ -579,8 +588,7 @@ def command_line(self, argv): else: concurrency = None - data_file = options.outfile if options.action in ["run", "combine"] \ - else getattr(options, "input_coverage", None) + data_file = options.outfile if options.action == "run" else None # Do something. self.coverage = Coverage( data_file=data_file or DEFAULT_DATAFILE, diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index ae8aecc8c..9adca7ed1 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -247,6 +247,11 @@ def test_combine(self): cov.combine(None, strict=True, keep=False) cov.save() """) + self.cmd_executes("combine -o foo.cov", """\ + cov = Coverage(data_file="foo.cov") + cov.combine(None, strict=True, keep=False) + cov.save() + """) def test_combine_doesnt_confuse_options_with_args(self): # https://github.com/nedbat/coveragepy/issues/385 @@ -512,6 +517,11 @@ def test_report(self): cov.load() cov.report(sort='-foo') """) + self.cmd_executes("report -c foo.cov.2", """\ + cov = Coverage(data_file="foo.cov.2") + cov.load() + cov.report(show_missing=None) + """) def test_run(self): # coverage run [-p] [-L] [--timid] MODULE.py [ARG1 ARG2 ...] From 2e7ed0061d3e0f1e966d5ebeb339fc5c26f85243 Mon Sep 17 00:00:00 2001 From: Nikita Bloshchanevich Date: Sat, 15 Jan 2022 15:00:11 +0100 Subject: [PATCH 3/5] coverage erase: add -c as well --- coverage/cmdline.py | 2 +- tests/test_cmdline.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 80fabc9f7..9aea885bf 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -384,7 +384,7 @@ def get_prog_name(self): ), 'erase': CmdOptionParser( - "erase", GLOBAL_ARGS, + "erase", [Opts.input_coverage] + GLOBAL_ARGS, description="Erase previously collected coverage data.", ), diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 9adca7ed1..653c23412 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -307,6 +307,10 @@ def test_erase(self): cov = Coverage() cov.erase() """) + self.cmd_executes("erase -c foo.cov", """\ + cov = Coverage(data_file="foo.cov") + cov.erase() + """) def test_version(self): # coverage --version From 6cda394c3fbc2bc5fc23a1135bd898c78ee3a544 Mon Sep 17 00:00:00 2001 From: Nikita Bloshchanevich Date: Sat, 15 Jan 2022 15:13:53 +0100 Subject: [PATCH 4/5] Fix input_coverage ignored --- coverage/cmdline.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 9aea885bf..b8c317f2b 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -125,7 +125,7 @@ class Opts: help="Write the JSON report to this file. Defaults to 'coverage.json'", ) output_coverage = optparse.make_option( - '-o', '', action='store', dest="outfile", + '-o', '', action='store', dest="output_coverage", metavar="OUTFILE", help="Write the recorded coverage information to this file. Defaults to '.coverage'" ) @@ -588,7 +588,8 @@ def command_line(self, argv): else: concurrency = None - data_file = options.outfile if options.action == "run" else None + data_file = getattr(options, "output_coverage", None) \ + or getattr(options, "input_coverage", None) # Do something. self.coverage = Coverage( data_file=data_file or DEFAULT_DATAFILE, From 2316b42d8e6d6f57156944cb4b689b4f6c82c10a Mon Sep 17 00:00:00 2001 From: Nikita Bloshchanevich Date: Fri, 21 Jan 2022 18:00:19 +0100 Subject: [PATCH 5/5] Use --data-file to configure the coverage database Request by maintainer. --- coverage/cmdline.py | 4 ++-- tests/test_cmdline.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/coverage/cmdline.py b/coverage/cmdline.py index b8c317f2b..ec5af05c6 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -125,12 +125,12 @@ class Opts: help="Write the JSON report to this file. Defaults to 'coverage.json'", ) output_coverage = optparse.make_option( - '-o', '', action='store', dest="output_coverage", + '', '--data-file', action='store', dest="output_coverage", metavar="OUTFILE", help="Write the recorded coverage information to this file. Defaults to '.coverage'" ) input_coverage = optparse.make_option( - '-c', '--input-coverage', action='store', dest="input_coverage", + '', '--data-file', action='store', dest="input_coverage", metavar="INPUT", help="Read coverage data for report generation from this file (needed if you have " "specified -o previously). Defaults to '.coverage'" diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index 653c23412..a6c8c0fce 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -247,7 +247,7 @@ def test_combine(self): cov.combine(None, strict=True, keep=False) cov.save() """) - self.cmd_executes("combine -o foo.cov", """\ + self.cmd_executes("combine --data-file=foo.cov", """\ cov = Coverage(data_file="foo.cov") cov.combine(None, strict=True, keep=False) cov.save() @@ -307,7 +307,7 @@ def test_erase(self): cov = Coverage() cov.erase() """) - self.cmd_executes("erase -c foo.cov", """\ + self.cmd_executes("erase --data-file=foo.cov", """\ cov = Coverage(data_file="foo.cov") cov.erase() """) @@ -521,7 +521,7 @@ def test_report(self): cov.load() cov.report(sort='-foo') """) - self.cmd_executes("report -c foo.cov.2", """\ + self.cmd_executes("report --data-file=foo.cov.2", """\ cov = Coverage(data_file="foo.cov.2") cov.load() cov.report(show_missing=None) @@ -652,7 +652,7 @@ def test_run(self): cov.stop() cov.save() """) - self.cmd_executes("run -o output.coverage foo.py", """\ + self.cmd_executes("run --data-file=output.coverage foo.py", """\ cov = Coverage(data_file="output.coverage") runner = PyRunner(['foo.py'], as_module=False) runner.prepare()