diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 002a8e448..ec5af05c6 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,17 @@ class Opts: metavar="OUTFILE", help="Write the JSON report to this file. Defaults to 'coverage.json'", ) + output_coverage = optparse.make_option( + '', '--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( + '', '--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'" + ) json_pretty_print = optparse.make_option( '', '--pretty-print', action='store_true', help="Format the JSON for human readers.", @@ -320,6 +332,8 @@ def get_prog_name(self): Opts.rcfile, ] +REPORT_ARGS = [Opts.input_coverage] + CMDS = { 'annotate': CmdOptionParser( "annotate", @@ -328,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 " + @@ -342,6 +356,7 @@ def get_prog_name(self): Opts.append, Opts.keep, Opts.quiet, + Opts.output_coverage ] + GLOBAL_ARGS, usage="[options] ... ", description=( @@ -369,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.", ), @@ -395,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. " + @@ -416,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." ), @@ -435,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." ), @@ -450,6 +465,7 @@ def get_prog_name(self): Opts.include, Opts.module, Opts.omit, + Opts.output_coverage, Opts.pylib, Opts.parallel_mode, Opts.source, @@ -469,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." ), @@ -572,8 +588,11 @@ def command_line(self, argv): else: concurrency = 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, 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..a6c8c0fce 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, @@ -245,6 +247,11 @@ def test_combine(self): cov.combine(None, strict=True, keep=False) cov.save() """) + self.cmd_executes("combine --data-file=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 @@ -300,6 +307,10 @@ def test_erase(self): cov = Coverage() cov.erase() """) + self.cmd_executes("erase --data-file=foo.cov", """\ + cov = Coverage(data_file="foo.cov") + cov.erase() + """) def test_version(self): # coverage --version @@ -510,6 +521,11 @@ def test_report(self): cov.load() cov.report(sort='-foo') """) + self.cmd_executes("report --data-file=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 ...] @@ -636,6 +652,15 @@ def test_run(self): cov.stop() cov.save() """) + 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() + 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