diff options
-rw-r--r-- | coverage/env.py | 6 | ||||
-rw-r--r-- | coverage/parser.py | 31 | ||||
-rw-r--r-- | tests/test_coverage.py | 16 | ||||
-rw-r--r-- | tests/test_parser.py | 17 |
4 files changed, 56 insertions, 14 deletions
diff --git a/coverage/env.py b/coverage/env.py index aa8bb8f6..fd98fa2b 100644 --- a/coverage/env.py +++ b/coverage/env.py @@ -34,6 +34,12 @@ class PYBEHAVIOR(object): # work? finally_jumps_back = (PYVERSION >= (3, 8)) + # When a function is decorated, does the trace function get called for the + # @-line and also the def-line (new behavior in 3.8)? Or just the @-line + # (old behavior)? + trace_decorated_def = (PYVERSION >= (3, 8)) + + # Coverage.py specifics. # Are we using the C-implemented trace function? diff --git a/coverage/parser.py b/coverage/parser.py index 5ffcad8c..6faa36e2 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -579,9 +579,19 @@ class AstArcAnalyzer(object): else: return node.lineno + def _line_decorated(self, node): + """Compute first line number for things that can be decorated (classes and functions).""" + lineno = node.lineno + if env.PYBEHAVIOR.trace_decorated_def: + if node.decorator_list: + lineno = node.decorator_list[0].lineno + return lineno + def _line__Assign(self, node): return self.line_for_node(node.value) + _line__ClassDef = _line_decorated + def _line__Dict(self, node): # Python 3.5 changed how dict literals are made. if env.PYVERSION >= (3, 5) and node.keys: @@ -594,6 +604,8 @@ class AstArcAnalyzer(object): else: return node.lineno + _line__FunctionDef = _line_decorated + def _line__List(self, node): if node.elts: return self.line_for_node(node.elts[0]) @@ -812,10 +824,10 @@ class AstArcAnalyzer(object): # Handlers: _handle__* # # Each handler deals with a specific AST node type, dispatched from - # add_arcs. Each deals with a particular kind of node type, and returns - # the set of exits from that node. These functions mirror the Python - # semantics of each syntactic construct. See the docstring for add_arcs to - # understand the concept of exits from a node. + # add_arcs. Handlers return the set of exits from that node, and can + # also call self.add_arc to record arcs they find. These functions mirror + # the Python semantics of each syntactic construct. See the docstring + # for add_arcs to understand the concept of exits from a node. @contract(returns='ArcStarts') def _handle__Break(self, node): @@ -827,13 +839,18 @@ class AstArcAnalyzer(object): @contract(returns='ArcStarts') def _handle_decorated(self, node): """Add arcs for things that can be decorated (classes and functions).""" - last = self.line_for_node(node) + main_line = last = node.lineno if node.decorator_list: + if env.PYBEHAVIOR.trace_decorated_def: + last = None for dec_node in node.decorator_list: dec_start = self.line_for_node(dec_node) - if dec_start != last: + if last is not None and dec_start != last: self.add_arc(last, dec_start) - last = dec_start + last = dec_start + if env.PYBEHAVIOR.trace_decorated_def: + self.add_arc(last, main_line) + last = main_line # The definition line may have been missed, but we should have it # in `self.statements`. For some constructs, `line_for_node` is # not what we'd think of as the first line in the statement, so map diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 676fc831..60205900 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -1579,6 +1579,9 @@ class Py24Test(CoverageTest): """Tests of new syntax in Python 2.4.""" def test_function_decorators(self): + lines = [1, 2, 3, 4, 6, 8, 10, 12] + if env.PYBEHAVIOR.trace_decorated_def: + lines = sorted(lines + [9]) self.check_coverage("""\ def require_int(func): def wrapper(arg): @@ -1593,9 +1596,12 @@ class Py24Test(CoverageTest): assert p1(10) == 20 """, - [1,2,3,4,6,8,10,12], "") + lines, "") def test_function_decorators_with_args(self): + lines = [1, 2, 3, 4, 5, 6, 8, 10, 12] + if env.PYBEHAVIOR.trace_decorated_def: + lines = sorted(lines + [9]) self.check_coverage("""\ def boost_by(extra): def decorator(func): @@ -1610,9 +1616,12 @@ class Py24Test(CoverageTest): assert boosted(10) == 200 """, - [1,2,3,4,5,6,8,10,12], "") + lines, "") def test_double_function_decorators(self): + lines = [1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 14, 15, 17, 19, 21, 22, 24, 26] + if env.PYBEHAVIOR.trace_decorated_def: + lines = sorted(lines + [16, 23]) self.check_coverage("""\ def require_int(func): def wrapper(arg): @@ -1641,8 +1650,7 @@ class Py24Test(CoverageTest): assert boosted2(10) == 200 """, - ([1,2,3,4,5,7,8,9,10,11,12,14,15,17,19,21,22,24,26], - [1,2,3,4,5,7,8,9,10,11,12,14, 17,19,21, 24,26]), "") + lines, "") class Py25Test(CoverageTest): diff --git a/tests/test_parser.py b/tests/test_parser.py index 6340a44b..e6768a22 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -166,6 +166,8 @@ class PythonParserTest(CoverageTest): return 26 """) raw_statements = set([3, 4, 5, 6, 8, 9, 10, 13, 15, 16, 17, 20, 22, 23, 25, 26]) + if env.PYBEHAVIOR.trace_decorated_def: + raw_statements.update([11, 19, 25]) self.assertEqual(parser.raw_statements, raw_statements) self.assertEqual(parser.statements, set([8])) @@ -196,13 +198,22 @@ class PythonParserTest(CoverageTest): def bar(self): pass """) - self.assertEqual(parser.statements, set([1, 2, 4, 8, 10])) - expected_arcs = set(self.arcz_to_arcs(".1 14 48 8. .2 2. -8A A-8")) - expected_exits = {1: 1, 2: 1, 4: 1, 8: 1, 10: 1} + + if env.PYBEHAVIOR.trace_decorated_def: + expected_statements = {1, 2, 4, 5, 8, 9, 10} + expected_arcs = set(self.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} + else: + expected_statements = {1, 2, 4, 8, 10} + expected_arcs = set(self.arcz_to_arcs(".1 14 48 8. .2 2. -8A A-8")) + expected_exits = {1: 1, 2: 1, 4: 1, 8: 1, 10: 1} + if env.PYVERSION >= (3, 7, 0, 'beta', 5): # 3.7 changed how functions with only docstrings are numbered. expected_arcs.update(set(self.arcz_to_arcs("-46 6-4"))) expected_exits.update({6: 1}) + + self.assertEqual(parser.statements, expected_statements) self.assertEqual(parser.arcs(), expected_arcs) self.assertEqual(parser.exit_counts(), expected_exits) |