Skip to content

Commit

Permalink
feat: xml and json say what they are doing, and -q quiets everything. #…
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed Oct 26, 2021
1 parent 18cf3b8 commit 93c9ca9
Show file tree
Hide file tree
Showing 11 changed files with 88 additions and 21 deletions.
10 changes: 9 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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/


Expand Down
11 changes: 10 additions & 1 deletion coverage/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -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=(
Expand Down Expand Up @@ -227,6 +231,7 @@ def __init__(self, *args, **kwargs):
parallel_mode=None,
precision=None,
pylib=None,
quiet=None,
rcfile=True,
show_contexts=None,
show_missing=None,
Expand Down Expand Up @@ -340,6 +345,7 @@ def get_prog_name(self):
[
Opts.append,
Opts.keep,
Opts.quiet,
] + GLOBAL_ARGS,
usage="[options] <path1> <path2> ... <pathN>",
description=(
Expand Down Expand Up @@ -387,6 +393,7 @@ def get_prog_name(self):
Opts.include,
Opts.omit,
Opts.precision,
Opts.quiet,
Opts.show_contexts,
Opts.skip_covered,
Opts.no_skip_covered,
Expand All @@ -411,6 +418,7 @@ def get_prog_name(self):
Opts.omit,
Opts.output_json,
Opts.json_pretty_print,
Opts.quiet,
Opts.show_contexts,
] + GLOBAL_ARGS,
usage="[options] [modules]",
Expand Down Expand Up @@ -463,6 +471,7 @@ def get_prog_name(self):
Opts.include,
Opts.omit,
Opts.output_xml,
Opts.quiet,
Opts.skip_empty,
] + GLOBAL_ARGS,
usage="[options] [modules]",
Expand Down Expand Up @@ -576,7 +585,7 @@ def command_line(self, argv):
concurrency=options.concurrency,
check_preimported=True,
context=options.context,
messages=True,
messages=not options.quiet,
)

if options.action == "debug":
Expand Down
4 changes: 2 additions & 2 deletions coverage/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -1006,7 +1006,7 @@ def xml_report(
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,
Expand All @@ -1030,7 +1030,7 @@ def json_report(
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."""
Expand Down
2 changes: 2 additions & 0 deletions coverage/jsonreport.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
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
Expand Down
5 changes: 4 additions & 1 deletion coverage/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
# 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
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
Expand Down Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions coverage/xmlreport.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions tests/test_cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,17 @@ def test_combine(self):
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
Expand Down Expand Up @@ -335,6 +346,16 @@ def test_html(self):
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 ...]
Expand Down Expand Up @@ -388,6 +409,16 @@ def test_json(self):
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 ...]
Expand Down Expand Up @@ -753,6 +784,16 @@ def test_xml(self):
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)
Expand Down
9 changes: 2 additions & 7 deletions tests/test_concurrency.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,13 +450,8 @@ def test_multiprocessing_with_branching(self):
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]
Expand Down
5 changes: 2 additions & 3 deletions tests/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import inspect
import io
import os.path
import re
from xml.etree import ElementTree

import pytest
Expand Down Expand Up @@ -256,8 +255,8 @@ def coverage_init(reg, options):

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.")
Expand Down
4 changes: 2 additions & 2 deletions tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -1325,7 +1325,7 @@ def test_accented_dot_py(self):

# 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
Expand Down Expand Up @@ -1358,7 +1358,7 @@ def test_accented_directory(self):
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()
Expand Down
16 changes: 12 additions & 4 deletions tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
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
Expand All @@ -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 == []

0 comments on commit 93c9ca9

Please sign in to comment.