diff options
author | Ned Batchelder <ned@nedbatchelder.com> | 2018-11-09 07:03:25 -0500 |
---|---|---|
committer | Ned Batchelder <ned@nedbatchelder.com> | 2018-11-11 16:45:33 -0500 |
commit | e5dcb933ab791206040a849eacd726ffe40c348a (patch) | |
tree | 81ea6018218db5436766f1d46ff999351e33b82c | |
parent | cc12f4b7c40347b7297f7f6d938150bfde8c9ed5 (diff) | |
download | python-coveragepy-git-e5dcb933ab791206040a849eacd726ffe40c348a.tar.gz |
Python 3.8 will optimize away "while True:"
-rw-r--r-- | coverage/env.py | 2 | ||||
-rw-r--r-- | coverage/parser.py | 21 | ||||
-rw-r--r-- | tests/test_arcs.py | 37 | ||||
-rw-r--r-- | tests/test_concurrency.py | 2 |
4 files changed, 52 insertions, 10 deletions
diff --git a/coverage/env.py b/coverage/env.py index fd98fa2b..d97b193c 100644 --- a/coverage/env.py +++ b/coverage/env.py @@ -39,6 +39,8 @@ class PYBEHAVIOR(object): # (old behavior)? trace_decorated_def = (PYVERSION >= (3, 8)) + # Are while-true loops optimized into absolute jumps with no loop setup? + nix_while_true = (PYVERSION >= (3, 8)) # Coverage.py specifics. diff --git a/coverage/parser.py b/coverage/parser.py index 6ae81c19..1c19f69e 100644 --- a/coverage/parser.py +++ b/coverage/parser.py @@ -711,6 +711,13 @@ class AstArcAnalyzer(object): node = None return node + # Missing nodes: _missing__* + # + # Entire statements can be optimized away by Python. They will appear in + # the AST, but not the bytecode. These functions are called (by + # find_non_missing_node) to find a node to use instead of the missing + # node. They can return None if the node should truly be gone. + def _missing__If(self, node): # If the if-node is missing, then one of its children might still be # here, but not both. So return the first of the two that isn't missing. @@ -738,6 +745,20 @@ class AstArcAnalyzer(object): return non_missing_children[0] return NodeList(non_missing_children) + def _missing__While(self, node): + body_nodes = self.find_non_missing_node(NodeList(node.body)) + if not body_nodes: + return None + # Make a synthetic While-true node. + new_while = ast.While() + new_while.lineno = body_nodes.lineno + new_while.test = ast.Name() + new_while.test.lineno = body_nodes.lineno + new_while.test.id = "True" + new_while.body = body_nodes.body + new_while.orelse = None + return new_while + def is_constant_expr(self, node): """Is this a compile-time constant?""" node_name = node.__class__.__name__ diff --git a/tests/test_arcs.py b/tests/test_arcs.py index 0cf4dd3b..324bd53f 100644 --- a/tests/test_arcs.py +++ b/tests/test_arcs.py @@ -248,6 +248,10 @@ class LoopArcTest(CoverageTest): def test_while_true(self): # With "while 1", the loop knows it's constant. + if env.PYBEHAVIOR.nix_while_true: + arcz = ".1 13 34 45 36 63 57 7." + else: + arcz = ".1 12 23 34 45 36 63 57 7." self.check_coverage("""\ a, i = 1, 0 while 1: @@ -257,11 +261,13 @@ class LoopArcTest(CoverageTest): i += 1 assert a == 4 and i == 3 """, - arcz=".1 12 23 34 45 36 63 57 7.", + arcz=arcz, ) # With "while True", 2.x thinks it's computation, # 3.x thinks it's constant. - if env.PY3: + if 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." else: arcz = ".1 12 23 34 45 36 62 57 7." @@ -287,22 +293,31 @@ class LoopArcTest(CoverageTest): """) out = self.run_command("coverage run --branch --source=. main.py") self.assertEqual(out, 'done\n') + if env.PYBEHAVIOR.nix_while_true: + num_stmts = 2 + else: + num_stmts = 3 + expected = "zero.py {n} {n} 0 0 0% 1-3".format(n=num_stmts) report = self.report_from_command("coverage report -m") squeezed = self.squeezed_lines(report) - self.assertIn("zero.py 3 3 0 0 0% 1-3", squeezed[3]) + self.assertIn(expected, squeezed[3]) def test_bug_496_continue_in_constant_while(self): # https://bitbucket.org/ned/coveragepy/issue/496 - if env.PY3: - arcz = ".1 12 23 34 45 53 46 6." + # A continue in a while-true needs to jump to the right place. + if 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." else: - arcz = ".1 12 23 34 45 52 46 6." + arcz = ".1 12 23 34 45 52 46 67 7." self.check_coverage("""\ up = iter('ta') while True: char = next(up) if char == 't': continue + i = "line 6" break """, arcz=arcz @@ -685,10 +700,12 @@ class ExceptionArcTest(CoverageTest): def test_bug_212(self): # "except Exception as e" is crucial here. + # Bug 212 said that the "if exc" line was incorrectly marked as only + # partially covered. self.check_coverage("""\ def b(exc): try: - while 1: + while "no peephole".upper(): raise Exception(exc) # 4 except Exception as e: if exc != 'expected': @@ -701,8 +718,10 @@ class ExceptionArcTest(CoverageTest): except: pass """, - arcz=".1 .2 1A 23 34 45 56 67 68 7. 8. AB BC C. DE E.", - arcz_missing="C.", arcz_unpredicted="CD") + arcz=".1 .2 1A 23 34 3. 45 56 67 68 7. 8. AB BC C. DE E.", + arcz_missing="3. C.", + arcz_unpredicted="CD", + ) def test_except_finally(self): self.check_coverage("""\ diff --git a/tests/test_concurrency.py b/tests/test_concurrency.py index 16d1af81..2877b7c2 100644 --- a/tests/test_concurrency.py +++ b/tests/test_concurrency.py @@ -115,7 +115,7 @@ SUM_RANGE_Q = """ def run(self): sum = 0 - while True: + while "no peephole".upper(): i = self.q.get() if i is None: break |