blob: 6472376e51daea7162144ecf91de2a72be206612 [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 LCOV-based summary reporting for coverage.py."""
from __future__ import annotations
import math
import textwrap
import coverage
from tests.coveragetest import CoverageTest
class LcovTest(CoverageTest):
"""Tests of the LCOV reports from coverage.py."""
def create_initial_files(self) -> None:
"""
Helper for tests that handles the common ceremony so the tests can
show the consequences of changes in the setup.
"""
self.make_file(
"main_file.py",
"""\
def cuboid_volume(l):
return (l*l*l)
def IsItTrue():
return True
""",
)
self.make_file(
"test_file.py",
"""\
from main_file import cuboid_volume
import unittest
class TestCuboid(unittest.TestCase):
def test_volume(self):
self.assertAlmostEqual(cuboid_volume(2),8)
self.assertAlmostEqual(cuboid_volume(1),1)
self.assertAlmostEqual(cuboid_volume(0),0)
self.assertAlmostEqual(cuboid_volume(5.5),166.375)
""",
)
def get_lcov_report_content(self, filename: str = "coverage.lcov") -> str:
"""Return the content of an LCOV report."""
with open(filename, encoding="utf-8") as file:
return file.read()
def test_lone_file(self) -> None:
# For a single file with a couple of functions, the lcov should cover
# the function definitions themselves, but not the returns.
self.make_file(
"main_file.py",
"""\
def cuboid_volume(l):
return (l*l*l)
def IsItTrue():
return True
""",
)
expected_result = textwrap.dedent("""\
SF:main_file.py
DA:1,1
DA:2,0
DA:4,1
DA:5,0
LF:4
LH:2
FN:1,2,cuboid_volume
FNDA:0,cuboid_volume
FN:4,5,IsItTrue
FNDA:0,IsItTrue
FNF:2
FNH:0
end_of_record
""")
self.assert_doesnt_exist(".coverage")
cov = coverage.Coverage(source=["."])
self.start_import_stop(cov, "main_file")
pct = cov.lcov_report()
assert pct == 50.0
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_line_checksums(self) -> None:
self.make_file(
"main_file.py",
"""\
def cuboid_volume(l):
return (l*l*l)
def IsItTrue():
return True
""",
)
self.make_file(".coveragerc", "[lcov]\nline_checksums = true\n")
self.assert_doesnt_exist(".coverage")
cov = coverage.Coverage(source=["."])
self.start_import_stop(cov, "main_file")
pct = cov.lcov_report()
assert pct == 50.0
expected_result = textwrap.dedent("""\
SF:main_file.py
DA:1,1,7URou3io0zReBkk69lEb/Q
DA:2,0,Xqj6H1iz/nsARMCAbE90ng
DA:4,1,ilhb4KUfytxtEuClijZPlQ
DA:5,0,LWILTcvARcydjFFyo9qM0A
LF:4
LH:2
FN:1,2,cuboid_volume
FNDA:0,cuboid_volume
FN:4,5,IsItTrue
FNDA:0,IsItTrue
FNF:2
FNH:0
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_simple_line_coverage_two_files(self) -> None:
# Test that line coverage is created when coverage is run,
# and matches the output of the file below.
self.create_initial_files()
self.assert_doesnt_exist(".coverage")
self.make_file(".coveragerc", "[lcov]\noutput = data.lcov\n")
cov = coverage.Coverage(source=".")
self.start_import_stop(cov, "test_file")
pct = cov.lcov_report()
assert pct == 50.0
self.assert_exists("data.lcov")
expected_result = textwrap.dedent("""\
SF:main_file.py
DA:1,1
DA:2,0
DA:4,1
DA:5,0
LF:4
LH:2
FN:1,2,cuboid_volume
FNDA:0,cuboid_volume
FN:4,5,IsItTrue
FNDA:0,IsItTrue
FNF:2
FNH:0
end_of_record
SF:test_file.py
DA:1,1
DA:2,1
DA:4,1
DA:5,1
DA:6,0
DA:7,0
DA:8,0
DA:9,0
LF:8
LH:4
FN:5,9,TestCuboid.test_volume
FNDA:0,TestCuboid.test_volume
FNF:1
FNH:0
end_of_record
""")
actual_result = self.get_lcov_report_content(filename="data.lcov")
assert expected_result == actual_result
def test_branch_coverage_one_file(self) -> None:
# Test that the reporter produces valid branch coverage.
self.make_file(
"main_file.py",
"""\
def is_it_x(x):
if x == 3:
return x
else:
return False
""",
)
self.assert_doesnt_exist(".coverage")
cov = coverage.Coverage(branch=True, source=".")
self.start_import_stop(cov, "main_file")
pct = cov.lcov_report()
assert math.isclose(pct, 16.666666666666668)
self.assert_exists("coverage.lcov")
expected_result = textwrap.dedent("""\
SF:main_file.py
DA:1,1
DA:2,0
DA:3,0
DA:5,0
LF:4
LH:1
FN:1,5,is_it_x
FNDA:0,is_it_x
FNF:1
FNH:0
BRDA:2,0,jump to line 3,-
BRDA:2,0,jump to line 5,-
BRF:2
BRH:0
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_branch_coverage_two_files(self) -> None:
# Test that valid branch coverage is generated
# in the case of two files.
self.make_file(
"main_file.py",
"""\
def is_it_x(x):
if x == 3:
return x
else:
return False
""",
)
self.make_file(
"test_file.py",
"""\
from main_file import *
import unittest
class TestIsItX(unittest.TestCase):
def test_is_it_x(self):
self.assertEqual(is_it_x(3), 3)
self.assertEqual(is_it_x(4), False)
""",
)
self.assert_doesnt_exist(".coverage")
cov = coverage.Coverage(branch=True, source=".")
self.start_import_stop(cov, "test_file")
pct = cov.lcov_report()
assert math.isclose(pct, 41.666666666666664)
self.assert_exists("coverage.lcov")
expected_result = textwrap.dedent("""\
SF:main_file.py
DA:1,1
DA:2,0
DA:3,0
DA:5,0
LF:4
LH:1
FN:1,5,is_it_x
FNDA:0,is_it_x
FNF:1
FNH:0
BRDA:2,0,jump to line 3,-
BRDA:2,0,jump to line 5,-
BRF:2
BRH:0
end_of_record
SF:test_file.py
DA:1,1
DA:2,1
DA:4,1
DA:5,1
DA:6,0
DA:7,0
LF:6
LH:4
FN:5,7,TestIsItX.test_is_it_x
FNDA:0,TestIsItX.test_is_it_x
FNF:1
FNH:0
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_half_covered_branch(self) -> None:
# Test that for a given branch that is only half covered,
# the block numbers remain the same, and produces valid lcov.
self.make_file(
"main_file.py",
"""\
something = True
if something:
print("Yes, something")
else:
print("No, nothing")
""",
)
self.assert_doesnt_exist(".coverage")
cov = coverage.Coverage(branch=True, source=".")
self.start_import_stop(cov, "main_file")
pct = cov.lcov_report()
assert math.isclose(pct, 66.66666666666667)
self.assert_exists("coverage.lcov")
expected_result = textwrap.dedent("""\
SF:main_file.py
DA:1,1
DA:3,1
DA:4,1
DA:6,0
LF:4
LH:3
BRDA:3,0,jump to line 4,1
BRDA:3,0,jump to line 6,0
BRF:2
BRH:1
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_empty_init_files(self) -> None:
# Test that an empty __init__.py still generates a (vacuous)
# coverage record.
self.make_file("__init__.py", "")
self.assert_doesnt_exist(".coverage")
cov = coverage.Coverage(branch=True, source=".")
self.start_import_stop(cov, "__init__")
pct = cov.lcov_report()
assert pct == 0.0
self.assert_exists("coverage.lcov")
expected_result = textwrap.dedent("""\
SF:__init__.py
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_empty_init_file_skipped(self) -> None:
# Test that the lcov reporter honors skip_empty. Because skip_empty
# keys off the overall number of lines of code, the result in this
# case will be the same regardless of the age of the Python interpreter.
self.make_file("__init__.py", "")
self.make_file(".coveragerc", "[report]\nskip_empty = True\n")
self.assert_doesnt_exist(".coverage")
cov = coverage.Coverage(branch=True, source=".")
self.start_import_stop(cov, "__init__")
pct = cov.lcov_report()
assert pct == 0.0
self.assert_exists("coverage.lcov")
expected_result = ""
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_excluded_lines(self) -> None:
self.make_file(
".coveragerc",
"""\
[report]
exclude_lines = foo
""",
)
self.make_file(
"runme.py",
"""\
s = "Hello 1"
t = "foo is ignored 2"
if s.upper() == "BYE 3":
i_am_missing_4()
foo_is_missing_5()
print("Done 6")
# foo 7
# line 8
""",
)
cov = coverage.Coverage(source=".", branch=True)
self.start_import_stop(cov, "runme")
cov.lcov_report()
expected_result = textwrap.dedent("""\
SF:runme.py
DA:1,1
DA:3,1
DA:4,0
DA:6,1
LF:4
LH:3
BRDA:3,0,jump to line 4,0
BRDA:3,0,jump to line 6,1
BRF:2
BRH:1
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_exit_branches(self) -> None:
self.make_file(
"runme.py",
"""\
def foo(a):
if a:
print(f"{a!r} is truthy")
foo(True)
foo(False)
foo([])
foo([0])
""",
)
cov = coverage.Coverage(source=".", branch=True)
self.start_import_stop(cov, "runme")
cov.lcov_report()
expected_result = textwrap.dedent("""\
SF:runme.py
DA:1,1
DA:2,1
DA:3,1
DA:4,1
DA:5,1
DA:6,1
DA:7,1
LF:7
LH:7
FN:1,3,foo
FNDA:1,foo
FNF:1
FNH:1
BRDA:2,0,jump to line 3,1
BRDA:2,0,return from function 'foo',1
BRF:2
BRH:2
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_genexpr_exit_arcs_pruned_full_coverage(self) -> None:
self.make_file(
"runme.py",
"""\
def foo(a):
if any(x > 0 for x in a):
print(f"{a!r} has positives")
foo([])
foo([0])
foo([0,1])
foo([0,-1])
""",
)
cov = coverage.Coverage(source=".", branch=True)
self.start_import_stop(cov, "runme")
cov.lcov_report()
expected_result = textwrap.dedent("""\
SF:runme.py
DA:1,1
DA:2,1
DA:3,1
DA:4,1
DA:5,1
DA:6,1
DA:7,1
LF:7
LH:7
FN:1,3,foo
FNDA:1,foo
FNF:1
FNH:1
BRDA:2,0,jump to line 3,1
BRDA:2,0,return from function 'foo',1
BRF:2
BRH:2
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_genexpr_exit_arcs_pruned_never_true(self) -> None:
self.make_file(
"runme.py",
"""\
def foo(a):
if any(x > 0 for x in a):
print(f"{a!r} has positives")
foo([])
foo([0])
""",
)
cov = coverage.Coverage(source=".", branch=True)
self.start_import_stop(cov, "runme")
cov.lcov_report()
expected_result = textwrap.dedent("""\
SF:runme.py
DA:1,1
DA:2,1
DA:3,0
DA:4,1
DA:5,1
LF:5
LH:4
FN:1,3,foo
FNDA:1,foo
FNF:1
FNH:1
BRDA:2,0,jump to line 3,0
BRDA:2,0,return from function 'foo',1
BRF:2
BRH:1
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_genexpr_exit_arcs_pruned_always_true(self) -> None:
self.make_file(
"runme.py",
"""\
def foo(a):
if any(x > 0 for x in a):
print(f"{a!r} has positives")
foo([1])
foo([1,2])
""",
)
cov = coverage.Coverage(source=".", branch=True)
self.start_import_stop(cov, "runme")
cov.lcov_report()
expected_result = textwrap.dedent("""\
SF:runme.py
DA:1,1
DA:2,1
DA:3,1
DA:4,1
DA:5,1
LF:5
LH:5
FN:1,3,foo
FNDA:1,foo
FNF:1
FNH:1
BRDA:2,0,jump to line 3,1
BRDA:2,0,return from function 'foo',0
BRF:2
BRH:1
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_genexpr_exit_arcs_pruned_not_reached(self) -> None:
self.make_file(
"runme.py",
"""\
def foo(a):
if any(x > 0 for x in a):
print(f"{a!r} has positives")
""",
)
cov = coverage.Coverage(source=".", branch=True)
self.start_import_stop(cov, "runme")
cov.lcov_report()
expected_result = textwrap.dedent("""\
SF:runme.py
DA:1,1
DA:2,0
DA:3,0
LF:3
LH:1
FN:1,3,foo
FNDA:0,foo
FNF:1
FNH:0
BRDA:2,0,jump to line 3,-
BRDA:2,0,return from function 'foo',-
BRF:2
BRH:0
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_always_raise(self) -> None:
self.make_file(
"always_raise.py",
"""\
try:
if not_defined:
print("Yes")
else:
print("No")
except Exception:
pass
""",
)
cov = coverage.Coverage(source=".", branch=True)
self.start_import_stop(cov, "always_raise")
cov.lcov_report()
expected_result = textwrap.dedent("""\
SF:always_raise.py
DA:1,1
DA:2,1
DA:3,0
DA:5,0
DA:6,1
DA:7,1
LF:6
LH:4
BRDA:2,0,jump to line 3,-
BRDA:2,0,jump to line 5,-
BRF:2
BRH:0
end_of_record
""")
actual_result = self.get_lcov_report_content()
assert expected_result == actual_result
def test_multiline_conditions(self) -> None:
self.make_file(
"multi.py",
"""\
def fun(x):
if (
x
):
print("got here")
""",
)
cov = coverage.Coverage(source=".", branch=True)
self.start_import_stop(cov, "multi")
cov.lcov_report()
lcov = self.get_lcov_report_content()
assert "BRDA:2,0,return from function 'fun',-" in lcov
def test_module_exit(self) -> None:
self.make_file(
"modexit.py",
"""\
#! /usr/bin/env python
def foo():
return bar(
)
if "x" == "y": # line 5
foo()
""",
)
cov = coverage.Coverage(source=".", branch=True)
self.start_import_stop(cov, "modexit")
cov.lcov_report()
lcov = self.get_lcov_report_content()
print(lcov)
assert "BRDA:5,0,exit the module,1" in lcov