From fd36918609a18bd36a7e2c3b208220cf25c0957d Mon Sep 17 00:00:00 2001 From: Ned Batchelder Date: Mon, 26 Sep 2022 19:21:41 -0400 Subject: [PATCH] fix: `class` statements shouldn't be branches. #1449 Revert "refactor: we no longer need to treat 'class' lines specially" This reverts commit 79f9f4575321fafc2ef770e3255f874db3d4b037. --- CHANGES.rst | 4 ++++ coverage/parser.py | 15 +++++++++++++++ lab/parser.py | 2 ++ tests/test_lcov.py | 6 ++---- tests/test_parser.py | 6 +++--- 5 files changed, 26 insertions(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 01d9f33ed..2eba01dd3 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,6 +20,9 @@ development at the same time, such as 4.5.x and 5.0. Unreleased ---------- +- Starting with coverage.py 6.2, ``class`` statements were marked as a branch. + This wasn't right, and has been reverted, fixing `issue 1449`_. + - Packaging is now compliant with `PEP 517`_, closing `issue 1395`_. - A new debug option ``--debug=pathmap`` shows details of the remapping of @@ -31,6 +34,7 @@ Unreleased .. _bug 50381: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=50381 .. _PEP 517: https://peps.python.org/pep-0517/ .. _issue 1395: https://github.com/nedbat/coveragepy/issues/1395 +.. _issue 1449: https://github.com/nedbat/coveragepy/issues/1449 .. _changes_6-4-4: diff --git a/coverage/parser.py b/coverage/parser.py index 3dbfbf308..8b2a9ac54 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -67,6 +67,9 @@ def __init__(self, text=None, filename=None, exclude=None): # The raw line numbers of excluded lines of code, as marked by pragmas. self.raw_excluded = set() + # The line numbers of class definitions. + self.raw_classdefs = set() + # The line numbers of docstring lines. self.raw_docstrings = set() @@ -130,6 +133,12 @@ def _raw_parse(self): indent += 1 elif toktype == token.DEDENT: indent -= 1 + elif toktype == token.NAME: + if ttext == 'class': + # Class definitions look like branches in the bytecode, so + # we need to exclude them. The simplest way is to note the + # lines with the 'class' keyword. + self.raw_classdefs.add(slineno) elif toktype == token.OP: if ttext == ':' and nesting == 0: should_exclude = (elineno in self.raw_excluded) or excluding_decorators @@ -292,6 +301,12 @@ def exit_counts(self): continue exit_counts[l1] += 1 + # Class definitions have one extra exit, so remove one for each: + for l in self.raw_classdefs: + # Ensure key is there: class definitions can include excluded lines. + if l in exit_counts: + exit_counts[l] -= 1 + return exit_counts def missing_arc_description(self, start, end, executed_arcs=None): diff --git a/lab/parser.py b/lab/parser.py index 43f3da52d..ebd4e7f3a 100644 --- a/lab/parser.py +++ b/lab/parser.py @@ -108,6 +108,8 @@ def one_file(self, options, filename): marks[2] = str(exits) if lineno in pyparser.raw_docstrings: marks[3] = '"' + if lineno in pyparser.raw_classdefs: + marks[3] = 'C' if lineno in pyparser.raw_excluded: marks[4] = 'x' diff --git a/tests/test_lcov.py b/tests/test_lcov.py index 09bb99aad..ed7706fb5 100644 --- a/tests/test_lcov.py +++ b/tests/test_lcov.py @@ -203,10 +203,8 @@ def test_is_it_x(self): DA:9,0,FPTWzd68bDx76HN7VHu1wA LF:6 LH:4 - BRDA:0,0,0,1 - BRDA:7,0,1,1 - BRF:2 - BRH:2 + BRF:0 + BRH:0 end_of_record """) actual_result = self.get_lcov_report_content() diff --git a/tests/test_parser.py b/tests/test_parser.py index 48f5ade3a..6d181c9eb 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -43,7 +43,7 @@ class Bar: pass """) assert parser.exit_counts() == { - 2:2, 3:1, 4:2, 5:1, 7:1, 9:2, 10:1 + 2:1, 3:1, 4:2, 5:1, 7:1, 9:1, 10:1 } def test_generator_exit_counts(self): @@ -89,7 +89,7 @@ class Bar: pass """) assert parser.exit_counts() == { - 1:1, 2:1, 3:1 + 1:0, 2:1, 3:1 } def test_missing_branch_to_excluded_code(self): @@ -472,7 +472,7 @@ def foo(self, a): class Bar: pass """ - counts = { 2:2, 3:1, 4:2, 5:1, 7:1, 9:2, 10:1 } + counts = { 2:1, 3:1, 4:2, 5:1, 7:1, 9:1, 10:1 } fname = slug + ".py" self.make_file(fname, text, newline=newline) parser = self.parse_file(fname)