diff options
-rw-r--r-- | CHANGES.rst | 10 | ||||
-rw-r--r-- | coverage/cmdline.py | 11 | ||||
-rw-r--r-- | coverage/control.py | 4 | ||||
-rw-r--r-- | coverage/jsonreport.py | 2 | ||||
-rw-r--r-- | coverage/report.py | 5 | ||||
-rw-r--r-- | coverage/xmlreport.py | 2 | ||||
-rw-r--r-- | tests/test_cmdline.py | 41 | ||||
-rw-r--r-- | tests/test_concurrency.py | 9 | ||||
-rw-r--r-- | tests/test_plugins.py | 5 | ||||
-rw-r--r-- | tests/test_process.py | 4 | ||||
-rw-r--r-- | tests/test_report.py | 16 |
11 files changed, 88 insertions, 21 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index f7636e78..3d3bcb1b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -30,7 +30,14 @@ Unreleased - Feature: The HTML report pages for Python source files now have a sticky header so the file name and controls are always visible. -- Added support for PyPy 3.8. +- Feature: The ``xml`` and ``json`` commands now describe what they wrote + where. + +- Feature: The ``html``, ``combine``, ``xml``, and ``json`` commands all accept + a ``-q/--quiet`` option to suppress the messages they write to stdout about + what they are doing (`issue 1254`_). + +- Feature: Added support for PyPy 3.8. - Fix: more generated code is now excluded from measurement. Code such as `attrs`_ boilerplate, or doctest code, was being measured though the @@ -51,6 +58,7 @@ Unreleased .. _issue 553: https://github.com/nedbat/coveragepy/issues/553 .. _issue 840: https://github.com/nedbat/coveragepy/issues/840 .. _issue 1160: https://github.com/nedbat/coveragepy/issues/1160 +.. _issue 1254: https://github.com/nedbat/coveragepy/issues/1254 .. _attrs: https://www.attrs.org/ diff --git a/coverage/cmdline.py b/coverage/cmdline.py index 81b87edf..e996ffff 100644 --- a/coverage/cmdline.py +++ b/coverage/cmdline.py @@ -146,6 +146,10 @@ class Opts: "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=( @@ -227,6 +231,7 @@ class CoverageOptionParser(optparse.OptionParser): parallel_mode=None, precision=None, pylib=None, + quiet=None, rcfile=True, show_contexts=None, show_missing=None, @@ -340,6 +345,7 @@ CMDS = { [ Opts.append, Opts.keep, + Opts.quiet, ] + GLOBAL_ARGS, usage="[options] <path1> <path2> ... <pathN>", description=( @@ -387,6 +393,7 @@ CMDS = { Opts.include, Opts.omit, Opts.precision, + Opts.quiet, Opts.show_contexts, Opts.skip_covered, Opts.no_skip_covered, @@ -411,6 +418,7 @@ CMDS = { Opts.omit, Opts.output_json, Opts.json_pretty_print, + Opts.quiet, Opts.show_contexts, ] + GLOBAL_ARGS, usage="[options] [modules]", @@ -463,6 +471,7 @@ CMDS = { Opts.include, Opts.omit, Opts.output_xml, + Opts.quiet, Opts.skip_empty, ] + GLOBAL_ARGS, usage="[options] [modules]", @@ -576,7 +585,7 @@ class CoverageScript: concurrency=options.concurrency, check_preimported=True, context=options.context, - messages=True, + messages=not options.quiet, ) if options.action == "debug": diff --git a/coverage/control.py b/coverage/control.py index defe9209..a96f558a 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -1006,7 +1006,7 @@ class Coverage: ignore_errors=ignore_errors, report_omit=omit, report_include=include, xml_output=outfile, report_contexts=contexts, skip_empty=skip_empty, ): - return render_report(self.config.xml_output, XmlReporter(self), morfs) + return render_report(self.config.xml_output, XmlReporter(self), morfs, self._message) def json_report( self, morfs=None, outfile=None, ignore_errors=None, @@ -1030,7 +1030,7 @@ class Coverage: json_output=outfile, report_contexts=contexts, json_pretty_print=pretty_print, json_show_contexts=show_contexts ): - return render_report(self.config.json_output, JsonReporter(self), morfs) + return render_report(self.config.json_output, JsonReporter(self), morfs, self._message) def sys_info(self): """Return a list of (key, value) pairs showing internal information.""" diff --git a/coverage/jsonreport.py b/coverage/jsonreport.py index daebca11..c87bb0e7 100644 --- a/coverage/jsonreport.py +++ b/coverage/jsonreport.py @@ -14,6 +14,8 @@ from coverage.results import Numbers class JsonReporter: """A reporter for writing JSON coverage results.""" + report_type = "JSON report" + def __init__(self, coverage): self.coverage = coverage self.config = self.coverage.config diff --git a/coverage/report.py b/coverage/report.py index 82994ecf..112dcae1 100644 --- a/coverage/report.py +++ b/coverage/report.py @@ -2,6 +2,7 @@ # For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt """Reporter foundation for coverage.py.""" + import sys from coverage.exceptions import CoverageException, NoSource, NotPython @@ -9,7 +10,7 @@ from coverage.files import prep_patterns, FnmatchMatcher from coverage.misc import ensure_dir_for_file, file_be_gone -def render_report(output_path, reporter, morfs): +def render_report(output_path, reporter, morfs, msgfn): """Run a one-file report generator, managing the output file. This function ensures the output file is ready to be written to. Then writes @@ -40,6 +41,8 @@ def render_report(output_path, reporter, morfs): file_to_close.close() if delete_file: file_be_gone(output_path) # pragma: part covered (doesn't return) + else: + msgfn(f"Wrote {reporter.report_type} to {output_path}") def get_analysis_to_report(coverage, morfs): diff --git a/coverage/xmlreport.py b/coverage/xmlreport.py index 6dc330f1..8c8409dc 100644 --- a/coverage/xmlreport.py +++ b/coverage/xmlreport.py @@ -30,6 +30,8 @@ def rate(hit, num): class XmlReporter: """A reporter for writing Cobertura-style XML coverage results.""" + report_type = "XML report" + def __init__(self, coverage): self.coverage = coverage self.config = self.coverage.config diff --git a/tests/test_cmdline.py b/tests/test_cmdline.py index b0716e6d..9e987760 100644 --- a/tests/test_cmdline.py +++ b/tests/test_cmdline.py @@ -234,6 +234,17 @@ class CmdLineTest(BaseCmdLineTest): cov.combine(None, strict=True, keep=False) cov.save() """) + # coverage combine quietly + self.cmd_executes("combine -q", """\ + cov = Coverage(messages=False) + cov.combine(None, strict=True, keep=False) + cov.save() + """) + self.cmd_executes("combine --quiet", """\ + cov = Coverage(messages=False) + 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 @@ -335,6 +346,16 @@ class CmdLineTest(BaseCmdLineTest): cov.load() cov.html_report(title='Hello_there') """) + self.cmd_executes("html -q", """\ + cov = Coverage(messages=False) + cov.load() + cov.html_report() + """) + self.cmd_executes("html --quiet", """\ + cov = Coverage(messages=False) + cov.load() + cov.html_report() + """) def test_json(self): # coverage json [-i] [--omit DIR,...] [FILE1 FILE2 ...] @@ -388,6 +409,16 @@ class CmdLineTest(BaseCmdLineTest): cov.load() cov.json_report(morfs=["mod1", "mod2", "mod3"]) """) + self.cmd_executes("json -q", """\ + cov = Coverage(messages=False) + cov.load() + cov.json_report() + """) + self.cmd_executes("json --quiet", """\ + cov = Coverage(messages=False) + cov.load() + cov.json_report() + """) def test_report(self): # coverage report [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...] @@ -753,6 +784,16 @@ class CmdLineTest(BaseCmdLineTest): cov.load() cov.xml_report(morfs=["mod1", "mod2", "mod3"]) """) + self.cmd_executes("xml -q", """\ + cov = Coverage(messages=False) + cov.load() + cov.xml_report() + """) + self.cmd_executes("xml --quiet", """\ + cov = Coverage(messages=False) + cov.load() + cov.xml_report() + """) def test_no_arguments_at_all(self): self.cmd_help("", topic="minimum_help", ret=OK) diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 696b12eb..c39b0163 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -450,13 +450,8 @@ class MultiprocessingTest(CoverageTest): out = self.run_command(f"coverage run --rcfile=multi.rc multi.py {start_method}") assert out.rstrip() == expected_out - out = self.run_command("coverage combine") - out_lines = out.splitlines() - assert len(out_lines) == nprocs + 1 - assert all( - re.fullmatch(r"Combined data file \.coverage\..*\.\d+\.\d+", line) - for line in out_lines - ) + out = self.run_command("coverage combine -q") # sneak in a test of -q + assert out == "" out = self.run_command("coverage report -m") last_line = self.squeezed_lines(out)[-1] diff --git a/tests/test_plugins.py b/tests/test_plugins.py index ecc464cc..17c7ba44 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -6,7 +6,6 @@ import inspect import io import os.path -import re from xml.etree import ElementTree import pytest @@ -256,8 +255,8 @@ class PluginTest(CoverageTest): out = self.run_command("coverage run main_file.py") assert out == "MAIN\n" - out = self.run_command("coverage html") - assert re.fullmatch(r"Wrote HTML report to htmlcov[/\\]index.html\n", out) + out = self.run_command("coverage html -q") # sneak in a test of -q + assert out == "" @pytest.mark.skipif(env.C_TRACER, reason="This test is only about PyTracer.") diff --git a/tests/test_process.py b/tests/test_process.py index 10312232..65a6e75d 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -1325,7 +1325,7 @@ class UnicodeFilePathsTest(CoverageTest): # The XML report is always UTF8-encoded. out = self.run_command("coverage xml") - assert out == "" + assert out == "Wrote XML report to coverage.xml\n" with open("coverage.xml", "rb") as xmlf: xml = xmlf.read() assert ' filename="h\xe2t.py"'.encode() in xml @@ -1358,7 +1358,7 @@ class UnicodeFilePathsTest(CoverageTest): assert expected % os.sep in index # The XML report is always UTF8-encoded. - out = self.run_command("coverage xml") + out = self.run_command("coverage xml -q") assert out == "" with open("coverage.xml", "rb") as xmlf: xml = xmlf.read() diff --git a/tests/test_report.py b/tests/test_report.py index 1a9dd179..7d6f55af 100644 --- a/tests/test_report.py +++ b/tests/test_report.py @@ -13,6 +13,8 @@ from tests.coveragetest import CoverageTest class FakeReporter: """A fake implementation of a one-file reporter.""" + report_type = "fake report file" + def __init__(self, output="", error=False): self.output = output self.error = error @@ -31,20 +33,26 @@ class RenderReportTest(CoverageTest): def test_stdout(self): fake = FakeReporter(output="Hello!\n") - render_report("-", fake, [pytest, "coverage"]) + msgs = [] + render_report("-", fake, [pytest, "coverage"], msgs.append) assert fake.morfs == [pytest, "coverage"] assert self.stdout() == "Hello!\n" + assert msgs == [] def test_file(self): fake = FakeReporter(output="Gréètings!\n") - render_report("output.txt", fake, []) + msgs = [] + render_report("output.txt", fake, [], msgs.append) assert self.stdout() == "" with open("output.txt", "rb") as f: - assert f.read() == b"Gr\xc3\xa9\xc3\xa8tings!\n" + assert f.read().rstrip() == b"Gr\xc3\xa9\xc3\xa8tings!" + assert msgs == ["Wrote fake report file to output.txt"] def test_exception(self): fake = FakeReporter(error=True) + msgs = [] with pytest.raises(CoverageException, match="You asked for it!"): - render_report("output.txt", fake, []) + render_report("output.txt", fake, [], msgs.append) assert self.stdout() == "" self.assert_doesnt_exist("output.txt") + assert msgs == [] |