bpo-42645: Make sure that return/break/continue are only traced once when exiting via a finally block. (GH-23780)
* Make sure that return/break/continue are only traced once when exiting via a finally block.
* Add test for return in try-finally.
* Update importlib
diff --git a/Lib/test/test_dis.py b/Lib/test/test_dis.py
index eb93170..f279f75 100644
--- a/Lib/test/test_dis.py
+++ b/Lib/test/test_dis.py
@@ -366,18 +366,14 @@ def _tryfinallyconst(b):
%3d 6 LOAD_FAST 1 (b)
8 CALL_FUNCTION 0
10 POP_TOP
-
-%3d 12 RETURN_VALUE
-
-%3d >> 14 LOAD_FAST 1 (b)
+ 12 RETURN_VALUE
+ >> 14 LOAD_FAST 1 (b)
16 CALL_FUNCTION 0
18 POP_TOP
20 RERAISE
""" % (_tryfinally.__code__.co_firstlineno + 1,
_tryfinally.__code__.co_firstlineno + 2,
_tryfinally.__code__.co_firstlineno + 4,
- _tryfinally.__code__.co_firstlineno + 2,
- _tryfinally.__code__.co_firstlineno + 4,
)
dis_tryfinallyconst = """\
@@ -388,19 +384,15 @@ def _tryfinallyconst(b):
%3d 4 LOAD_FAST 0 (b)
6 CALL_FUNCTION 0
8 POP_TOP
-
-%3d 10 LOAD_CONST 1 (1)
+ 10 LOAD_CONST 1 (1)
12 RETURN_VALUE
-
-%3d >> 14 LOAD_FAST 0 (b)
+ >> 14 LOAD_FAST 0 (b)
16 CALL_FUNCTION 0
18 POP_TOP
20 RERAISE
""" % (_tryfinallyconst.__code__.co_firstlineno + 1,
_tryfinallyconst.__code__.co_firstlineno + 2,
_tryfinallyconst.__code__.co_firstlineno + 4,
- _tryfinallyconst.__code__.co_firstlineno + 2,
- _tryfinallyconst.__code__.co_firstlineno + 4,
)
def _g(x):
diff --git a/Lib/test/test_sys_settrace.py b/Lib/test/test_sys_settrace.py
index a842139..3bfc993 100644
--- a/Lib/test/test_sys_settrace.py
+++ b/Lib/test/test_sys_settrace.py
@@ -678,6 +678,119 @@ def func():
(4, 'line'),
(4, 'return')])
+ def test_if_break(self):
+
+ def func():
+ seq = [1, 0]
+ while seq:
+ n = seq.pop()
+ if n:
+ break # line 5
+ else:
+ n = 99
+ return n # line 8
+
+ self.run_and_compare(func,
+ [(0, 'call'),
+ (1, 'line'),
+ (2, 'line'),
+ (3, 'line'),
+ (4, 'line'),
+ (2, 'line'),
+ (3, 'line'),
+ (4, 'line'),
+ (5, 'line'),
+ (8, 'line'),
+ (8, 'return')])
+
+ def test_break_through_finally(self):
+
+ def func():
+ a, c, d, i = 1, 1, 1, 99
+ try:
+ for i in range(3):
+ try:
+ a = 5
+ if i > 0:
+ break # line 7
+ a = 8
+ finally:
+ c = 10
+ except:
+ d = 12 # line 12
+ assert a == 5 and c == 10 and d == 1 # line 13
+
+ self.run_and_compare(func,
+ [(0, 'call'),
+ (1, 'line'),
+ (2, 'line'),
+ (3, 'line'),
+ (4, 'line'),
+ (5, 'line'),
+ (6, 'line'),
+ (8, 'line'),
+ (10, 'line'),
+ (3, 'line'),
+ (4, 'line'),
+ (5, 'line'),
+ (6, 'line'),
+ (7, 'line'),
+ (10, 'line'),
+ (13, 'line'),
+ (13, 'return')])
+
+ def test_continue_through_finally(self):
+
+ def func():
+ a, b, c, d, i = 1, 1, 1, 1, 99
+ try:
+ for i in range(2):
+ try:
+ a = 5
+ if i > 0:
+ continue # line 7
+ b = 8
+ finally:
+ c = 10
+ except:
+ d = 12 # line 12
+ assert (a, b, c, d) == (5, 8, 10, 1) # line 13
+
+ self.run_and_compare(func,
+ [(0, 'call'),
+ (1, 'line'),
+ (2, 'line'),
+ (3, 'line'),
+ (4, 'line'),
+ (5, 'line'),
+ (6, 'line'),
+ (8, 'line'),
+ (10, 'line'),
+ (3, 'line'),
+ (4, 'line'),
+ (5, 'line'),
+ (6, 'line'),
+ (7, 'line'),
+ (10, 'line'),
+ (3, 'line'),
+ (13, 'line'),
+ (13, 'return')])
+
+ def test_return_through_finally(self):
+
+ def func():
+ try:
+ return 2
+ finally:
+ 4
+
+ self.run_and_compare(func,
+ [(0, 'call'),
+ (1, 'line'),
+ (2, 'line'),
+ (4, 'line'),
+ (4, 'return')])
+
class SkipLineEventsTraceTestCase(TraceTestCase):
"""Repeat the trace tests, but with per-line events skipped"""