17
17
from tests .helpers import arcz_to_arcs , xfail_pypy38
18
18
19
19
20
- class PythonParserTest (CoverageTest ):
20
+ class PythonParserTestBase (CoverageTest ):
21
21
"""Tests for coverage.py's Python code parsing."""
22
22
23
23
run_in_temp_dir = False
24
24
25
- def parse_source (self , text : str ) -> PythonParser :
25
+ def parse_text (self , text : str ) -> PythonParser :
26
26
"""Parse `text` as source, and return the `PythonParser` used."""
27
27
text = textwrap .dedent (text )
28
28
parser = PythonParser (text = text , exclude = "nocover" )
29
29
parser .parse_source ()
30
30
return parser
31
31
32
+
33
+ class PythonParserTest (PythonParserTestBase ):
34
+ """Tests of coverage.parser."""
35
+
32
36
def test_exit_counts (self ) -> None :
33
- parser = self .parse_source ("""\
37
+ parser = self .parse_text ("""\
34
38
# check some basic branch counting
35
39
class Foo:
36
40
def foo(self, a):
@@ -48,7 +52,7 @@ class Bar:
48
52
49
53
def test_generator_exit_counts (self ) -> None :
50
54
# https://github.com/nedbat/coveragepy/issues/324
51
- parser = self .parse_source ("""\
55
+ parser = self .parse_text ("""\
52
56
def gen(input):
53
57
for n in inp:
54
58
yield (i * 2 for i in range(n))
@@ -63,7 +67,7 @@ def gen(input):
63
67
}
64
68
65
69
def test_try_except (self ) -> None :
66
- parser = self .parse_source ("""\
70
+ parser = self .parse_text ("""\
67
71
try:
68
72
a = 2
69
73
except ValueError:
@@ -79,7 +83,7 @@ def test_try_except(self) -> None:
79
83
}
80
84
81
85
def test_excluded_classes (self ) -> None :
82
- parser = self .parse_source ("""\
86
+ parser = self .parse_text ("""\
83
87
class Foo:
84
88
def __init__(self):
85
89
pass
@@ -93,15 +97,15 @@ class Bar:
93
97
}
94
98
95
99
def test_missing_branch_to_excluded_code (self ) -> None :
96
- parser = self .parse_source ("""\
100
+ parser = self .parse_text ("""\
97
101
if fooey:
98
102
a = 2
99
103
else: # nocover
100
104
a = 4
101
105
b = 5
102
106
""" )
103
107
assert parser .exit_counts () == { 1 :1 , 2 :1 , 5 :1 }
104
- parser = self .parse_source ("""\
108
+ parser = self .parse_text ("""\
105
109
def foo():
106
110
if fooey:
107
111
a = 3
@@ -110,7 +114,7 @@ def foo():
110
114
b = 6
111
115
""" )
112
116
assert parser .exit_counts () == { 1 :1 , 2 :2 , 3 :1 , 5 :1 , 6 :1 }
113
- parser = self .parse_source ("""\
117
+ parser = self .parse_text ("""\
114
118
def foo():
115
119
if fooey:
116
120
a = 3
@@ -126,7 +130,7 @@ def test_indentation_error(self) -> None:
126
130
"'unindent does not match any outer indentation level.*' at line 3"
127
131
)
128
132
with pytest .raises (NotPython , match = msg ):
129
- _ = self .parse_source ("""\
133
+ _ = self .parse_text ("""\
130
134
0 spaces
131
135
2
132
136
1
@@ -143,11 +147,58 @@ def test_token_error(self) -> None:
143
147
+ r"' at line 1"
144
148
)
145
149
with pytest .raises (NotPython , match = msg ):
146
- _ = self .parse_source ("'''" )
150
+ _ = self .parse_text ("'''" )
151
+
152
+ def test_empty_decorated_function (self ) -> None :
153
+ parser = self .parse_text ("""\
154
+ def decorator(func):
155
+ return func
156
+
157
+ @decorator
158
+ def foo(self):
159
+ '''Docstring'''
160
+
161
+ @decorator
162
+ def bar(self):
163
+ pass
164
+ """ )
165
+
166
+ expected_statements = {1 , 2 , 4 , 5 , 8 , 9 , 10 }
167
+ expected_arcs = set (arcz_to_arcs (".1 14 45 58 89 9. .2 2. -8A A-8" ))
168
+ expected_exits = {1 : 1 , 2 : 1 , 4 : 1 , 5 : 1 , 8 : 1 , 9 : 1 , 10 : 1 }
169
+
170
+ if env .PYBEHAVIOR .docstring_only_function :
171
+ # 3.7 changed how functions with only docstrings are numbered.
172
+ expected_arcs .update (set (arcz_to_arcs ("-46 6-4" )))
173
+ expected_exits .update ({6 : 1 })
174
+
175
+ if env .PYBEHAVIOR .trace_decorator_line_again :
176
+ expected_arcs .update (set (arcz_to_arcs ("54 98" )))
177
+ expected_exits .update ({9 : 2 , 5 : 2 })
178
+
179
+ assert expected_statements == parser .statements
180
+ assert expected_arcs == parser .arcs ()
181
+ assert expected_exits == parser .exit_counts ()
182
+
183
+ def test_fuzzed_double_parse (self ) -> None :
184
+ # https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=50381
185
+ # The second parse used to raise `TypeError: 'NoneType' object is not iterable`
186
+ msg = (
187
+ r"(EOF in multi-line statement)" # before 3.12.0b1
188
+ + r"|(unmatched ']')" # after 3.12.0b1
189
+ )
190
+ with pytest .raises (NotPython , match = msg ):
191
+ self .parse_text ("]" )
192
+ with pytest .raises (NotPython , match = msg ):
193
+ self .parse_text ("]" )
194
+
195
+
196
+ class ExclusionParserTest (PythonParserTestBase ):
197
+ """Tests for the exclusion code in PythonParser."""
147
198
148
199
@xfail_pypy38
149
200
def test_decorator_pragmas (self ) -> None :
150
- parser = self .parse_source ("""\
201
+ parser = self .parse_text ("""\
151
202
# 1
152
203
153
204
@foo(3) # nocover
@@ -183,7 +234,7 @@ def func(x=25):
183
234
def test_decorator_pragmas_with_colons (self ) -> None :
184
235
# A colon in a decorator expression would confuse the parser,
185
236
# ending the exclusion of the decorated function.
186
- parser = self .parse_source ("""\
237
+ parser = self .parse_text ("""\
187
238
@decorate(X) # nocover
188
239
@decorate("Hello"[2])
189
240
def f():
@@ -199,7 +250,7 @@ def g():
199
250
assert parser .statements == set ()
200
251
201
252
def test_class_decorator_pragmas (self ) -> None :
202
- parser = self .parse_source ("""\
253
+ parser = self .parse_text ("""\
203
254
class Foo(object):
204
255
def __init__(self):
205
256
self.x = 3
@@ -212,61 +263,10 @@ def __init__(self):
212
263
assert parser .raw_statements == {1 , 2 , 3 , 5 , 6 , 7 , 8 }
213
264
assert parser .statements == {1 , 2 , 3 }
214
265
215
- def test_empty_decorated_function (self ) -> None :
216
- parser = self .parse_source ("""\
217
- def decorator(func):
218
- return func
219
-
220
- @decorator
221
- def foo(self):
222
- '''Docstring'''
223
-
224
- @decorator
225
- def bar(self):
226
- pass
227
- """ )
228
-
229
- expected_statements = {1 , 2 , 4 , 5 , 8 , 9 , 10 }
230
- expected_arcs = set (arcz_to_arcs (".1 14 45 58 89 9. .2 2. -8A A-8" ))
231
- expected_exits = {1 : 1 , 2 : 1 , 4 : 1 , 5 : 1 , 8 : 1 , 9 : 1 , 10 : 1 }
232
-
233
- if env .PYBEHAVIOR .docstring_only_function :
234
- # 3.7 changed how functions with only docstrings are numbered.
235
- expected_arcs .update (set (arcz_to_arcs ("-46 6-4" )))
236
- expected_exits .update ({6 : 1 })
237
-
238
- if env .PYBEHAVIOR .trace_decorator_line_again :
239
- expected_arcs .update (set (arcz_to_arcs ("54 98" )))
240
- expected_exits .update ({9 : 2 , 5 : 2 })
241
266
242
- assert expected_statements == parser .statements
243
- assert expected_arcs == parser .arcs ()
244
- assert expected_exits == parser .exit_counts ()
245
-
246
- def test_fuzzed_double_parse (self ) -> None :
247
- # https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=50381
248
- # The second parse used to raise `TypeError: 'NoneType' object is not iterable`
249
- msg = (
250
- r"(EOF in multi-line statement)" # before 3.12.0b1
251
- + r"|(unmatched ']')" # after 3.12.0b1
252
- )
253
- with pytest .raises (NotPython , match = msg ):
254
- self .parse_source ("]" )
255
- with pytest .raises (NotPython , match = msg ):
256
- self .parse_source ("]" )
257
-
258
-
259
- class ParserMissingArcDescriptionTest (CoverageTest ):
267
+ class ParserMissingArcDescriptionTest (PythonParserTestBase ):
260
268
"""Tests for PythonParser.missing_arc_description."""
261
269
262
- run_in_temp_dir = False
263
-
264
- def parse_text (self , source : str ) -> PythonParser :
265
- """Parse Python source, and return the parser object."""
266
- parser = PythonParser (text = textwrap .dedent (source ))
267
- parser .parse_source ()
268
- return parser
269
-
270
270
def test_missing_arc_description (self ) -> None :
271
271
# This code is never run, so the actual values don't matter.
272
272
parser = self .parse_text ("""\
0 commit comments