Skip to content

Commit 5ee7c64

Browse files
committed
wip: implicit path mapping during reporting. #1212
1 parent ff9839f commit 5ee7c64

File tree

5 files changed

+181
-13
lines changed

5 files changed

+181
-13
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ Unreleased
2929
- Using ``--format=total`` will write a single total number to the
3030
output. This can be useful for making badges or writing status updates.
3131

32+
- TODO: implicit path mapping during reporting.
33+
3234
- Combining data files with ``coverage combine`` now quickly hashes the data
3335
files to skip files that provide no new information. This can reduce the
3436
time needed. Many details affect the results, but for coverage.py's own test

coverage/control.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,18 @@ def save(self):
733733
data = self.get_data()
734734
data.write()
735735

736+
def _make_aliases(self):
737+
"""Create a PathAliases from our configuration."""
738+
aliases = PathAliases(
739+
debugfn=(self._debug.write if self._debug.should("pathmap") else None),
740+
relative=self.config.relative_files,
741+
)
742+
for paths in self.config.paths.values():
743+
result = paths[0]
744+
for pattern in paths[1:]:
745+
aliases.add(pattern, result)
746+
return aliases
747+
736748
def combine(self, data_paths=None, strict=False, keep=False):
737749
"""Combine together a number of similarly-named coverage data files.
738750
@@ -764,18 +776,9 @@ def combine(self, data_paths=None, strict=False, keep=False):
764776
self._post_init()
765777
self.get_data()
766778

767-
aliases = PathAliases(
768-
debugfn=(self._debug.write if self._debug.should("pathmap") else None),
769-
relative=self.config.relative_files,
770-
)
771-
for paths in self.config.paths.values():
772-
result = paths[0]
773-
for pattern in paths[1:]:
774-
aliases.add(pattern, result)
775-
776779
combine_parallel_data(
777780
self._data,
778-
aliases=aliases,
781+
aliases=self._make_aliases(),
779782
data_paths=data_paths,
780783
strict=strict,
781784
keep=keep,
@@ -925,6 +928,13 @@ def _get_file_reporters(self, morfs=None):
925928
file_reporters = [self._get_file_reporter(morf) for morf in morfs]
926929
return file_reporters
927930

931+
def _prepare_data_for_reporting(self):
932+
"""Re-map data before reporting, to get implicit 'combine' behavior."""
933+
if self.config.paths:
934+
mapped_data = CoverageData(warn=self._warn, debug=self._debug, no_disk=True)
935+
mapped_data.update(self._data, aliases=self._make_aliases())
936+
self._data = mapped_data
937+
928938
def report(
929939
self,
930940
morfs=None,
@@ -990,6 +1000,7 @@ def report(
9901000
The `format` parameter.
9911001
9921002
"""
1003+
self._prepare_data_for_reporting()
9931004
with override_config(
9941005
self,
9951006
ignore_errors=ignore_errors,
@@ -1034,6 +1045,7 @@ def annotate(
10341045
print("The annotate command will be removed in a future version.")
10351046
print("Get in touch if you still use it: [email protected]")
10361047

1048+
self._prepare_data_for_reporting()
10371049
with override_config(
10381050
self,
10391051
ignore_errors=ignore_errors,
@@ -1083,6 +1095,7 @@ def html_report(
10831095
changing the files in the report folder.
10841096
10851097
"""
1098+
self._prepare_data_for_reporting()
10861099
with override_config(
10871100
self,
10881101
ignore_errors=ignore_errors,
@@ -1123,6 +1136,7 @@ def xml_report(
11231136
Returns a float, the total percentage covered.
11241137
11251138
"""
1139+
self._prepare_data_for_reporting()
11261140
with override_config(
11271141
self,
11281142
ignore_errors=ignore_errors,
@@ -1157,6 +1171,7 @@ def json_report(
11571171
.. versionadded:: 5.0
11581172
11591173
"""
1174+
self._prepare_data_for_reporting()
11601175
with override_config(
11611176
self,
11621177
ignore_errors=ignore_errors,
@@ -1187,6 +1202,7 @@ def lcov_report(
11871202
11881203
.. versionadded:: 6.3
11891204
"""
1205+
self._prepare_data_for_reporting()
11901206
with override_config(
11911207
self,
11921208
ignore_errors=ignore_errors,

coverage/sqldata.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,12 @@ def update(self, other_data, aliases=None):
648648
"inner join file on file.id = line_bits.file_id " +
649649
"inner join context on context.id = line_bits.context_id"
650650
)
651-
lines = {(files[path], context): numbits for (path, context, numbits) in cur}
651+
lines = {}
652+
for path, context, numbits in cur:
653+
key = (files[path], context)
654+
if key in lines:
655+
numbits = numbits_union(lines[key], numbits)
656+
lines[key] = numbits
652657
cur.close()
653658

654659
# Get tracer data.

tests/coveragetest.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,14 @@ def check_coverage(
219219

220220
return cov
221221

222-
def make_data_file(self, basename=None, suffix=None, lines=None, file_tracers=None):
222+
def make_data_file(self, basename=None, suffix=None, lines=None, arcs=None, file_tracers=None):
223223
"""Write some data into a coverage data file."""
224224
data = coverage.CoverageData(basename=basename, suffix=suffix)
225+
assert lines is None or arcs is None
225226
if lines:
226227
data.add_lines(lines)
228+
if arcs:
229+
data.add_arcs(arcs)
227230
if file_tracers:
228231
data.add_file_tracers(file_tracers)
229232
data.write()

tests/test_api.py

Lines changed: 143 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
from coverage.misc import import_local_file
2424

2525
from tests.coveragetest import CoverageTest, TESTS_DIR, UsingModulesMixin
26-
from tests.helpers import assert_count_equal, assert_coverage_warnings
26+
from tests.goldtest import contains, doesnt_contain
27+
from tests.helpers import arcz_to_arcs, assert_count_equal, assert_coverage_warnings
2728
from tests.helpers import change_dir, nice_file, os_sep
2829

2930
BAD_SQLITE_REGEX = r"file( is encrypted or)? is not a database"
@@ -1456,3 +1457,144 @@ def test_combine_parallel_data_keep(self):
14561457
# After combining, the .coverage file & the original combined file should still be there.
14571458
self.assert_exists(".coverage")
14581459
self.assert_file_count(".coverage.*", 2)
1460+
1461+
1462+
class ReportMapsPathsTest(CoverageTest):
1463+
"""Check that reporting implicitly maps paths."""
1464+
1465+
def make_files(self, data="lines", settings=False):
1466+
"""Create the test files we need for line coverage."""
1467+
src = """\
1468+
if VER == 1:
1469+
print("line 2")
1470+
if VER == 2:
1471+
print("line 4")
1472+
if VER == 3:
1473+
print("line 6")
1474+
"""
1475+
self.make_file("src/program.py", src)
1476+
self.make_file("ver1/program.py", src)
1477+
self.make_file("ver2/program.py", src)
1478+
1479+
if data == "line":
1480+
self.make_data_file(
1481+
lines={
1482+
abs_file("ver1/program.py"): [1, 2, 3, 5],
1483+
abs_file("ver2/program.py"): [1, 3, 4, 5],
1484+
}
1485+
)
1486+
else:
1487+
self.make_data_file(
1488+
arcs={
1489+
abs_file("ver1/program.py"): arcz_to_arcs(".1 12 23 35 5."),
1490+
abs_file("ver2/program.py"): arcz_to_arcs(".1 13 34 45 5."),
1491+
}
1492+
)
1493+
1494+
if settings:
1495+
self.make_file(".coveragerc", """\
1496+
[paths]
1497+
source =
1498+
src
1499+
ver1
1500+
ver2
1501+
""")
1502+
1503+
def test_map_paths_during_line_report_without_setting(self):
1504+
self.make_files(data="line")
1505+
cov = coverage.Coverage()
1506+
cov.load()
1507+
cov.report(show_missing=True)
1508+
expected = textwrap.dedent(os_sep("""\
1509+
Name Stmts Miss Cover Missing
1510+
-----------------------------------------------
1511+
ver1/program.py 6 2 67% 4, 6
1512+
ver2/program.py 6 2 67% 2, 6
1513+
-----------------------------------------------
1514+
TOTAL 12 4 67%
1515+
"""))
1516+
assert expected == self.stdout()
1517+
1518+
def test_map_paths_during_line_report(self):
1519+
self.make_files(data="line", settings=True)
1520+
cov = coverage.Coverage()
1521+
cov.load()
1522+
cov.report(show_missing=True)
1523+
expected = textwrap.dedent(os_sep("""\
1524+
Name Stmts Miss Cover Missing
1525+
----------------------------------------------
1526+
src/program.py 6 1 83% 6
1527+
----------------------------------------------
1528+
TOTAL 6 1 83%
1529+
"""))
1530+
assert expected == self.stdout()
1531+
1532+
def test_map_paths_during_branch_report_without_setting(self):
1533+
self.make_files(data="arcs")
1534+
cov = coverage.Coverage(branch=True)
1535+
cov.load()
1536+
cov.report(show_missing=True)
1537+
expected = textwrap.dedent(os_sep("""\
1538+
Name Stmts Miss Branch BrPart Cover Missing
1539+
-------------------------------------------------------------
1540+
ver1/program.py 6 2 6 3 58% 1->3, 4, 6
1541+
ver2/program.py 6 2 6 3 58% 2, 3->5, 6
1542+
-------------------------------------------------------------
1543+
TOTAL 12 4 12 6 58%
1544+
"""))
1545+
assert expected == self.stdout()
1546+
1547+
def test_map_paths_during_branch_report(self):
1548+
self.make_files(data="arcs", settings=True)
1549+
cov = coverage.Coverage(branch=True)
1550+
cov.load()
1551+
cov.report(show_missing=True)
1552+
expected = textwrap.dedent(os_sep("""\
1553+
Name Stmts Miss Branch BrPart Cover Missing
1554+
------------------------------------------------------------
1555+
src/program.py 6 1 6 1 83% 6
1556+
------------------------------------------------------------
1557+
TOTAL 6 1 6 1 83%
1558+
"""))
1559+
assert expected == self.stdout()
1560+
1561+
def test_map_paths_during_annotate(self):
1562+
self.make_files(data="line", settings=True)
1563+
cov = coverage.Coverage()
1564+
cov.load()
1565+
cov.annotate()
1566+
self.assert_exists(os_sep("src/program.py,cover"))
1567+
self.assert_doesnt_exist(os_sep("ver1/program.py,cover"))
1568+
self.assert_doesnt_exist(os_sep("ver2/program.py,cover"))
1569+
1570+
def test_map_paths_during_html_report(self):
1571+
self.make_files(data="line", settings=True)
1572+
cov = coverage.Coverage()
1573+
cov.load()
1574+
cov.html_report()
1575+
contains("htmlcov/index.html", os_sep("src/program.py"))
1576+
doesnt_contain("htmlcov/index.html", os_sep("ver1/program.py"), os_sep("ver2/program.py"))
1577+
1578+
def test_map_paths_during_xml_report(self):
1579+
self.make_files(data="line", settings=True)
1580+
cov = coverage.Coverage()
1581+
cov.load()
1582+
cov.xml_report()
1583+
contains("coverage.xml", os_sep("src/program.py"))
1584+
doesnt_contain("coverage.xml", os_sep("ver1/program.py"), os_sep("ver2/program.py"))
1585+
1586+
def test_map_paths_during_json_report(self):
1587+
self.make_files(data="line", settings=True)
1588+
cov = coverage.Coverage()
1589+
cov.load()
1590+
cov.json_report()
1591+
contains("coverage.json", os_sep("src/program.py"))
1592+
doesnt_contain("coverage.json", os_sep("ver1/program.py"), os_sep("ver2/program.py"))
1593+
1594+
def test_map_paths_during_lcov_report(self):
1595+
self.make_files(data="line", settings=True)
1596+
cov = coverage.Coverage()
1597+
cov.load()
1598+
cov.lcov_report()
1599+
contains("coverage.lcov", os_sep("src/program.py"))
1600+
doesnt_contain("coverage.lcov", os_sep("ver1/program.py"), os_sep("ver2/program.py"))

0 commit comments

Comments
 (0)