summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNed Batchelder <ned@nedbatchelder.com>2018-11-09 07:03:25 -0500
committerNed Batchelder <ned@nedbatchelder.com>2018-11-12 06:50:37 -0500
commit24c5d84084622669739e285ecb893dfa0d7e4cc0 (patch)
tree4812a140e68a03e4fb6582844bcdd26e1856b7bf
parent50e1a681905aacefda8a2897345a4a739d2416d8 (diff)
downloadpython-coveragepy-git-24c5d84084622669739e285ecb893dfa0d7e4cc0.tar.gz
Python 3.8 will optimize away "while True:"
(cherry picked from commit e5dcb933ab791206040a849eacd726ffe40c348a)
-rw-r--r--coverage/env.py2
-rw-r--r--coverage/parser.py21
-rw-r--r--tests/test_arcs.py37
-rw-r--r--tests/test_concurrency.py2
4 files changed, 52 insertions, 10 deletions
diff --git a/coverage/env.py b/coverage/env.py
index 72099cc9..9f90c414 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 6e81cf97..a2839cf2 100644
--- a/coverage/parser.py
+++ b/coverage/parser.py
@@ -709,6 +709,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.
@@ -736,6 +743,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 c49f819d..fd9ec3a1 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
@@ -689,10 +704,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':
@@ -705,8 +722,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 71006042..578cc679 100644
--- a/tests/test_concurrency.py
+++ b/tests/test_concurrency.py
@@ -114,7 +114,7 @@ SUM_RANGE_Q = """
def run(self):
sum = 0
- while True:
+ while "no peephole".upper():
i = self.q.get()
if i is None:
break