summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2021-01-02 10:38:56 -0500
committerNed Batchelder <ned@nedbatchelder.com>2021-01-10 09:46:18 -0500
commite3f8053d805f8930ffc8424ac098576457c5f506 (patch)
treee34badb9cdbe80b7f9360efd8fe75f266ea04708
parent3adae9b5cdf67f7364607e3ca7307fa6ebbe1b08 (diff)
downloadpython-coveragepy-git-e3f8053d805f8930ffc8424ac098576457c5f506.tar.gz
PEP 626: constant tests are kept as no-ops
The conditionals are now getting unwieldy, perhaps we can simplify them in the future?
-rw-r--r--coverage/env.py20
-rw-r--r--coverage/parser.py7
-rw-r--r--setup.cfg2
-rw-r--r--tests/coveragetest.py2
-rw-r--r--tests/test_arcs.py141
-rw-r--r--tests/test_coverage.py19
6 files changed, 155 insertions, 36 deletions
diff --git a/coverage/env.py b/coverage/env.py
index b91af463..e9c65bb1 100644
--- a/coverage/env.py
+++ b/coverage/env.py
@@ -33,14 +33,27 @@ PYPY3 = PYPY and PY3
class PYBEHAVIOR(object):
"""Flags indicating this Python's behavior."""
+ pep626 = CPYTHON and (PYVERSION > (3, 10, 0, 'alpha', 4))
+
# Is "if __debug__" optimized away?
- optimize_if_debug = (not PYPY)
+ if PYPY3:
+ optimize_if_debug = True
+ elif PYPY2:
+ optimize_if_debug = False
+ else:
+ optimize_if_debug = not pep626
# Is "if not __debug__" optimized away?
optimize_if_not_debug = (not PYPY) and (PYVERSION >= (3, 7, 0, 'alpha', 4))
+ if pep626:
+ optimize_if_not_debug = False
+ if PYPY3:
+ optimize_if_not_debug = True
# Is "if not __debug__" optimized away even better?
optimize_if_not_debug2 = (not PYPY) and (PYVERSION >= (3, 8, 0, 'beta', 1))
+ if pep626:
+ optimize_if_not_debug2 = False
# Do we have yield-from?
yield_from = (PYVERSION >= (3, 3))
@@ -67,7 +80,7 @@ class PYBEHAVIOR(object):
# used to be an empty string (meaning the current directory). It changed
# to be the actual path to the current directory, so that os.chdir wouldn't
# affect the outcome.
- actual_syspath0_dash_m = (not PYPY) and (PYVERSION >= (3, 7, 0, 'beta', 3))
+ actual_syspath0_dash_m = CPYTHON and (PYVERSION >= (3, 7, 0, 'beta', 3))
# When a break/continue/return statement in a try block jumps to a finally
# block, does the finally block do the break/continue/return (pre-3.8), or
@@ -97,6 +110,9 @@ class PYBEHAVIOR(object):
# real line of code. Now they always start at 1.
module_firstline_1 = pep626
+ # Are "if 0:" lines (and similar) kept in the compiled code?
+ keep_constant_test = pep626
+
# Coverage.py specifics.
# Are we using the C-implemented trace function?
diff --git a/coverage/parser.py b/coverage/parser.py
index 6a3ca2fc..1e307c41 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -1118,9 +1118,14 @@ class AstArcAnalyzer(object):
@contract(returns='ArcStarts')
def _handle__While(self, node):
- constant_test = self.is_constant_expr(node.test)
start = to_top = self.line_for_node(node.test)
+ constant_test = self.is_constant_expr(node.test)
+ top_is_body0 = False
if constant_test and (env.PY3 or constant_test == "Num"):
+ top_is_body0 = True
+ if env.PYBEHAVIOR.keep_constant_test:
+ top_is_body0 = False
+ if top_is_body0:
to_top = self.line_for_node(node.body[0])
self.block_stack.append(LoopBlock(start=to_top))
from_start = ArcStart(start, cause="the condition on line {lineno} was never true")
diff --git a/setup.cfg b/setup.cfg
index 16e2bc6c..2d015d95 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,6 +6,8 @@ markers =
filterwarnings =
ignore:dns.hash module will be removed:DeprecationWarning
ignore:Using or importing the ABCs:DeprecationWarning
+# xfail tests that pass should fail the test suite
+xfail_strict=true
[pep8]
# E265 block comment should start with '# '
diff --git a/tests/coveragetest.py b/tests/coveragetest.py
index b2763b04..dbadd226 100644
--- a/tests/coveragetest.py
+++ b/tests/coveragetest.py
@@ -509,5 +509,5 @@ def command_line(args):
def xfail(condition, reason):
- """A decorator to mark as test as expected to fail."""
+ """A decorator to mark a test as expected to fail."""
return pytest.mark.xfail(condition, reason=reason, strict=True)
diff --git a/tests/test_arcs.py b/tests/test_arcs.py
index b927526b..fb958a66 100644
--- a/tests/test_arcs.py
+++ b/tests/test_arcs.py
@@ -3,7 +3,9 @@
"""Tests for coverage.py's arc measurement."""
-from tests.coveragetest import CoverageTest
+import pytest
+
+from tests.coveragetest import CoverageTest, xfail
import coverage
from coverage import env
@@ -264,12 +266,17 @@ class LoopArcTest(CoverageTest):
""",
arcz=".1 12 23 34 45 25 56 51 67 17 7.", arcz_missing="17 25")
- def test_while_true(self):
+ def test_while_1(self):
# With "while 1", the loop knows it's constant.
- if env.PYBEHAVIOR.nix_while_true:
+ if env.PYBEHAVIOR.keep_constant_test:
+ arcz = ".1 12 23 34 45 36 62 57 7."
+ arcz_missing = ""
+ elif env.PYBEHAVIOR.nix_while_true:
arcz = ".1 13 34 45 36 63 57 7."
+ arcz_missing = ""
else:
arcz = ".1 12 23 34 45 36 63 57 7."
+ arcz_missing = ""
self.check_coverage("""\
a, i = 1, 0
while 1:
@@ -280,10 +287,15 @@ class LoopArcTest(CoverageTest):
assert a == 4 and i == 3
""",
arcz=arcz,
+ arcz_missing=arcz_missing,
)
+
+ def test_while_true(self):
# With "while True", 2.x thinks it's computation,
# 3.x thinks it's constant.
- if env.PYBEHAVIOR.nix_while_true:
+ if env.PYBEHAVIOR.keep_constant_test:
+ arcz = ".1 12 23 34 45 36 62 57 7."
+ elif env.PYBEHAVIOR.nix_while_true:
arcz = ".1 13 34 45 36 63 57 7."
elif env.PY3:
arcz = ".1 12 23 34 45 36 63 57 7."
@@ -311,7 +323,9 @@ class LoopArcTest(CoverageTest):
""")
out = self.run_command("coverage run --branch --source=. main.py")
self.assertEqual(out, 'done\n')
- if env.PYBEHAVIOR.nix_while_true:
+ if env.PYBEHAVIOR.keep_constant_test:
+ num_stmts = 3
+ elif env.PYBEHAVIOR.nix_while_true:
num_stmts = 2
else:
num_stmts = 3
@@ -323,7 +337,9 @@ class LoopArcTest(CoverageTest):
def test_bug_496_continue_in_constant_while(self):
# https://github.com/nedbat/coveragepy/issues/496
# A continue in a while-true needs to jump to the right place.
- if env.PYBEHAVIOR.nix_while_true:
+ if env.PYBEHAVIOR.keep_constant_test:
+ arcz = ".1 12 23 34 45 52 46 67 7."
+ elif env.PYBEHAVIOR.nix_while_true:
arcz = ".1 13 34 45 53 46 67 7."
elif env.PY3:
arcz = ".1 12 23 34 45 53 46 67 7."
@@ -1111,38 +1127,75 @@ class OptimizedIfTest(CoverageTest):
"""Tests of if statements being optimized away."""
def test_optimized_away_if_0(self):
+ if env.PYBEHAVIOR.keep_constant_test:
+ lines = [1, 2, 3, 4, 8, 9]
+ arcz = ".1 12 23 24 34 48 49 89 9."
+ arcz_missing = "24"
+ # 49 isn't missing because line 4 is matched by the default partial
+ # exclusion regex, and no branches are considered missing if they
+ # start from an excluded line.
+ else:
+ lines = [1, 2, 3, 8, 9]
+ arcz = ".1 12 23 28 38 89 9."
+ arcz_missing = "28"
+
self.check_coverage("""\
a = 1
if len([2]):
c = 3
- if 0: # this line isn't in the compiled code.
+ if 0:
if len([5]):
d = 6
else:
e = 8
f = 9
""",
- lines=[1, 2, 3, 8, 9],
- arcz=".1 12 23 28 38 89 9.",
- arcz_missing="28",
+ lines=lines,
+ arcz=arcz,
+ arcz_missing=arcz_missing,
)
def test_optimized_away_if_1(self):
+ if env.PYBEHAVIOR.keep_constant_test:
+ lines = [1, 2, 3, 4, 5, 6, 9]
+ arcz = ".1 12 23 24 34 45 49 56 69 59 9."
+ arcz_missing = "24 59"
+ # 49 isn't missing because line 4 is matched by the default partial
+ # exclusion regex, and no branches are considered missing if they
+ # start from an excluded line.
+ else:
+ lines = [1, 2, 3, 5, 6, 9]
+ arcz = ".1 12 23 25 35 56 69 59 9."
+ arcz_missing = "25 59"
+
self.check_coverage("""\
a = 1
if len([2]):
c = 3
- if 1: # this line isn't in the compiled code,
- if len([5]): # but these are.
+ if 1:
+ if len([5]):
d = 6
else:
e = 8
f = 9
""",
- lines=[1, 2, 3, 5, 6, 9],
- arcz=".1 12 23 25 35 56 69 59 9.",
- arcz_missing="25 59",
+ lines=lines,
+ arcz=arcz,
+ arcz_missing=arcz_missing,
)
+
+ def test_optimized_away_if_1_no_else(self):
+ if env.PYBEHAVIOR.keep_constant_test:
+ lines = [1, 2, 3, 4, 5]
+ arcz = ".1 12 23 25 34 45 5."
+ arcz_missing = ""
+ # 25 isn't missing because line 2 is matched by the default partial
+ # exclusion regex, and no branches are considered missing if they
+ # start from an excluded line.
+ else:
+ lines = [1, 3, 4, 5]
+ arcz = ".1 13 34 45 5."
+ arcz_missing = ""
self.check_coverage("""\
a = 1
if 1:
@@ -1150,11 +1203,24 @@ class OptimizedIfTest(CoverageTest):
c = 4
d = 5
""",
- lines=[1, 3, 4, 5],
- arcz=".1 13 34 45 5.",
+ lines=lines,
+ arcz=arcz,
+ arcz_missing=arcz_missing,
)
- def test_optimized_nested(self):
+ def test_optimized_if_nested(self):
+ if env.PYBEHAVIOR.keep_constant_test:
+ lines = [1, 2, 8, 11, 12, 13, 14, 15]
+ arcz = ".1 12 28 2F 8B 8F BC CD DE EF F."
+ arcz_missing = ""
+ # 2F and 8F aren't missing because they're matched by the default
+ # partial exclusion regex, and no branches are considered missing
+ # if they start from an excluded line.
+ else:
+ lines = [1, 12, 14, 15]
+ arcz = ".1 1C CE EF F."
+ arcz_missing = ""
+
self.check_coverage("""\
a = 1
if 0:
@@ -1172,14 +1238,34 @@ class OptimizedIfTest(CoverageTest):
h = 14
i = 15
""",
- lines=[1, 12, 14, 15],
- arcz=".1 1C CE EF F.",
+ lines=lines,
+ arcz=arcz,
+ arcz_missing=arcz_missing,
)
+ def test_dunder_debug(self):
+ # Since some of our tests use __debug__, let's make sure it is true as
+ # we expect
+ assert __debug__
+ # Check that executed code has __debug__
+ self.check_coverage("""\
+ assert __debug__, "assert __debug__"
+ """
+ )
+ # Check that if it didn't have debug, it would let us know.
+ with pytest.raises(AssertionError):
+ self.check_coverage("""\
+ assert not __debug__, "assert not __debug__"
+ """
+ )
+
def test_if_debug(self):
- if not env.PYBEHAVIOR.optimize_if_debug:
- self.skipTest("PyPy doesn't optimize away 'if __debug__:'")
- # CPython optimizes away "if __debug__:"
+ if env.PYBEHAVIOR.optimize_if_debug:
+ arcz = ".1 12 24 41 26 61 1."
+ arcz_missing = ""
+ else:
+ arcz = ".1 12 23 31 34 41 26 61 1."
+ arcz_missing = "31"
self.check_coverage("""\
for value in [True, False]:
if value:
@@ -1188,14 +1274,13 @@ class OptimizedIfTest(CoverageTest):
else:
x = 6
""",
- arcz=".1 12 24 41 26 61 1.",
+ arcz=arcz,
+ arcz_missing=arcz_missing,
)
+ @xfail(env.PYBEHAVIOR.pep626, reason="https://bugs.python.org/issue42803")
def test_if_not_debug(self):
- # Before 3.7, no Python optimized away "if not __debug__:"
- if not env.PYBEHAVIOR.optimize_if_debug:
- self.skipTest("PyPy doesn't optimize away 'if __debug__:'")
- elif env.PYBEHAVIOR.optimize_if_not_debug2:
+ if env.PYBEHAVIOR.optimize_if_not_debug2:
arcz = ".1 12 24 41 26 61 1."
arcz_missing = ""
elif env.PYBEHAVIOR.optimize_if_not_debug:
diff --git a/tests/test_coverage.py b/tests/test_coverage.py
index b56d3b59..a6c8f492 100644
--- a/tests/test_coverage.py
+++ b/tests/test_coverage.py
@@ -8,7 +8,7 @@ import coverage
from coverage import env
from coverage.misc import CoverageException
-from tests.coveragetest import CoverageTest
+from tests.coveragetest import CoverageTest, xfail
class TestCoverageTest(CoverageTest):
@@ -622,7 +622,9 @@ class SimpleStatementTest(CoverageTest):
b = 3
assert (a,b) == (1,3)
""",
- [1,3,4], "")
+ ([1,3,4], [1,2,3,4]),
+ "",
+ )
self.check_coverage("""\
a = 1
"An extra docstring, should be a comment."
@@ -632,7 +634,9 @@ class SimpleStatementTest(CoverageTest):
c = 6
assert (a,b,c) == (1,3,6)
""",
- ([1,3,6,7], [1,3,5,6,7], [1,3,4,5,6,7]), "")
+ ([1,3,6,7], [1,3,5,6,7], [1,3,4,5,6,7], [1,2,3,4,5,6,7]),
+ "",
+ )
def test_nonascii(self):
self.check_coverage("""\
@@ -675,6 +679,7 @@ class CompoundStatementTest(CoverageTest):
""",
[1,2,3,5], "")
+ @xfail(env.PYBEHAVIOR.pep626, reason="pep626: https://bugs.python.org/issue42810")
def test_if(self):
self.check_coverage("""\
a = 1
@@ -926,12 +931,18 @@ class CompoundStatementTest(CoverageTest):
[1,2,4,5,7,9,10], "4, 7")
def test_constant_if(self):
+ if env.PYBEHAVIOR.keep_constant_test:
+ lines = [1, 2, 3]
+ else:
+ lines = [2, 3]
self.check_coverage("""\
if 1:
a = 2
assert a == 2
""",
- [2,3], "")
+ lines,
+ "",
+ )
def test_while(self):
self.check_coverage("""\