Skip to content

Commit 33f12df

Browse files
committed
feat: Coverage.branch_stats() #1888
1 parent c4919cb commit 33f12df

File tree

4 files changed

+39
-6
lines changed

4 files changed

+39
-6
lines changed

CHANGES.rst

+4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ upgrading your version of coverage.py.
2323
Unreleased
2424
----------
2525

26+
- The Coverage object has a new method, :meth:`.Coverage.branch_stats` for
27+
getting simple branch information for a module. Closes `issue 1888`_.
28+
2629
- Many constant tests in if statements are now recognized as being optimized
2730
away. For example, previously ``if 13:`` would have been considered a branch
2831
with one path not taken. Now it is understood as always true and no coverage
@@ -39,6 +42,7 @@ Unreleased
3942

4043
- Confirmed support for PyPy 3.11. Thanks Michał Górny.
4144

45+
.. _issue 1888: https://github.com/nedbat/coveragepy/issues/1888
4246
.. _pull 1919: https://github.com/nedbat/coveragepy/pull/1919
4347

4448

coverage/control.py

+15
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,7 @@ def analysis2(
950950
analysis.missing_formatted(),
951951
)
952952

953+
@functools.lru_cache(maxsize=1)
953954
def _analyze(self, morf: TMorf) -> Analysis:
954955
"""Analyze a module or file. Private for now."""
955956
self._init()
@@ -960,6 +961,20 @@ def _analyze(self, morf: TMorf) -> Analysis:
960961
filename = self._file_mapper(file_reporter.filename)
961962
return analysis_from_file_reporter(data, self.config.precision, file_reporter, filename)
962963

964+
def branch_stats(self, morf: TMorf) -> dict[TLineNo, tuple[int, int]]:
965+
"""Get branch statistics about a module.
966+
967+
`morf` is a module or a file name.
968+
969+
Returns a dict mapping line numbers to a tuple:
970+
(total_exits, taken_exits).
971+
972+
.. versionadded:: 7.7
973+
974+
"""
975+
analysis = self._analyze(morf)
976+
return analysis.branch_stats()
977+
963978
@functools.lru_cache(maxsize=1)
964979
def _get_file_reporter(self, morf: TMorf) -> FileReporter:
965980
"""Get a FileReporter for a module or file name."""

coverage/results.py

+1
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ def branch_stats(self) -> dict[TLineNo, tuple[int, int]]:
231231
232232
Returns a dict mapping line numbers to a tuple:
233233
(total_exits, taken_exits).
234+
234235
"""
235236

236237
missing_arcs = self.missing_branch_arcs()

tests/test_api.py

+19-6
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,10 @@ def fun1(x):
10081008
print("done") # pragma: nocover
10091009
10101010
def fun2(x):
1011-
print("x")
1011+
if x:
1012+
print("x")
1013+
else:
1014+
print("not x")
10121015
10131016
fun2(3)
10141017
""")
@@ -1018,12 +1021,22 @@ def fun2(x):
10181021

10191022
nums = cov._analyze("missing.py").numbers
10201023
assert nums.n_files == 1
1021-
assert nums.n_statements == 7
1024+
assert nums.n_statements == 9
10221025
assert nums.n_excluded == 1
1023-
assert nums.n_missing == 3
1024-
assert nums.n_branches == 2
1025-
assert nums.n_partial_branches == 0
1026-
assert nums.n_missing_branches == 2
1026+
assert nums.n_missing == 4
1027+
assert nums.n_branches == 4
1028+
assert nums.n_partial_branches == 1
1029+
assert nums.n_missing_branches == 3
1030+
1031+
filename, statements, excluded, missing, missing_formatted = cov.analysis2("missing.py")
1032+
assert os.path.relpath(filename) == "missing.py"
1033+
assert statements == [1, 2, 3, 5, 8, 9, 10, 12, 14]
1034+
assert excluded == [6]
1035+
assert missing == [2, 3, 5, 12]
1036+
assert missing_formatted == "2-5, 12"
1037+
1038+
branch_stats = cov.branch_stats("missing.py")
1039+
assert branch_stats == {2: (2, 0), 9: (2, 1)}
10271040

10281041

10291042
class TestRunnerPluginTest(CoverageTest):

0 commit comments

Comments
 (0)