diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2021-01-02 10:38:56 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2021-01-10 09:46:18 -0500 |
commit | e3f8053d805f8930ffc8424ac098576457c5f506 (patch) | |
tree | e34badb9cdbe80b7f9360efd8fe75f266ea04708 | |
parent | 3adae9b5cdf67f7364607e3ca7307fa6ebbe1b08 (diff) | |
download | python-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.py | 20 | ||||
-rw-r--r-- | coverage/parser.py | 7 | ||||
-rw-r--r-- | setup.cfg | 2 | ||||
-rw-r--r-- | tests/coveragetest.py | 2 | ||||
-rw-r--r-- | tests/test_arcs.py | 141 | ||||
-rw-r--r-- | tests/test_coverage.py | 19 |
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") @@ -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("""\ |