blob: fca027f9b11d9eb95a1e9efff36bb64cf477935c [file] [log] [blame]
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
"""Test text-based summary reporting for coverage.py"""
from __future__ import annotations
import glob
import io
import math
import os
import os.path
import py_compile
import re
import pytest
import coverage
from coverage import env
from coverage.control import Coverage
from coverage.data import CoverageData
from coverage.exceptions import ConfigError, NoDataError, NotPython
from coverage.files import abs_file
from coverage.report import SummaryReporter
from coverage.types import TConfigValueIn
from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin
from tests.helpers import assert_coverage_warnings
class SummaryTest(UsingModulesMixin, CoverageTest):
"""Tests of the text summary reporting for coverage.py."""
def make_mycode(self) -> None:
"""Make the mycode.py file when needed."""
self.make_file("mycode.py", """\
import covmod1
import covmodzip1
a = 1
print('done')
""")
def test_report(self) -> None:
self.make_mycode()
cov = coverage.Coverage()
self.start_import_stop(cov, "mycode")
assert self.stdout() == 'done\n'
report = self.get_report(cov)
# Name Stmts Miss Cover
# ------------------------------------------------------------------
# c:/ned/coverage/tests/modules/covmod1.py 2 0 100%
# c:/ned/coverage/tests/zipmods.zip/covmodzip1.py 2 0 100%
# mycode.py 4 0 100%
# ------------------------------------------------------------------
# TOTAL 8 0 100%
assert "/coverage/__init__/" not in report
assert "/tests/modules/covmod1.py " in report
assert "/tests/zipmods.zip/covmodzip1.py " in report
assert "mycode.py " in report
assert self.last_line_squeezed(report) == "TOTAL 8 0 100%"
def test_report_just_one(self) -> None:
# Try reporting just one module
self.make_mycode()
cov = coverage.Coverage()
self.start_import_stop(cov, "mycode")
report = self.get_report(cov, morfs=["mycode.py"])
# Name Stmts Miss Cover
# -------------------------------
# mycode.py 4 0 100%
# -------------------------------
# TOTAL 4 0 100%
assert self.line_count(report) == 5
assert "/coverage/" not in report
assert "/tests/modules/covmod1.py " not in report
assert "/tests/zipmods.zip/covmodzip1.py " not in report
assert "mycode.py " in report
assert self.last_line_squeezed(report) == "TOTAL 4 0 100%"
def test_report_wildcard(self) -> None:
# Try reporting using wildcards to get the modules.
self.make_mycode()
self.add_test_modules_to_pythonpath()
# Wildcard is handled by shell or cmdline.py, so use real commands
self.run_command("coverage run mycode.py")
report = self.report_from_command("coverage report my*.py")
# Name Stmts Miss Cover
# -------------------------------
# mycode.py 4 0 100%
# -------------------------------
# TOTAL 4 0 100%
assert self.line_count(report) == 5
assert "/coverage/" not in report
assert "/tests/modules/covmod1.py " not in report
assert "/tests/zipmods.zip/covmodzip1.py " not in report
assert "mycode.py " in report
assert self.last_line_squeezed(report) == "TOTAL 4 0 100%"
def test_report_omitting(self) -> None:
# Try reporting while omitting some modules
self.make_mycode()
cov = coverage.Coverage()
self.start_import_stop(cov, "mycode")
report = self.get_report(cov, omit=[f"{TESTS_DIR}/*", "*/site-packages/*"])
# Name Stmts Miss Cover
# -------------------------------
# mycode.py 4 0 100%
# -------------------------------
# TOTAL 4 0 100%
assert self.line_count(report) == 5
assert "/coverage/" not in report
assert "/tests/modules/covmod1.py " not in report
assert "/tests/zipmods.zip/covmodzip1.py " not in report
assert "mycode.py " in report
assert self.last_line_squeezed(report) == "TOTAL 4 0 100%"
def test_report_including(self) -> None:
# Try reporting while including some modules
self.make_mycode()
cov = coverage.Coverage()
self.start_import_stop(cov, "mycode")
report = self.get_report(cov, include=["mycode*"])
# Name Stmts Miss Cover
# -------------------------------
# mycode.py 4 0 100%
# -------------------------------
# TOTAL 4 0 100%
assert self.line_count(report) == 5
assert "/coverage/" not in report
assert "/tests/modules/covmod1.py " not in report
assert "/tests/zipmods.zip/covmodzip1.py " not in report
assert "mycode.py " in report
assert self.last_line_squeezed(report) == "TOTAL 4 0 100%"
def test_report_include_relative_files_and_path(self) -> None:
"""
Test that when relative_files is True and a relative path to a module
is included, coverage is reported for the module.
Ref: https://github.com/nedbat/coveragepy/issues/1604
"""
self.make_mycode()
self.make_file(".coveragerc", """\
[run]
relative_files = true
""")
self.make_file("submodule/mycode.py", "import mycode")
cov = coverage.Coverage()
self.start_import_stop(cov, "submodule/mycode")
report = self.get_report(cov, include="submodule/mycode.py")
# Name Stmts Miss Cover
# ---------------------------------------
# submodule/mycode.py 1 0 100%
# ---------------------------------------
# TOTAL 1 0 100%
assert "submodule/mycode.py " in report
assert self.last_line_squeezed(report) == "TOTAL 1 0 100%"
def test_report_include_relative_files_and_wildcard_path(self) -> None:
self.make_mycode()
self.make_file(".coveragerc", """\
[run]
relative_files = true
""")
self.make_file("submodule/mycode.py", "import nested.submodule.mycode")
self.make_file("nested/submodule/mycode.py", "import mycode")
cov = coverage.Coverage()
self.start_import_stop(cov, "submodule/mycode")
report = self.get_report(cov, include="*/submodule/mycode.py")
# Name Stmts Miss Cover
# -------------------------------------------------
# nested/submodule/mycode.py 1 0 100%
# submodule/mycode.py 1 0 100%
# -------------------------------------------------
# TOTAL 2 0 100%
reported_files = [line.split()[0] for line in report.splitlines()[2:4]]
assert reported_files == [
"nested/submodule/mycode.py",
"submodule/mycode.py",
]
def test_omit_files_here(self) -> None:
# https://github.com/nedbat/coveragepy/issues/1407
self.make_file("foo.py", "")
self.make_file("bar/bar.py", "")
self.make_file("tests/test_baz.py", """\
def test_foo():
assert True
test_foo()
""")
self.run_command("coverage run --source=. --omit='./*.py' -m tests.test_baz")
report = self.report_from_command("coverage report")
# Name Stmts Miss Cover
# ---------------------------------------
# tests/test_baz.py 3 0 100%
# ---------------------------------------
# TOTAL 3 0 100%
assert self.line_count(report) == 5
assert "foo" not in report
assert "bar" not in report
assert "tests/test_baz.py" in report
assert self.last_line_squeezed(report) == "TOTAL 3 0 100%"
def test_run_source_vs_report_include(self) -> None:
# https://github.com/nedbat/coveragepy/issues/621
self.make_file(".coveragerc", """\
[run]
source = .
[report]
include = mod/*,tests/*
""")
# It should be OK to use that configuration.
cov = coverage.Coverage()
with self.assert_warnings(cov, []):
with cov.collect():
pass
def test_run_omit_vs_report_omit(self) -> None:
# https://github.com/nedbat/coveragepy/issues/622
# report:omit shouldn't clobber run:omit.
self.make_mycode()
self.make_file(".coveragerc", """\
[run]
omit = */covmodzip1.py
[report]
omit = */covmod1.py
""")
self.add_test_modules_to_pythonpath()
self.run_command("coverage run mycode.py")
# Read the data written, to see that the right files have been omitted from running.
covdata = CoverageData()
covdata.read()
files = [os.path.basename(p) for p in covdata.measured_files()]
assert "covmod1.py" in files
assert "covmodzip1.py" not in files
def test_report_branches(self) -> None:
self.make_file("mybranch.py", """\
def branch(x):
if x:
print("x")
return x
branch(1)
""")
cov = coverage.Coverage(source=["."], branch=True)
self.start_import_stop(cov, "mybranch")
assert self.stdout() == 'x\n'
report = self.get_report(cov)
# Name Stmts Miss Branch BrPart Cover
# -----------------------------------------------
# mybranch.py 5 0 2 1 86%
# -----------------------------------------------
# TOTAL 5 0 2 1 86%
assert self.line_count(report) == 5
assert "mybranch.py " in report
assert self.last_line_squeezed(report) == "TOTAL 5 0 2 1 86%"
def test_report_show_missing(self) -> None:
self.make_file("mymissing.py", """\
def missing(x, y):
if x:
print("x")
return x
if y:
print("y")
try:
print("z")
1/0
print("Never!")
except ZeroDivisionError:
pass
return x
missing(0, 1)
""")
cov = coverage.Coverage(source=["."])
self.start_import_stop(cov, "mymissing")
assert self.stdout() == 'y\nz\n'
report = self.get_report(cov, show_missing=True)
# Name Stmts Miss Cover Missing
# --------------------------------------------
# mymissing.py 14 3 79% 3-4, 10
# --------------------------------------------
# TOTAL 14 3 79%
assert self.line_count(report) == 5
squeezed = self.squeezed_lines(report)
assert squeezed[2] == "mymissing.py 14 3 79% 3-4, 10"
assert squeezed[4] == "TOTAL 14 3 79%"
def test_report_show_missing_branches(self) -> None:
self.make_file("mybranch.py", """\
def branch(x, y):
if x:
print("x")
if y:
print("y")
branch(1, 1)
""")
cov = coverage.Coverage(branch=True)
self.start_import_stop(cov, "mybranch")
assert self.stdout() == 'x\ny\n'
report = self.get_report(cov, show_missing=True)
# Name Stmts Miss Branch BrPart Cover Missing
# ----------------------------------------------------------
# mybranch.py 6 0 4 2 80% 2->4, 4->exit
# ----------------------------------------------------------
# TOTAL 6 0 4 2 80%
assert self.line_count(report) == 5
squeezed = self.squeezed_lines(report)
assert squeezed[2] == "mybranch.py 6 0 4 2 80% 2->4, 4->exit"
assert squeezed[4] == "TOTAL 6 0 4 2 80%"
def test_report_show_missing_branches_and_lines(self) -> None:
self.make_file("main.py", """\
import mybranch
""")
self.make_file("mybranch.py", """\
def branch(x, y, z):
if x:
print("x")
if y:
print("y")
if z:
if x and y:
print("z")
return x
branch(1, 1, 0)
""")
cov = coverage.Coverage(branch=True)
self.start_import_stop(cov, "main")
assert self.stdout() == 'x\ny\n'
report_lines = self.get_report(cov, squeeze=False, show_missing=True).splitlines()
expected = [
'Name Stmts Miss Branch BrPart Cover Missing',
'---------------------------------------------------------',
'main.py 1 0 0 0 100%',
'mybranch.py 10 2 8 3 61% 2->4, 4->6, 7-8',
'---------------------------------------------------------',
'TOTAL 11 2 8 3 63%',
]
assert expected == report_lines
def test_report_skip_covered_no_branches(self) -> None:
self.make_file("main.py", """\
import not_covered
def normal():
print("z")
normal()
""")
self.make_file("not_covered.py", """\
def not_covered():
print("n")
""")
# --fail-under is handled by cmdline.py, use real commands.
out = self.run_command("coverage run main.py")
assert out == "z\n"
report = self.report_from_command("coverage report --skip-covered --fail-under=70")
# Name Stmts Miss Cover
# ------------------------------------
# not_covered.py 2 1 50%
# ------------------------------------
# TOTAL 6 1 83%
#
# 1 file skipped due to complete coverage.
assert self.line_count(report) == 7, report
squeezed = self.squeezed_lines(report)
assert squeezed[2] == "not_covered.py 2 1 50%"
assert squeezed[4] == "TOTAL 6 1 83%"
assert squeezed[6] == "1 file skipped due to complete coverage."
assert self.last_command_status == 0
def test_report_skip_covered_branches(self) -> None:
self.make_file("main.py", """\
import not_covered, covered
def normal(z):
if z:
print("z")
normal(True)
normal(False)
""")
self.make_file("not_covered.py", """\
def not_covered(n):
if n:
print("n")
not_covered(True)
""")
self.make_file("covered.py", """\
def foo():
pass
foo()
""")
cov = coverage.Coverage(branch=True)
self.start_import_stop(cov, "main")
assert self.stdout() == "n\nz\n"
report = self.get_report(cov, skip_covered=True)
# Name Stmts Miss Branch BrPart Cover
# --------------------------------------------------
# not_covered.py 4 0 2 1 83%
# --------------------------------------------------
# TOTAL 13 0 4 1 94%
#
# 2 files skipped due to complete coverage.
assert self.line_count(report) == 7, report
squeezed = self.squeezed_lines(report)
assert squeezed[2] == "not_covered.py 4 0 2 1 83%"
assert squeezed[4] == "TOTAL 13 0 4 1 94%"
assert squeezed[6] == "2 files skipped due to complete coverage."
def test_report_skip_covered_branches_with_totals(self) -> None:
self.make_file("main.py", """\
import not_covered
import also_not_run
def normal(z):
if z:
print("z")
normal(True)
normal(False)
""")
self.make_file("not_covered.py", """\
def not_covered(n):
if n:
print("n")
not_covered(True)
""")
self.make_file("also_not_run.py", """\
def does_not_appear_in_this_film(ni):
print("Ni!")
""")
cov = coverage.Coverage(branch=True)
self.start_import_stop(cov, "main")
assert self.stdout() == "n\nz\n"
report = self.get_report(cov, skip_covered=True)
# Name Stmts Miss Branch BrPart Cover
# --------------------------------------------------
# also_not_run.py 2 1 0 0 50%
# not_covered.py 4 0 2 1 83%
# --------------------------------------------------
# TOTAL 13 1 4 1 88%
#
# 1 file skipped due to complete coverage.
assert self.line_count(report) == 8, report
squeezed = self.squeezed_lines(report)
assert squeezed[2] == "also_not_run.py 2 1 0 0 50%"
assert squeezed[3] == "not_covered.py 4 0 2 1 83%"
assert squeezed[5] == "TOTAL 13 1 4 1 88%"
assert squeezed[7] == "1 file skipped due to complete coverage."
def test_report_skip_covered_all_files_covered(self) -> None:
self.make_file("main.py", """\
def foo():
pass
foo()
""")
cov = coverage.Coverage(source=["."], branch=True)
self.start_import_stop(cov, "main")
assert self.stdout() == ""
report = self.get_report(cov, skip_covered=True)
# Name Stmts Miss Branch BrPart Cover
# -----------------------------------------
# TOTAL 3 0 0 0 100%
#
# 1 file skipped due to complete coverage.
assert self.line_count(report) == 5, report
squeezed = self.squeezed_lines(report)
assert squeezed[4] == "1 file skipped due to complete coverage."
report = self.get_report(cov, squeeze=False, skip_covered=True, output_format="markdown")
# | Name | Stmts | Miss | Branch | BrPart | Cover |
# |---------- | -------: | -------: | -------: | -------: | -------: |
# | **TOTAL** | **3** | **0** | **0** | **0** | **100%** |
#
# 1 file skipped due to complete coverage.
assert self.line_count(report) == 5, report
assert report.split("\n")[0] == (
'| Name | Stmts | Miss | Branch | BrPart | Cover |'
)
assert report.split("\n")[1] == (
'|---------- | -------: | -------: | -------: | -------: | -------: |'
)
assert report.split("\n")[2] == (
'| **TOTAL** | **3** | **0** | **0** | **0** | **100%** |'
)
squeezed = self.squeezed_lines(report)
assert squeezed[4] == "1 file skipped due to complete coverage."
total = self.get_report(cov, output_format="total", skip_covered=True)
assert total == "100\n"
def test_report_skip_covered_longfilename(self) -> None:
self.make_file("long_______________filename.py", """\
def foo():
pass
foo()
""")
cov = coverage.Coverage(source=["."], branch=True)
self.start_import_stop(cov, "long_______________filename")
assert self.stdout() == ""
report = self.get_report(cov, squeeze=False, skip_covered=True)
# Name Stmts Miss Branch BrPart Cover
# -----------------------------------------
# TOTAL 3 0 0 0 100%
#
# 1 file skipped due to complete coverage.
assert self.line_count(report) == 5, report
lines = self.report_lines(report)
assert lines[0] == "Name Stmts Miss Branch BrPart Cover"
squeezed = self.squeezed_lines(report)
assert squeezed[4] == "1 file skipped due to complete coverage."
def test_report_skip_covered_no_data(self) -> None:
cov = coverage.Coverage()
cov.load()
with pytest.raises(NoDataError, match="No data to report."):
self.get_report(cov, skip_covered=True)
self.assert_doesnt_exist(".coverage")
def test_report_skip_empty(self) -> None:
self.make_file("main.py", """\
import submodule
def normal():
print("z")
normal()
""")
self.make_file("submodule/__init__.py", "")
cov = coverage.Coverage()
self.start_import_stop(cov, "main")
assert self.stdout() == "z\n"
report = self.get_report(cov, skip_empty=True)
# Name Stmts Miss Cover
# ------------------------------------
# main.py 4 0 100%
# ------------------------------------
# TOTAL 4 0 100%
#
# 1 empty file skipped.
assert self.line_count(report) == 7, report
squeezed = self.squeezed_lines(report)
assert squeezed[2] == "main.py 4 0 100%"
assert squeezed[4] == "TOTAL 4 0 100%"
assert squeezed[6] == "1 empty file skipped."
def test_report_skip_empty_no_data(self) -> None:
self.make_file("__init__.py", "")
cov = coverage.Coverage()
self.start_import_stop(cov, "__init__")
assert self.stdout() == ""
report = self.get_report(cov, skip_empty=True)
# Name Stmts Miss Cover
# ------------------------------------
# TOTAL 0 0 100%
#
# 1 empty file skipped.
assert self.line_count(report) == 5, report
assert report.split("\n")[2] == "TOTAL 0 0 100%"
assert report.split("\n")[4] == "1 empty file skipped."
def test_report_precision(self) -> None:
self.make_file(".coveragerc", """\
[report]
precision = 3
omit = */site-packages/*
""")
self.make_file("main.py", """\
import not_covered, covered
def normal(z):
if z:
print("z")
normal(True)
normal(False)
""")
self.make_file("not_covered.py", """\
def not_covered(n):
if n:
print("n")
not_covered(True)
""")
self.make_file("covered.py", """\
def foo():
pass
foo()
""")
cov = coverage.Coverage(branch=True)
self.start_import_stop(cov, "main")
assert self.stdout() == "n\nz\n"
report = self.get_report(cov, squeeze=False)
# Name Stmts Miss Branch BrPart Cover
# ------------------------------------------------------
# covered.py 3 0 0 0 100.000%
# main.py 6 0 2 0 100.000%
# not_covered.py 4 0 2 1 83.333%
# ------------------------------------------------------
# TOTAL 13 0 4 1 94.118%
assert self.line_count(report) == 7, report
squeezed = self.squeezed_lines(report)
assert squeezed[2] == "covered.py 3 0 0 0 100.000%"
assert squeezed[4] == "not_covered.py 4 0 2 1 83.333%"
assert squeezed[6] == "TOTAL 13 0 4 1 94.118%"
def test_report_precision_all_zero(self) -> None:
self.make_file("not_covered.py", """\
def not_covered(n):
if n:
print("n")
""")
self.make_file("empty.py", "")
cov = coverage.Coverage(source=["."])
self.start_import_stop(cov, "empty")
report = self.get_report(cov, precision=6, squeeze=False)
# Name Stmts Miss Cover
# -----------------------------------------
# empty.py 0 0 100.000000%
# not_covered.py 3 3 0.000000%
# -----------------------------------------
# TOTAL 3 3 0.000000%
assert self.line_count(report) == 6, report
assert "empty.py 0 0 100.000000%" in report
assert "not_covered.py 3 3 0.000000%" in report
assert "TOTAL 3 3 0.000000%" in report
def test_report_module_docstrings(self) -> None:
self.make_file("main.py", """\
# Line 1
'''Line 2 docstring.'''
import other
a = 4
""")
self.make_file("other.py", """\
'''Line 1'''
a = 2
""")
cov = coverage.Coverage()
self.start_import_stop(cov, "main")
report = self.get_report(cov)
# Name Stmts Miss Cover
# ------------------------------
# main.py 2 0 100%
# other.py 1 0 100%
# ------------------------------
# TOTAL 3 0 100%
assert self.line_count(report) == 6, report
squeezed = self.squeezed_lines(report)
assert squeezed[2] == "main.py 2 0 100%"
assert squeezed[3] == "other.py 1 0 100%"
assert squeezed[5] == "TOTAL 3 0 100%"
def test_dotpy_not_python(self) -> None:
# We run a .py file, and when reporting, we can't parse it as Python.
# We should get an error message in the report.
self.make_data_file(lines={"mycode.py": [1]})
self.make_file("mycode.py", "This isn't python at all!")
cov = coverage.Coverage()
cov.load()
msg = r"Couldn't parse '.*[/\\]mycode.py' as Python source: '.*' at line 1"
with pytest.raises(NotPython, match=msg):
self.get_report(cov, morfs=["mycode.py"])
def test_accented_directory(self) -> None:
# Make a file with a non-ascii character in the directory name.
self.make_file("\xe2/accented.py", "print('accented')")
self.make_data_file(lines={abs_file("\xe2/accented.py"): [1]})
report_expected = (
"Name Stmts Miss Cover\n" +
"-----------------------------------\n" +
"\xe2/accented.py 1 0 100%\n" +
"-----------------------------------\n" +
"TOTAL 1 0 100%\n"
)
cov = coverage.Coverage()
cov.load()
output = self.get_report(cov, squeeze=False)
assert output == report_expected
def test_accenteddotpy_not_python(self) -> None:
# We run a .py file with a non-ascii name, and when reporting, we can't
# parse it as Python. We should get an error message in the report.
self.make_data_file(lines={"accented\xe2.py": [1]})
self.make_file("accented\xe2.py", "This isn't python at all!")
cov = coverage.Coverage()
cov.load()
msg = r"Couldn't parse '.*[/\\]accented\xe2.py' as Python source: '.*' at line 1"
with pytest.raises(NotPython, match=msg):
self.get_report(cov, morfs=["accented\xe2.py"])
def test_dotpy_not_python_ignored(self) -> None:
# We run a .py file, and when reporting, we can't parse it as Python,
# but we've said to ignore errors, so there's no error reported,
# though we still get a warning.
self.make_file("mycode.py", "This isn't python at all!")
self.make_data_file(lines={"mycode.py": [1]})
cov = coverage.Coverage()
cov.load()
with pytest.raises(NoDataError, match="No data to report."):
with pytest.warns(Warning) as warns:
self.get_report(cov, morfs=["mycode.py"], ignore_errors=True)
assert_coverage_warnings(
warns,
re.compile(r"Couldn't parse Python file '.*[/\\]mycode.py' \(couldnt-parse\)"),
)
def test_dothtml_not_python(self) -> None:
# We run a .html file, and when reporting, we can't parse it as
# Python. Since it wasn't .py, no error is reported.
# Pretend to run an html file.
self.make_file("mycode.html", "<h1>This isn't python at all!</h1>")
self.make_data_file(lines={"mycode.html": [1]})
cov = coverage.Coverage()
cov.load()
with pytest.raises(NoDataError, match="No data to report."):
self.get_report(cov, morfs=["mycode.html"])
def test_report_no_extension(self) -> None:
self.make_file("xxx", """\
# This is a python file though it doesn't look like it, like a main script.
a = b = c = d = 0
a = 3
b = 4
if not b:
c = 6
d = 7
print(f"xxx: {a} {b} {c} {d}")
""")
self.make_data_file(lines={abs_file("xxx"): [2, 3, 4, 5, 7, 8]})
cov = coverage.Coverage()
cov.load()
report = self.get_report(cov)
assert self.last_line_squeezed(report) == "TOTAL 7 1 86%"
def test_report_with_chdir(self) -> None:
self.make_file("chdir.py", """\
import os
print("Line One")
os.chdir("subdir")
print("Line Two")
print(open("something").read())
""")
self.make_file("subdir/something", "hello")
out = self.run_command("coverage run --source=. chdir.py")
assert out == "Line One\nLine Two\nhello\n"
report = self.report_from_command("coverage report")
assert self.last_line_squeezed(report) == "TOTAL 5 0 100%"
report = self.report_from_command("coverage report --format=markdown")
assert self.last_line_squeezed(report) == "| **TOTAL** | **5** | **0** | **100%** |"
def test_bug_156_file_not_run_should_be_zero(self) -> None:
# https://github.com/nedbat/coveragepy/issues/156
self.make_file("mybranch.py", """\
def branch(x):
if x:
print("x")
return x
branch(1)
""")
self.make_file("main.py", """\
print("y")
""")
cov = coverage.Coverage(branch=True, source=["."])
self.start_import_stop(cov, "main")
report = self.get_report(cov).splitlines()
assert "mybranch.py 5 5 2 0 0%" in report
def run_TheCode_and_report_it(self) -> str:
"""A helper for the next few tests."""
cov = coverage.Coverage()
self.start_import_stop(cov, "TheCode")
return self.get_report(cov)
def test_bug_203_mixed_case_listed_twice_with_rc(self) -> None:
self.make_file("TheCode.py", "a = 1\n")
self.make_file(".coveragerc", "[run]\nsource = .\n")
report = self.run_TheCode_and_report_it()
assert "TheCode" in report
assert "thecode" not in report
def test_bug_203_mixed_case_listed_twice(self) -> None:
self.make_file("TheCode.py", "a = 1\n")
report = self.run_TheCode_and_report_it()
assert "TheCode" in report
assert "thecode" not in report
@pytest.mark.skipif(not env.WINDOWS, reason=".pyw files are only on Windows.")
def test_pyw_files(self) -> None:
# https://github.com/nedbat/coveragepy/issues/261
self.make_file("start.pyw", """\
import mod
print("In start.pyw")
""")
self.make_file("mod.pyw", """\
print("In mod.pyw")
""")
cov = coverage.Coverage()
# start_import_stop can't import the .pyw file, so use the long form.
with cov.collect():
import start # pylint: disable=import-error, unused-import
report = self.get_report(cov)
assert "NoSource" not in report
report_lines = report.splitlines()
assert "start.pyw 2 0 100%" in report_lines
assert "mod.pyw 1 0 100%" in report_lines
def test_tracing_pyc_file(self) -> None:
# Create two Python files.
self.make_file("mod.py", "a = 1\n")
self.make_file("main.py", "import mod\n")
# Make one into a .pyc.
py_compile.compile("mod.py")
# Run the program.
cov = coverage.Coverage()
self.start_import_stop(cov, "main")
report_lines = self.get_report(cov).splitlines()
assert "mod.py 1 0 100%" in report_lines
report = self.get_report(cov, squeeze=False, output_format="markdown")
assert report.split("\n")[3] == "| mod.py | 1 | 0 | 100% |"
assert report.split("\n")[4] == "| **TOTAL** | **2** | **0** | **100%** |"
def test_missing_py_file_during_run(self) -> None:
# Create two Python files.
self.make_file("mod.py", "a = 1\n")
self.make_file("main.py", "import mod\n")
# Make one into a .pyc, and remove the .py.
py_compile.compile("mod.py")
os.remove("mod.py")
# Python 3 puts the .pyc files in a __pycache__ directory, and will
# not import from there without source. It will import a .pyc from
# the source location though.
pycs = glob.glob("__pycache__/mod.*.pyc")
assert len(pycs) == 1
os.rename(pycs[0], "mod.pyc")
# Run the program.
cov = coverage.Coverage()
self.start_import_stop(cov, "main")
# Put back the missing Python file.
self.make_file("mod.py", "a = 1\n")
report = self.get_report(cov).splitlines()
assert "mod.py 1 0 100%" in report
def test_empty_files(self) -> None:
# Shows that empty files like __init__.py are listed as having zero
# statements, not one statement.
cov = coverage.Coverage(branch=True)
with cov.collect():
import usepkgs # pylint: disable=import-error, unused-import
report = self.get_report(cov)
assert "tests/modules/pkg1/__init__.py 1 0 0 0 100%" in report
assert "tests/modules/pkg2/__init__.py 0 0 0 0 100%" in report
report = self.get_report(cov, squeeze=False, output_format="markdown")
# get_report() escapes backslash so we expect forward slash escaped
# underscore
assert "tests/modules/pkg1//_/_init/_/_.py " in report
assert "| 1 | 0 | 0 | 0 | 100% |" in report
assert "tests/modules/pkg2//_/_init/_/_.py " in report
assert "| 0 | 0 | 0 | 0 | 100% |" in report
def test_markdown_with_missing(self) -> None:
self.make_file("mymissing.py", """\
def missing(x, y):
if x:
print("x")
return x
if y:
print("y")
try:
print("z")
1/0
print("Never!")
except ZeroDivisionError:
pass
return x
missing(0, 1)
""")
cov = coverage.Coverage(source=["."])
self.start_import_stop(cov, "mymissing")
assert self.stdout() == 'y\nz\n'
report = self.get_report(cov, squeeze=False, output_format="markdown", show_missing=True)
# | Name | Stmts | Miss | Cover | Missing |
# |------------- | -------: | -------: | ------: | --------: |
# | mymissing.py | 14 | 3 | 79% | 3-4, 10 |
# | **TOTAL** | **14** | **3** | **79%** | |
assert self.line_count(report) == 4
report_lines = report.split("\n")
assert report_lines[2] == "| mymissing.py | 14 | 3 | 79% | 3-4, 10 |"
assert report_lines[3] == "| **TOTAL** | **14** | **3** | **79%** | |"
assert self.get_report(cov, output_format="total") == "79\n"
assert self.get_report(cov, output_format="total", precision=2) == "78.57\n"
assert self.get_report(cov, output_format="total", precision=4) == "78.5714\n"
def test_bug_1524(self) -> None:
self.make_file("bug1524.py", """\
class Mine:
@property
def thing(self) -> int:
return 17
print(Mine().thing)
""")
cov = coverage.Coverage()
self.start_import_stop(cov, "bug1524")
assert self.stdout() == "17\n"
report = self.get_report(cov)
report_lines = report.splitlines()
assert report_lines[2] == "bug1524.py 5 0 100%"
class ReportingReturnValueTest(CoverageTest):
"""Tests of reporting functions returning values."""
def run_coverage(self) -> Coverage:
"""Run coverage on doit.py and return the coverage object."""
self.make_file("doit.py", """\
a = 1
b = 2
c = 3
d = 4
if a > 10:
f = 6
g = 7
""")
cov = coverage.Coverage()
self.start_import_stop(cov, "doit")
return cov
def test_report(self) -> None:
cov = self.run_coverage()
val = cov.report(include="*/doit.py")
assert math.isclose(val, 6 / 7 * 100)
def test_html(self) -> None:
cov = self.run_coverage()
val = cov.html_report(include="*/doit.py")
assert math.isclose(val, 6 / 7 * 100)
def test_xml(self) -> None:
cov = self.run_coverage()
val = cov.xml_report(include="*/doit.py")
assert math.isclose(val, 6 / 7 * 100)
class SummaryReporterConfigurationTest(CoverageTest):
"""Tests of SummaryReporter."""
def make_rigged_file(self, filename: str, stmts: int, miss: int) -> None:
"""Create a file that will have specific results.
`stmts` and `miss` are ints, the number of statements, and
missed statements that should result.
"""
run = stmts - miss - 1
dont_run = miss
source = ""
source += "a = 1\n" * run
source += "if a == 99:\n"
source += " a = 2\n" * dont_run
self.make_file(filename, source)
def get_summary_text(self, *options: tuple[str, TConfigValueIn]) -> str:
"""Get text output from the SummaryReporter.
The arguments are tuples: (name, value) for Coverage.set_option.
"""
self.make_rigged_file("file1.py", 339, 155)
self.make_rigged_file("file2.py", 13, 3)
self.make_rigged_file("file10.py", 234, 228)
self.make_file("doit.py", "import file1, file2, file10")
cov = Coverage(source=["."], omit=["doit.py"])
self.start_import_stop(cov, "doit")
for name, value in options:
cov.set_option(name, value)
printer = SummaryReporter(cov)
destination = io.StringIO()
printer.report([], destination)
return destination.getvalue()
def test_test_data(self) -> None:
# We use our own test files as test data. Check that our assumptions
# about them are still valid. We want the three columns of numbers to
# sort in three different orders.
report = self.get_summary_text()
# Name Stmts Miss Cover
# ------------------------------
# file1.py 339 155 54%
# file2.py 13 3 77%
# file10.py 234 228 3%
# ------------------------------
# TOTAL 586 386 34%
lines = report.splitlines()[2:-2]
assert len(lines) == 3
nums = [list(map(int, l.replace('%', '').split()[1:])) for l in lines]
# [
# [339, 155, 54],
# [ 13, 3, 77],
# [234, 228, 3]
# ]
assert nums[1][0] < nums[2][0] < nums[0][0]
assert nums[1][1] < nums[0][1] < nums[2][1]
assert nums[2][2] < nums[0][2] < nums[1][2]
def test_defaults(self) -> None:
"""Run the report with no configuration options."""
report = self.get_summary_text()
assert 'Missing' not in report
assert 'Branch' not in report
def test_print_missing(self) -> None:
"""Run the report printing the missing lines."""
report = self.get_summary_text(('report:show_missing', True))
assert 'Missing' in report
assert 'Branch' not in report
def assert_ordering(self, text: str, *words: str) -> None:
"""Assert that the `words` appear in order in `text`."""
indexes = list(map(text.find, words))
assert -1 not in indexes
msg = f"The words {words!r} don't appear in order in {text!r}"
assert indexes == sorted(indexes), msg
def test_default_sort_report(self) -> None:
# Sort the text report by the default (Name) column.
report = self.get_summary_text()
self.assert_ordering(report, "file1.py", "file2.py", "file10.py")
def test_sort_report_by_name(self) -> None:
# Sort the text report explicitly by the Name column.
report = self.get_summary_text(('report:sort', 'Name'))
self.assert_ordering(report, "file1.py", "file2.py", "file10.py")
def test_sort_report_by_stmts(self) -> None:
# Sort the text report by the Stmts column.
report = self.get_summary_text(('report:sort', 'Stmts'))
self.assert_ordering(report, "file2.py", "file10.py", "file1.py")
def test_sort_report_by_missing(self) -> None:
# Sort the text report by the Missing column.
report = self.get_summary_text(('report:sort', 'Miss'))
self.assert_ordering(report, "file2.py", "file1.py", "file10.py")
def test_sort_report_by_cover(self) -> None:
# Sort the text report by the Cover column.
report = self.get_summary_text(('report:sort', 'Cover'))
self.assert_ordering(report, "file10.py", "file1.py", "file2.py")
def test_sort_report_by_cover_plus(self) -> None:
# Sort the text report by the Cover column, including the explicit + sign.
report = self.get_summary_text(('report:sort', '+Cover'))
self.assert_ordering(report, "file10.py", "file1.py", "file2.py")
def test_sort_report_by_cover_reversed(self) -> None:
# Sort the text report by the Cover column reversed.
report = self.get_summary_text(('report:sort', '-Cover'))
self.assert_ordering(report, "file2.py", "file1.py", "file10.py")
def test_sort_report_by_invalid_option(self) -> None:
# Sort the text report by a nonsense column.
msg = "Invalid sorting option: 'Xyzzy'"
with pytest.raises(ConfigError, match=msg):
self.get_summary_text(('report:sort', 'Xyzzy'))
def test_report_with_invalid_format(self) -> None:
# Ask for an invalid format.
msg = "Unknown report format choice: 'xyzzy'"
with pytest.raises(ConfigError, match=msg):
self.get_summary_text(('report:format', 'xyzzy'))