diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2021-11-10 21:53:06 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2021-11-10 21:53:06 -0500 |
commit | 0387bbaa5e9ab2ad834311249802695ebb0c34ca (patch) | |
tree | 81eba7ac0b1fe21c4ad3230efcc9a97fe920e606 | |
parent | d1d60a72cd60b2472d02cdbbcb487e31f25fe32a (diff) | |
download | python-coveragepy-git-0387bbaa5e9ab2ad834311249802695ebb0c34ca.tar.gz |
fix: colons in decorators shouldn't stop an exclusion
-rw-r--r-- | CHANGES.rst | 3 | ||||
-rw-r--r-- | coverage/parser.py | 9 | ||||
-rw-r--r-- | tests/test_parser.py | 22 |
3 files changed, 30 insertions, 4 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 15c81aaf..0b6e2490 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -22,7 +22,8 @@ This list is detailed and covers changes in each pre-release version. Unreleased ---------- -Nothing yet. +- Fix: A colon in a decorator expression would cause an exclusion to end too + early, preventing the exclusion of the decorated function. This is now fixed. .. _changes_612: diff --git a/coverage/parser.py b/coverage/parser.py index ec788c06..b47fd12e 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -67,7 +67,7 @@ class PythonParser: # The raw line numbers of excluded lines of code, as marked by pragmas. self.raw_excluded = set() - # The line numbers of class and function definitions. + # The line numbers of class definitions. self.raw_classdefs = set() # The line numbers of docstring lines. @@ -120,6 +120,7 @@ class PythonParser: first_line = None empty = True first_on_line = True + nesting = 0 tokgen = generate_tokens(self.text) for toktype, ttext, (slineno, _), (elineno, _), ltext in tokgen: @@ -139,7 +140,7 @@ class PythonParser: # lines with the 'class' keyword. self.raw_classdefs.add(slineno) elif toktype == token.OP: - if ttext == ':': + if ttext == ':' and nesting == 0: should_exclude = (elineno in self.raw_excluded) or excluding_decorators if not excluding and should_exclude: # Start excluding a suite. We trigger off of the colon @@ -155,6 +156,10 @@ class PythonParser: excluding_decorators = True if excluding_decorators: self.raw_excluded.add(elineno) + elif ttext in "([{": + nesting += 1 + elif ttext in ")]}": + nesting -= 1 elif toktype == token.STRING and prev_toktype == token.INDENT: # Strings that are first on an indented line are docstrings. # (a trick from trace.py in the stdlib.) This works for diff --git a/tests/test_parser.py b/tests/test_parser.py index d5f43197..303f2b55 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -174,10 +174,30 @@ class PythonParserTest(CoverageTest): """) raw_statements = {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]) + raw_statements.update({11, 19}) assert parser.raw_statements == raw_statements assert parser.statements == {8} + def test_decorator_pragmas_with_colons(self): + # A colon in a decorator expression would confuse the parser, + # ending the exclusion of the decorated function. + parser = self.parse_source("""\ + @decorate(X) # nocover + @decorate("Hello"[2]) + def f(): + x = 4 + + @decorate(X) # nocover + @decorate("Hello"[:7]) + def g(): + x = 9 + """) + raw_statements = {1, 2, 4, 6, 7, 9} + if env.PYBEHAVIOR.trace_decorated_def: + raw_statements.update({3, 8}) + assert parser.raw_statements == raw_statements + assert parser.statements == set() + def test_class_decorator_pragmas(self): parser = self.parse_source("""\ class Foo(object): |