1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
# For details: https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt
"""Tests for coverage/regions.py."""
from __future__ import annotations
import collections
import textwrap
from pathlib import Path
import pytest
import coverage
from coverage import env
from coverage.plugin import CodeRegion
from coverage.regions import code_regions
skip_pypy38 = pytest.mark.skipif(
env.PYPY and env.PYVERSION < (3, 9),
reason="PyPy 3.8 somehow gets different results from ast?",
# But PyPy 3.8 is almost out of support so meh.
)
@skip_pypy38
def test_code_regions() -> None:
regions = code_regions(textwrap.dedent("""\
# Numbers in this code are the line number.
'''Module docstring'''
CONST = 4
class MyClass:
class_attr = 6
def __init__(self):
self.x = 9
def method_a(self):
self.x = 12
def inmethod():
self.x = 14
class DeepInside:
def method_b():
self.x = 17
class Deeper:
def bb():
self.x = 20
self.y = 21
class InnerClass:
constant = 24
def method_c(self):
self.x = 26
def func():
x = 29
y = 30
def inner():
z = 32
def inner_inner():
w = 34
class InsideFunc:
def method_d(self):
self.x = 38
return 40
async def afunc():
x = 43
"""))
F = "function"
C = "class"
assert sorted(regions) == sorted([
CodeRegion(F, "MyClass.__init__", start=8, lines={9}),
CodeRegion(F, "MyClass.method_a", start=11, lines={12, 13, 21}),
CodeRegion(F, "MyClass.method_a.inmethod", start=13, lines={14, 15, 16, 18, 19}),
CodeRegion(F, "MyClass.method_a.inmethod.DeepInside.method_b", start=16, lines={17}),
CodeRegion(F, "MyClass.method_a.inmethod.DeepInside.Deeper.bb", start=19, lines={20}),
CodeRegion(F, "MyClass.InnerClass.method_c", start=25, lines={26}),
CodeRegion(F, "func", start=28, lines={29, 30, 31, 35, 36, 37, 39, 40}),
CodeRegion(F, "func.inner", start=31, lines={32, 33}),
CodeRegion(F, "func.inner.inner_inner", start=33, lines={34}),
CodeRegion(F, "func.InsideFunc.method_d", start=37, lines={38}),
CodeRegion(F, "afunc", start=42, lines={43}),
CodeRegion(C, "MyClass", start=5, lines={9, 12, 13, 14, 15, 16, 18, 19, 21}),
CodeRegion(C, "MyClass.method_a.inmethod.DeepInside", start=15, lines={17}),
CodeRegion(C, "MyClass.method_a.inmethod.DeepInside.Deeper", start=18, lines={20}),
CodeRegion(C, "MyClass.InnerClass", start=23, lines={26}),
CodeRegion(C, "func.InsideFunc", start=36, lines={38}),
])
@skip_pypy38
def test_real_code_regions() -> None:
# Run code_regions on most of the coverage source code, checking that it
# succeeds and there are no overlaps.
cov_dir = Path(coverage.__file__).parent.parent
any_fails = False
# To run against all the files in the tox venvs:
# for source_file in cov_dir.rglob("*.py"):
for sub in [".", "ci", "coverage", "lab", "tests"]:
for source_file in (cov_dir / sub).glob("*.py"):
regions = code_regions(source_file.read_text(encoding="utf-8"))
for kind in ["function", "class"]:
kind_regions = [reg for reg in regions if reg.kind == kind]
line_counts = collections.Counter(
lno for reg in kind_regions for lno in reg.lines
)
overlaps = [line for line, count in line_counts.items() if count > 1]
if overlaps: # pragma: only failure
print(
f"{kind.title()} overlaps in {source_file.relative_to(Path.cwd())}: "
+ f"{overlaps}"
)
any_fails = True
if any_fails:
pytest.fail("Overlaps were found") # pragma: only failure
|