Skip to content

Commit 93c9ca9

Browse files
committed
feat: xml and json say what they are doing, and -q quiets everything. #1254
1 parent 18cf3b8 commit 93c9ca9

File tree

11 files changed

+88
-21
lines changed

11 files changed

+88
-21
lines changed

CHANGES.rst

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,14 @@ Unreleased
3030
- Feature: The HTML report pages for Python source files now have a sticky
3131
header so the file name and controls are always visible.
3232

33-
- Added support for PyPy 3.8.
33+
- Feature: The ``xml`` and ``json`` commands now describe what they wrote
34+
where.
35+
36+
- Feature: The ``html``, ``combine``, ``xml``, and ``json`` commands all accept
37+
a ``-q/--quiet`` option to suppress the messages they write to stdout about
38+
what they are doing (`issue 1254`_).
39+
40+
- Feature: Added support for PyPy 3.8.
3441

3542
- Fix: more generated code is now excluded from measurement. Code such as
3643
`attrs`_ boilerplate, or doctest code, was being measured though the
@@ -51,6 +58,7 @@ Unreleased
5158
.. _issue 553: https://github.com/nedbat/coveragepy/issues/553
5259
.. _issue 840: https://github.com/nedbat/coveragepy/issues/840
5360
.. _issue 1160: https://github.com/nedbat/coveragepy/issues/1160
61+
.. _issue 1254: https://github.com/nedbat/coveragepy/issues/1254
5462
.. _attrs: https://www.attrs.org/
5563

5664

coverage/cmdline.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ class Opts:
146146
"reported coverage percentages."
147147
),
148148
)
149+
quiet = optparse.make_option(
150+
'-q', '--quiet', action='store_true',
151+
help="Don't print messages about what is happening.",
152+
)
149153
rcfile = optparse.make_option(
150154
'', '--rcfile', action='store',
151155
help=(
@@ -227,6 +231,7 @@ def __init__(self, *args, **kwargs):
227231
parallel_mode=None,
228232
precision=None,
229233
pylib=None,
234+
quiet=None,
230235
rcfile=True,
231236
show_contexts=None,
232237
show_missing=None,
@@ -340,6 +345,7 @@ def get_prog_name(self):
340345
[
341346
Opts.append,
342347
Opts.keep,
348+
Opts.quiet,
343349
] + GLOBAL_ARGS,
344350
usage="[options] <path1> <path2> ... <pathN>",
345351
description=(
@@ -387,6 +393,7 @@ def get_prog_name(self):
387393
Opts.include,
388394
Opts.omit,
389395
Opts.precision,
396+
Opts.quiet,
390397
Opts.show_contexts,
391398
Opts.skip_covered,
392399
Opts.no_skip_covered,
@@ -411,6 +418,7 @@ def get_prog_name(self):
411418
Opts.omit,
412419
Opts.output_json,
413420
Opts.json_pretty_print,
421+
Opts.quiet,
414422
Opts.show_contexts,
415423
] + GLOBAL_ARGS,
416424
usage="[options] [modules]",
@@ -463,6 +471,7 @@ def get_prog_name(self):
463471
Opts.include,
464472
Opts.omit,
465473
Opts.output_xml,
474+
Opts.quiet,
466475
Opts.skip_empty,
467476
] + GLOBAL_ARGS,
468477
usage="[options] [modules]",
@@ -576,7 +585,7 @@ def command_line(self, argv):
576585
concurrency=options.concurrency,
577586
check_preimported=True,
578587
context=options.context,
579-
messages=True,
588+
messages=not options.quiet,
580589
)
581590

582591
if options.action == "debug":

coverage/control.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,7 @@ def xml_report(
10061006
ignore_errors=ignore_errors, report_omit=omit, report_include=include,
10071007
xml_output=outfile, report_contexts=contexts, skip_empty=skip_empty,
10081008
):
1009-
return render_report(self.config.xml_output, XmlReporter(self), morfs)
1009+
return render_report(self.config.xml_output, XmlReporter(self), morfs, self._message)
10101010

10111011
def json_report(
10121012
self, morfs=None, outfile=None, ignore_errors=None,
@@ -1030,7 +1030,7 @@ def json_report(
10301030
json_output=outfile, report_contexts=contexts, json_pretty_print=pretty_print,
10311031
json_show_contexts=show_contexts
10321032
):
1033-
return render_report(self.config.json_output, JsonReporter(self), morfs)
1033+
return render_report(self.config.json_output, JsonReporter(self), morfs, self._message)
10341034

10351035
def sys_info(self):
10361036
"""Return a list of (key, value) pairs showing internal information."""

coverage/jsonreport.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
class JsonReporter:
1515
"""A reporter for writing JSON coverage results."""
1616

17+
report_type = "JSON report"
18+
1719
def __init__(self, coverage):
1820
self.coverage = coverage
1921
self.config = self.coverage.config

coverage/report.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@
22
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
33

44
"""Reporter foundation for coverage.py."""
5+
56
import sys
67

78
from coverage.exceptions import CoverageException, NoSource, NotPython
89
from coverage.files import prep_patterns, FnmatchMatcher
910
from coverage.misc import ensure_dir_for_file, file_be_gone
1011

1112

12-
def render_report(output_path, reporter, morfs):
13+
def render_report(output_path, reporter, morfs, msgfn):
1314
"""Run a one-file report generator, managing the output file.
1415
1516
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):
4041
file_to_close.close()
4142
if delete_file:
4243
file_be_gone(output_path) # pragma: part covered (doesn't return)
44+
else:
45+
msgfn(f"Wrote {reporter.report_type} to {output_path}")
4346

4447

4548
def get_analysis_to_report(coverage, morfs):

coverage/xmlreport.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ def rate(hit, num):
3030
class XmlReporter:
3131
"""A reporter for writing Cobertura-style XML coverage results."""
3232

33+
report_type = "XML report"
34+
3335
def __init__(self, coverage):
3436
self.coverage = coverage
3537
self.config = self.coverage.config

tests/test_cmdline.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,17 @@ def test_combine(self):
234234
cov.combine(None, strict=True, keep=False)
235235
cov.save()
236236
""")
237+
# coverage combine quietly
238+
self.cmd_executes("combine -q", """\
239+
cov = Coverage(messages=False)
240+
cov.combine(None, strict=True, keep=False)
241+
cov.save()
242+
""")
243+
self.cmd_executes("combine --quiet", """\
244+
cov = Coverage(messages=False)
245+
cov.combine(None, strict=True, keep=False)
246+
cov.save()
247+
""")
237248

238249
def test_combine_doesnt_confuse_options_with_args(self):
239250
# https://github.com/nedbat/coveragepy/issues/385
@@ -335,6 +346,16 @@ def test_html(self):
335346
cov.load()
336347
cov.html_report(title='Hello_there')
337348
""")
349+
self.cmd_executes("html -q", """\
350+
cov = Coverage(messages=False)
351+
cov.load()
352+
cov.html_report()
353+
""")
354+
self.cmd_executes("html --quiet", """\
355+
cov = Coverage(messages=False)
356+
cov.load()
357+
cov.html_report()
358+
""")
338359

339360
def test_json(self):
340361
# coverage json [-i] [--omit DIR,...] [FILE1 FILE2 ...]
@@ -388,6 +409,16 @@ def test_json(self):
388409
cov.load()
389410
cov.json_report(morfs=["mod1", "mod2", "mod3"])
390411
""")
412+
self.cmd_executes("json -q", """\
413+
cov = Coverage(messages=False)
414+
cov.load()
415+
cov.json_report()
416+
""")
417+
self.cmd_executes("json --quiet", """\
418+
cov = Coverage(messages=False)
419+
cov.load()
420+
cov.json_report()
421+
""")
391422

392423
def test_report(self):
393424
# coverage report [-m] [-i] [-o DIR,...] [FILE1 FILE2 ...]
@@ -753,6 +784,16 @@ def test_xml(self):
753784
cov.load()
754785
cov.xml_report(morfs=["mod1", "mod2", "mod3"])
755786
""")
787+
self.cmd_executes("xml -q", """\
788+
cov = Coverage(messages=False)
789+
cov.load()
790+
cov.xml_report()
791+
""")
792+
self.cmd_executes("xml --quiet", """\
793+
cov = Coverage(messages=False)
794+
cov.load()
795+
cov.xml_report()
796+
""")
756797

757798
def test_no_arguments_at_all(self):
758799
self.cmd_help("", topic="minimum_help", ret=OK)

tests/test_concurrency.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -450,13 +450,8 @@ def test_multiprocessing_with_branching(self):
450450
out = self.run_command(f"coverage run --rcfile=multi.rc multi.py {start_method}")
451451
assert out.rstrip() == expected_out
452452

453-
out = self.run_command("coverage combine")
454-
out_lines = out.splitlines()
455-
assert len(out_lines) == nprocs + 1
456-
assert all(
457-
re.fullmatch(r"Combined data file \.coverage\..*\.\d+\.\d+", line)
458-
for line in out_lines
459-
)
453+
out = self.run_command("coverage combine -q") # sneak in a test of -q
454+
assert out == ""
460455
out = self.run_command("coverage report -m")
461456

462457
last_line = self.squeezed_lines(out)[-1]

tests/test_plugins.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import inspect
77
import io
88
import os.path
9-
import re
109
from xml.etree import ElementTree
1110

1211
import pytest
@@ -256,8 +255,8 @@ def coverage_init(reg, options):
256255

257256
out = self.run_command("coverage run main_file.py")
258257
assert out == "MAIN\n"
259-
out = self.run_command("coverage html")
260-
assert re.fullmatch(r"Wrote HTML report to htmlcov[/\\]index.html\n", out)
258+
out = self.run_command("coverage html -q") # sneak in a test of -q
259+
assert out == ""
261260

262261

263262
@pytest.mark.skipif(env.C_TRACER, reason="This test is only about PyTracer.")

tests/test_process.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1325,7 +1325,7 @@ def test_accented_dot_py(self):
13251325

13261326
# The XML report is always UTF8-encoded.
13271327
out = self.run_command("coverage xml")
1328-
assert out == ""
1328+
assert out == "Wrote XML report to coverage.xml\n"
13291329
with open("coverage.xml", "rb") as xmlf:
13301330
xml = xmlf.read()
13311331
assert ' filename="h\xe2t.py"'.encode() in xml
@@ -1358,7 +1358,7 @@ def test_accented_directory(self):
13581358
assert expected % os.sep in index
13591359

13601360
# The XML report is always UTF8-encoded.
1361-
out = self.run_command("coverage xml")
1361+
out = self.run_command("coverage xml -q")
13621362
assert out == ""
13631363
with open("coverage.xml", "rb") as xmlf:
13641364
xml = xmlf.read()

tests/test_report.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
class FakeReporter:
1414
"""A fake implementation of a one-file reporter."""
1515

16+
report_type = "fake report file"
17+
1618
def __init__(self, output="", error=False):
1719
self.output = output
1820
self.error = error
@@ -31,20 +33,26 @@ class RenderReportTest(CoverageTest):
3133

3234
def test_stdout(self):
3335
fake = FakeReporter(output="Hello!\n")
34-
render_report("-", fake, [pytest, "coverage"])
36+
msgs = []
37+
render_report("-", fake, [pytest, "coverage"], msgs.append)
3538
assert fake.morfs == [pytest, "coverage"]
3639
assert self.stdout() == "Hello!\n"
40+
assert msgs == []
3741

3842
def test_file(self):
3943
fake = FakeReporter(output="Gréètings!\n")
40-
render_report("output.txt", fake, [])
44+
msgs = []
45+
render_report("output.txt", fake, [], msgs.append)
4146
assert self.stdout() == ""
4247
with open("output.txt", "rb") as f:
43-
assert f.read() == b"Gr\xc3\xa9\xc3\xa8tings!\n"
48+
assert f.read().rstrip() == b"Gr\xc3\xa9\xc3\xa8tings!"
49+
assert msgs == ["Wrote fake report file to output.txt"]
4450

4551
def test_exception(self):
4652
fake = FakeReporter(error=True)
53+
msgs = []
4754
with pytest.raises(CoverageException, match="You asked for it!"):
48-
render_report("output.txt", fake, [])
55+
render_report("output.txt", fake, [], msgs.append)
4956
assert self.stdout() == ""
5057
self.assert_doesnt_exist("output.txt")
58+
assert msgs == []

0 commit comments

Comments
 (0)