Skip to content

Commit e2c2861

Browse files
authored
bpo-46709: check eval breaker in specialized CALL opcodes (GH-31404)
1 parent c3ce778 commit e2c2861

File tree

2 files changed

+94
-59
lines changed

2 files changed

+94
-59
lines changed

Lib/unittest/test/test_break.py

+85-59
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,18 @@
44
import sys
55
import signal
66
import weakref
7-
87
import unittest
98

9+
from test import support
10+
1011

1112
@unittest.skipUnless(hasattr(os, 'kill'), "Test requires os.kill")
1213
@unittest.skipIf(sys.platform =="win32", "Test cannot run on Windows")
1314
class TestBreak(unittest.TestCase):
1415
int_handler = None
16+
# This number was smart-guessed, previously tests were failing
17+
# after 7th run. So, we take `x * 2 + 1` to be sure.
18+
default_repeats = 15
1519

1620
def setUp(self):
1721
self._default_handler = signal.getsignal(signal.SIGINT)
@@ -24,6 +28,27 @@ def tearDown(self):
2428
unittest.signals._interrupt_handler = None
2529

2630

31+
def withRepeats(self, test_function, repeats=None):
32+
if not support.check_impl_detail(cpython=True):
33+
# Override repeats count on non-cpython to execute only once.
34+
# Because this test only makes sense to be repeated on CPython.
35+
repeats = 1
36+
elif repeats is None:
37+
repeats = self.default_repeats
38+
39+
for repeat in range(repeats):
40+
with self.subTest(repeat=repeat):
41+
# We don't run `setUp` for the very first repeat
42+
# and we don't run `tearDown` for the very last one,
43+
# because they are handled by the test class itself.
44+
if repeat != 0:
45+
self.setUp()
46+
try:
47+
test_function()
48+
finally:
49+
if repeat != repeats - 1:
50+
self.tearDown()
51+
2752
def testInstallHandler(self):
2853
default_handler = signal.getsignal(signal.SIGINT)
2954
unittest.installHandler()
@@ -48,35 +73,34 @@ def testRegisterResult(self):
4873
unittest.removeResult(result)
4974

5075
def testInterruptCaught(self):
51-
default_handler = signal.getsignal(signal.SIGINT)
52-
53-
result = unittest.TestResult()
54-
unittest.installHandler()
55-
unittest.registerResult(result)
56-
57-
self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler)
58-
5976
def test(result):
6077
pid = os.getpid()
6178
os.kill(pid, signal.SIGINT)
6279
result.breakCaught = True
6380
self.assertTrue(result.shouldStop)
6481

65-
try:
66-
test(result)
67-
except KeyboardInterrupt:
68-
self.fail("KeyboardInterrupt not handled")
69-
self.assertTrue(result.breakCaught)
82+
def test_function():
83+
result = unittest.TestResult()
84+
unittest.installHandler()
85+
unittest.registerResult(result)
7086

87+
self.assertNotEqual(
88+
signal.getsignal(signal.SIGINT),
89+
self._default_handler,
90+
)
91+
92+
try:
93+
test(result)
94+
except KeyboardInterrupt:
95+
self.fail("KeyboardInterrupt not handled")
96+
self.assertTrue(result.breakCaught)
97+
self.withRepeats(test_function)
7198

7299
def testSecondInterrupt(self):
73100
# Can't use skipIf decorator because the signal handler may have
74101
# been changed after defining this method.
75102
if signal.getsignal(signal.SIGINT) == signal.SIG_IGN:
76103
self.skipTest("test requires SIGINT to not be ignored")
77-
result = unittest.TestResult()
78-
unittest.installHandler()
79-
unittest.registerResult(result)
80104

81105
def test(result):
82106
pid = os.getpid()
@@ -86,64 +110,66 @@ def test(result):
86110
os.kill(pid, signal.SIGINT)
87111
self.fail("Second KeyboardInterrupt not raised")
88112

89-
try:
90-
test(result)
91-
except KeyboardInterrupt:
92-
pass
93-
else:
94-
self.fail("Second KeyboardInterrupt not raised")
95-
self.assertTrue(result.breakCaught)
113+
def test_function():
114+
result = unittest.TestResult()
115+
unittest.installHandler()
116+
unittest.registerResult(result)
96117

118+
with self.assertRaises(KeyboardInterrupt):
119+
test(result)
120+
self.assertTrue(result.breakCaught)
121+
self.withRepeats(test_function)
97122

98-
def testTwoResults(self):
99-
unittest.installHandler()
100123

101-
result = unittest.TestResult()
102-
unittest.registerResult(result)
103-
new_handler = signal.getsignal(signal.SIGINT)
124+
def testTwoResults(self):
125+
def test_function():
126+
unittest.installHandler()
104127

105-
result2 = unittest.TestResult()
106-
unittest.registerResult(result2)
107-
self.assertEqual(signal.getsignal(signal.SIGINT), new_handler)
128+
result = unittest.TestResult()
129+
unittest.registerResult(result)
130+
new_handler = signal.getsignal(signal.SIGINT)
108131

109-
result3 = unittest.TestResult()
132+
result2 = unittest.TestResult()
133+
unittest.registerResult(result2)
134+
self.assertEqual(signal.getsignal(signal.SIGINT), new_handler)
110135

111-
def test(result):
112-
pid = os.getpid()
113-
os.kill(pid, signal.SIGINT)
136+
result3 = unittest.TestResult()
114137

115-
try:
116-
test(result)
117-
except KeyboardInterrupt:
118-
self.fail("KeyboardInterrupt not handled")
138+
try:
139+
os.kill(os.getpid(), signal.SIGINT)
140+
except KeyboardInterrupt:
141+
self.fail("KeyboardInterrupt not handled")
119142

120-
self.assertTrue(result.shouldStop)
121-
self.assertTrue(result2.shouldStop)
122-
self.assertFalse(result3.shouldStop)
143+
self.assertTrue(result.shouldStop)
144+
self.assertTrue(result2.shouldStop)
145+
self.assertFalse(result3.shouldStop)
146+
self.withRepeats(test_function)
123147

124148

125149
def testHandlerReplacedButCalled(self):
126150
# Can't use skipIf decorator because the signal handler may have
127151
# been changed after defining this method.
128152
if signal.getsignal(signal.SIGINT) == signal.SIG_IGN:
129153
self.skipTest("test requires SIGINT to not be ignored")
130-
# If our handler has been replaced (is no longer installed) but is
131-
# called by the *new* handler, then it isn't safe to delay the
132-
# SIGINT and we should immediately delegate to the default handler
133-
unittest.installHandler()
134-
135-
handler = signal.getsignal(signal.SIGINT)
136-
def new_handler(frame, signum):
137-
handler(frame, signum)
138-
signal.signal(signal.SIGINT, new_handler)
139154

140-
try:
141-
pid = os.getpid()
142-
os.kill(pid, signal.SIGINT)
143-
except KeyboardInterrupt:
144-
pass
145-
else:
146-
self.fail("replaced but delegated handler doesn't raise interrupt")
155+
def test_function():
156+
# If our handler has been replaced (is no longer installed) but is
157+
# called by the *new* handler, then it isn't safe to delay the
158+
# SIGINT and we should immediately delegate to the default handler
159+
unittest.installHandler()
160+
161+
handler = signal.getsignal(signal.SIGINT)
162+
def new_handler(frame, signum):
163+
handler(frame, signum)
164+
signal.signal(signal.SIGINT, new_handler)
165+
166+
try:
167+
os.kill(os.getpid(), signal.SIGINT)
168+
except KeyboardInterrupt:
169+
pass
170+
else:
171+
self.fail("replaced but delegated handler doesn't raise interrupt")
172+
self.withRepeats(test_function)
147173

148174
def testRunner(self):
149175
# Creating a TextTestRunner with the appropriate argument should

Python/ceval.c

+9
Original file line numberDiff line numberDiff line change
@@ -4742,6 +4742,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
47424742
if (res == NULL) {
47434743
goto error;
47444744
}
4745+
CHECK_EVAL_BREAKER();
47454746
DISPATCH();
47464747
}
47474748

@@ -4761,6 +4762,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
47614762
if (res == NULL) {
47624763
goto error;
47634764
}
4765+
CHECK_EVAL_BREAKER();
47644766
DISPATCH();
47654767
}
47664768

@@ -4785,6 +4787,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
47854787
if (res == NULL) {
47864788
goto error;
47874789
}
4790+
CHECK_EVAL_BREAKER();
47884791
DISPATCH();
47894792
}
47904793

@@ -4816,6 +4819,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
48164819
if (res == NULL) {
48174820
goto error;
48184821
}
4822+
CHECK_EVAL_BREAKER();
48194823
DISPATCH();
48204824
}
48214825

@@ -4854,6 +4858,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
48544858
*/
48554859
goto error;
48564860
}
4861+
CHECK_EVAL_BREAKER();
48574862
DISPATCH();
48584863
}
48594864

@@ -4896,6 +4901,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
48964901
if (res == NULL) {
48974902
goto error;
48984903
}
4904+
CHECK_EVAL_BREAKER();
48994905
DISPATCH();
49004906
}
49014907

@@ -5013,6 +5019,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
50135019
if (res == NULL) {
50145020
goto error;
50155021
}
5022+
CHECK_EVAL_BREAKER();
50165023
DISPATCH();
50175024
}
50185025

@@ -5040,6 +5047,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
50405047
if (res == NULL) {
50415048
goto error;
50425049
}
5050+
CHECK_EVAL_BREAKER();
50435051
DISPATCH();
50445052
}
50455053

@@ -5067,6 +5075,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, InterpreterFrame *frame, int thr
50675075
if (res == NULL) {
50685076
goto error;
50695077
}
5078+
CHECK_EVAL_BREAKER();
50705079
DISPATCH();
50715080
}
50725081

0 commit comments

Comments
 (0)