Skip to content

Commit

Permalink
refactor(test): reorg the parser tests in prep for moving more here
Browse files Browse the repository at this point in the history
  • Loading branch information
nedbat committed Dec 20, 2023
1 parent 24df7e9 commit 538ca96
Showing 1 changed file with 66 additions and 66 deletions.
132 changes: 66 additions & 66 deletions tests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,24 @@
from tests.helpers import arcz_to_arcs, xfail_pypy38


class PythonParserTest(CoverageTest):
class PythonParserTestBase(CoverageTest):
"""Tests for coverage.py's Python code parsing."""

run_in_temp_dir = False

def parse_source(self, text: str) -> PythonParser:
def parse_text(self, text: str) -> PythonParser:
"""Parse `text` as source, and return the `PythonParser` used."""
text = textwrap.dedent(text)
parser = PythonParser(text=text, exclude="nocover")
parser.parse_source()
return parser


class PythonParserTest(PythonParserTestBase):
"""Tests of coverage.parser."""

def test_exit_counts(self) -> None:
parser = self.parse_source("""\
parser = self.parse_text("""\
# check some basic branch counting
class Foo:
def foo(self, a):
Expand All @@ -48,7 +52,7 @@ class Bar:

def test_generator_exit_counts(self) -> None:
# https://github.com/nedbat/coveragepy/issues/324
parser = self.parse_source("""\
parser = self.parse_text("""\
def gen(input):
for n in inp:
yield (i * 2 for i in range(n))
Expand All @@ -63,7 +67,7 @@ def gen(input):
}

def test_try_except(self) -> None:
parser = self.parse_source("""\
parser = self.parse_text("""\
try:
a = 2
except ValueError:
Expand All @@ -79,7 +83,7 @@ def test_try_except(self) -> None:
}

def test_excluded_classes(self) -> None:
parser = self.parse_source("""\
parser = self.parse_text("""\
class Foo:
def __init__(self):
pass
Expand All @@ -93,15 +97,15 @@ class Bar:
}

def test_missing_branch_to_excluded_code(self) -> None:
parser = self.parse_source("""\
parser = self.parse_text("""\
if fooey:
a = 2
else: # nocover
a = 4
b = 5
""")
assert parser.exit_counts() == { 1:1, 2:1, 5:1 }
parser = self.parse_source("""\
parser = self.parse_text("""\
def foo():
if fooey:
a = 3
Expand All @@ -110,7 +114,7 @@ def foo():
b = 6
""")
assert parser.exit_counts() == { 1:1, 2:2, 3:1, 5:1, 6:1 }
parser = self.parse_source("""\
parser = self.parse_text("""\
def foo():
if fooey:
a = 3
Expand All @@ -126,7 +130,7 @@ def test_indentation_error(self) -> None:
"'unindent does not match any outer indentation level.*' at line 3"
)
with pytest.raises(NotPython, match=msg):
_ = self.parse_source("""\
_ = self.parse_text("""\
0 spaces
2
1
Expand All @@ -143,11 +147,58 @@ def test_token_error(self) -> None:
+ r"' at line 1"
)
with pytest.raises(NotPython, match=msg):
_ = self.parse_source("'''")
_ = self.parse_text("'''")

def test_empty_decorated_function(self) -> None:
parser = self.parse_text("""\
def decorator(func):
return func
@decorator
def foo(self):
'''Docstring'''
@decorator
def bar(self):
pass
""")

expected_statements = {1, 2, 4, 5, 8, 9, 10}
expected_arcs = set(arcz_to_arcs(".1 14 45 58 89 9. .2 2. -8A A-8"))
expected_exits = {1: 1, 2: 1, 4: 1, 5: 1, 8: 1, 9: 1, 10: 1}

if env.PYBEHAVIOR.docstring_only_function:
# 3.7 changed how functions with only docstrings are numbered.
expected_arcs.update(set(arcz_to_arcs("-46 6-4")))
expected_exits.update({6: 1})

if env.PYBEHAVIOR.trace_decorator_line_again:
expected_arcs.update(set(arcz_to_arcs("54 98")))
expected_exits.update({9: 2, 5: 2})

assert expected_statements == parser.statements
assert expected_arcs == parser.arcs()
assert expected_exits == parser.exit_counts()

def test_fuzzed_double_parse(self) -> None:
# https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=50381
# The second parse used to raise `TypeError: 'NoneType' object is not iterable`
msg = (
r"(EOF in multi-line statement)" # before 3.12.0b1
+ r"|(unmatched ']')" # after 3.12.0b1
)
with pytest.raises(NotPython, match=msg):
self.parse_text("]")
with pytest.raises(NotPython, match=msg):
self.parse_text("]")


class ExclusionParserTest(PythonParserTestBase):
"""Tests for the exclusion code in PythonParser."""

@xfail_pypy38
def test_decorator_pragmas(self) -> None:
parser = self.parse_source("""\
parser = self.parse_text("""\
# 1
@foo(3) # nocover
Expand Down Expand Up @@ -183,7 +234,7 @@ def func(x=25):
def test_decorator_pragmas_with_colons(self) -> None:
# A colon in a decorator expression would confuse the parser,
# ending the exclusion of the decorated function.
parser = self.parse_source("""\
parser = self.parse_text("""\
@decorate(X) # nocover
@decorate("Hello"[2])
def f():
Expand All @@ -199,7 +250,7 @@ def g():
assert parser.statements == set()

def test_class_decorator_pragmas(self) -> None:
parser = self.parse_source("""\
parser = self.parse_text("""\
class Foo(object):
def __init__(self):
self.x = 3
Expand All @@ -212,61 +263,10 @@ def __init__(self):
assert parser.raw_statements == {1, 2, 3, 5, 6, 7, 8}
assert parser.statements == {1, 2, 3}

def test_empty_decorated_function(self) -> None:
parser = self.parse_source("""\
def decorator(func):
return func
@decorator
def foo(self):
'''Docstring'''
@decorator
def bar(self):
pass
""")

expected_statements = {1, 2, 4, 5, 8, 9, 10}
expected_arcs = set(arcz_to_arcs(".1 14 45 58 89 9. .2 2. -8A A-8"))
expected_exits = {1: 1, 2: 1, 4: 1, 5: 1, 8: 1, 9: 1, 10: 1}

if env.PYBEHAVIOR.docstring_only_function:
# 3.7 changed how functions with only docstrings are numbered.
expected_arcs.update(set(arcz_to_arcs("-46 6-4")))
expected_exits.update({6: 1})

if env.PYBEHAVIOR.trace_decorator_line_again:
expected_arcs.update(set(arcz_to_arcs("54 98")))
expected_exits.update({9: 2, 5: 2})

assert expected_statements == parser.statements
assert expected_arcs == parser.arcs()
assert expected_exits == parser.exit_counts()

def test_fuzzed_double_parse(self) -> None:
# https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=50381
# The second parse used to raise `TypeError: 'NoneType' object is not iterable`
msg = (
r"(EOF in multi-line statement)" # before 3.12.0b1
+ r"|(unmatched ']')" # after 3.12.0b1
)
with pytest.raises(NotPython, match=msg):
self.parse_source("]")
with pytest.raises(NotPython, match=msg):
self.parse_source("]")


class ParserMissingArcDescriptionTest(CoverageTest):
class ParserMissingArcDescriptionTest(PythonParserTestBase):
"""Tests for PythonParser.missing_arc_description."""

run_in_temp_dir = False

def parse_text(self, source: str) -> PythonParser:
"""Parse Python source, and return the parser object."""
parser = PythonParser(text=textwrap.dedent(source))
parser.parse_source()
return parser

def test_missing_arc_description(self) -> None:
# This code is never run, so the actual values don't matter.
parser = self.parse_text("""\
Expand Down

0 comments on commit 538ca96

Please sign in to comment.