summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2021-10-26 19:07:03 -0400
committerNed Batchelder <ned@nedbatchelder.com>2021-10-26 19:17:19 -0400
commit93c9ca9f1b2e5d0b45dbf4b82c77aaf05b458bac (patch)
tree6b0e1cc009da5e33ca87e8fb096704b27afe1a8f
parent18cf3b897d4b1e1a66beda180ec151cc0dd4dbc3 (diff)
downloadpython-coveragepy-git-93c9ca9f1b2e5d0b45dbf4b82c77aaf05b458bac.tar.gz
feat: xml and json say what they are doing, and -q quiets everything. #1254nedbat/dashq
-rw-r--r--CHANGES.rst10
-rw-r--r--coverage/cmdline.py11
-rw-r--r--coverage/control.py4
-rw-r--r--coverage/jsonreport.py2
-rw-r--r--coverage/report.py5
-rw-r--r--coverage/xmlreport.py2
-rw-r--r--tests/test_cmdline.py41
-rw-r--r--tests/test_concurrency.py9
-rw-r--r--tests/test_plugins.py5
-rw-r--r--tests/test_process.py4
-rw-r--r--tests/test_report.py16
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 == []