8000 fix handling of empty and 1-item tuples for `sys.exit` · python/cpython@ae37e95 · GitHub
[go: up one dir, main page]

Skip to content

Commit ae37e95

Browse files
committed
fix handling of empty and 1-item tuples for sys.exit
1 parent 7c43615 commit ae37e95

File tree

3 files changed

+71
-37
lines changed

3 files changed

+71
-37
lines changed

Lib/test/test_sys.py

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -205,62 +205,84 @@ def test_excepthook(self):
205205
# Python/pythonrun.c::PyErr_PrintEx() is tricky.
206206

207207

208+
def raise_system_exit(*args, **kwargs):
209+
raise SystemExit(*args, **kwargs)
210+
211+
208212
class SysModuleTest(unittest.TestCase):
209213

210214
def tearDown(self):
211215
test.support.reap_children()
212216

213217
def test_exit(self):
214-
# call with two arguments
218+
# call with two arguments is only forbidden for sys.exit()
215219
self.assertRaises(TypeError, sys.exit, 42, 42)
220+
with self.subTest('sys.exit'):
221+
self.do_test_exit(sys.exit)
222+
with self.subTest('raise SystemExit'):
223+
self.do_test_exit(raise_system_exit)
216224

225+
def do_test_exit(self, sys_exit_raiser):
217226
# call without argument
218227
with self.assertRaises(SystemExit) as cm:
219-
sys.exit()
228+
sys_exit_raiser()
220229
self.assertIsNone(cm.exception.code)
230+
with self.assertRaises(SystemExit) as cm:
231+
sys_exit_raiser(None)
232+
self.assertIsNone(cm.exception.code)
233+
234+
# call with integer argument
235+
with self.assertRaises(SystemExit) as cm:
236+
sys_exit_raiser(42)
237+
self.assertEqual(cm.exception.code, 42)
238+
239+
# gh-133548: call with tuple argument with one entry
240+
with self.assertRaises(SystemExit) as cm:
241+
sys_exit_raiser((42,))
242+
self.assertEqual(cm.exception.code, (42,))
243+
244+
# call with string argument
245+
with self.assertRaises(SystemExit) as cm:
246+
sys_exit_raiser("exit")
247+
self.assertEqual(cm.exception.code, "exit")
248+
249+
# call with tuple argument with two entries
250+
with self.assertRaises(SystemExit) as cm:
251+
sys_exit_raiser((42, 42))
252+
self.assertEqual(cm.exception.args, ((42, 42),))
253+
self.assertEqual(cm.exception.code, (42, 42))
254+
255+
def test_exit_message(self):
256+
with self.subTest('sys.exit'):
257+
self.do_test_exit_message("sys.exit")
258+
with self.subTest('raise SystemExit'):
259+
self.do_test_exit_message("raise SystemExit")
260+
261+
def do_test_exit_message(self, call_statement):
262+
def sys_exit_impl(value='', prolog=''):
263+
return f'import sys\n{prolog}\n{call_statement}({value})'
221264

222-
rc, out, err = assert_python_ok('-c', 'import sys; sys.exit()')
265+
rc, out, err = assert_python_ok('-c', sys_exit_impl())
223266
self.assertEqual(rc, 0)
224267
self.assertEqual(out, b'')
225268
self.assertEqual(err, b'')
226269

227270
# gh-125842: Windows uses 32-bit unsigned integers for exit codes
228271
# so a -1 exit code is sometimes interpreted as 0xffff_ffff.
229-
rc, out, err = assert_python_failure('-c', 'import sys; sys.exit(0xffff_ffff)')
272+
rc, out, err = assert_python_failure('-c', sys_exit_impl(0xffff_ffff))
230273
self.assertIn(rc, (-1, 0xff, 0xffff_ffff))
231274
self.assertEqual(out, b'')
232275
self.assertEqual(err, b'')
233276

234277
# Overflow results in a -1 exit code, which may be converted to 0xff
235278
# or 0xffff_ffff.
236-
rc, out, err = assert_python_failure('-c', 'import sys; sys.exit(2**128)')
279+
rc, out, err = assert_python_failure('-c', sys_exit_impl(2**128))
237280
self.assertIn(rc, (-1, 0xff, 0xffff_ffff))
238281
self.assertEqual(out, b'')
239282
self.assertEqual(err, b'')
240283

241-
# call with integer argument
242-
with self.assertRaises(SystemExit) as cm:
243-
sys.exit(42)
244-
self.assertEqual(cm.exception.code, 42)
245-
246-
# call with tuple argument with one entry
247-
# entry will be unpacked
248-
with self.assertRaises(SystemExit) as cm:
249-
sys.exit((42,))
250-
self.assertEqual(cm.exception.code, 42)
251-
252-
# call with string argument
253-
with self.assertRaises(SystemExit) as cm:
254-
sys.exit("exit")
255-
self.assertEqual(cm.exception.code, "exit")
256-
257-
# call with tuple argument with two entries
258-
with self.assertRaises(SystemExit) as cm:
259-
sys.exit((17, 23))
260-
self.assertEqual(cm.exception.code, (17, 23))
261-
262-
# test that the exit machinery handles SystemExits properly
263-
rc, out, err = assert_python_failure('-c', 'raise SystemExit(47)')
284+
# test that the exit machinery handles custom codes properly
285+
rc, out, err = assert_python_failure('-c', sys_exit_impl(47))
264286
self.assertEqual(rc, 47)
265287
self.assertEqual(out, b'')
266288
self.assertEqual(err, b'')
@@ -274,20 +296,20 @@ def check_exit_message(code, expected, **env_vars):
274296
# test that stderr buffer is flushed before the exit message is written
275297
# into stderr
276298
check_exit_message(
277-
r'import sys; sys.stderr.write("unflushed,"); sys.exit("message")',
278-
b"unflushed,message")
299+
sys_exit_impl("'message'", 'sys.stderr.write("unflushed,")'),
300+
b"unflushed,message"
301+
)
279302

280303
# test that the exit message is written with backslashreplace error
281304
# handler to stderr
282-
check_exit_message(
283-
r'import sys; sys.exit("surrogates:\uDCFF")',
284-
b"surrogates:\\udcff")
305+
check_exit_message(sys_exit_impl(r"'surrogates:\uDCFF'"),
306+
b"surrogates:\\udcff")
285307

286308
# test that the unicode message is encoded to the stderr encoding
287309
# instead of the default encoding (utf8)
288-
check_exit_message(
289-
r'import sys; sys.exit("h\xe9")',
290-
b"h\xe9", PYTHONIOENCODING='latin-1')
310+
check_exit_message(sys_exit_impl(r"'h\xe9'"), b"h\xe9",
311+
PYTHONIOENCODING='latin-1')
312+
291313

292314
@support.requires_subprocess()
293315
def test_exit_codes_under_repl(self):
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:func:`sys.exit` and :exc:`SystemExit` now correctly handle empty and 1-item
2+
tuples arguments. Previously, such tuples were unpacked by :func:`!sys.exit`
3+
but not by :exc:`!SystemExit` and the process status code was incorrectly
4+
set. Patch by Bénédikt Tran.

Python/sysmodule.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,15 @@ sys_exit_impl(PyObject *module, PyObject *status)
916916
/*[clinic end generated code: output=13870986c1ab2ec0 input=b86ca9497baa94f2]*/
917917
{
918918
/* Raise SystemExit so callers may catch it or clean up. */
919-
PyErr_SetObject(PyExc_SystemExit, status);
919+
if (PyTuple_Check(status)) {
920+
/* Make sure that tuples are not flattened during normalization. */
921+
PyObject *exc = PyObject_CallOneArg(PyExc_SystemExit, status);
922+
PyErr_SetObject(PyExc_SystemExit, exc);
923+
Py_DECREF(exc);
924+
}
925+
else {
926+
PyErr_SetObject(PyExc_SystemExit, status);
927+
}
920928
return NULL;
921929
}
922930

0 commit comments

Comments
 (0)
0