Skip to content

Commit 9a0896e

Browse files
committed
[3.14] gh-152132: Fix Py_RunMain() to return an exit code (#153446) (#153455)
gh-152132: Fix Py_RunMain() to return an exit code (#153446) * Fix Py_RunMain() to return an exit code, rather than calling Py_Exit(), when running a script, a command, or the REPL. * _PyRun_SimpleFile() now logs errors to stderr, for example if setting __main__.__file__ fails. * Add tests on Py_RunMain() exitcode. * Rename functions: * _PyRun_SimpleStringFlagsWithName() => _PyRun_SimpleString() * _PyRun_SimpleFileObject() => _PyRun_SimpleFile() * _PyRun_AnyFileObject() => _PyRun_AnyFile() * _PyRun_InteractiveLoopObject() => _PyRun_InteractiveLoop() * Change _PyRun_SimpleString(), _PyRun_SimpleFile(), _PyRun_AnyFile() and _PyRun_InteractiveLoop() return type to PyObject*. * pymain_repl() now displays the error if PySys_Audit() or import _pyrepl failed. * PyRun_SimpleFileExFlags() and PyRun_AnyFileExFlags() now log PyUnicode_DecodeFSDefault() error. So these functions can no longer return -1 with an exception set. (cherry picked from commit fac72f1) (cherry picked from commit 62c34a6)
1 parent 70c8b9f commit 9a0896e

8 files changed

Lines changed: 407 additions & 237 deletions

File tree

Include/internal/pycore_pylifecycle.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,6 @@ extern int _Py_LegacyLocaleDetected(int warn);
111111
// Export for 'readline' shared extension
112112
PyAPI_FUNC(char*) _Py_SetLocaleFromEnv(int category);
113113

114-
// Export for special main.c string compiling with source tracebacks
115-
int _PyRun_SimpleStringFlagsWithName(const char *command, const char* name, PyCompilerFlags *flags);
116-
117114

118115
/* interpreter config */
119116

Include/internal/pycore_pythonrun.h

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,32 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11-
extern int _PyRun_SimpleFileObject(
11+
extern PyObject* _PyRun_SimpleFile(
1212
FILE *fp,
1313
PyObject *filename,
1414
int closeit,
1515
PyCompilerFlags *flags);
1616

17-
extern int _PyRun_AnyFileObject(
17+
extern PyObject* _PyRun_AnyFile(
1818
FILE *fp,
1919
PyObject *filename,
2020
int closeit,
2121
PyCompilerFlags *flags);
2222

23-
extern int _PyRun_InteractiveLoopObject(
24-
FILE *fp,
25-
PyObject *filename,
26-
PyCompilerFlags *flags);
27-
2823
extern const char* _Py_SourceAsString(
2924
PyObject *cmd,
3025
const char *funcname,
3126
const char *what,
3227
PyCompilerFlags *cf,
3328
PyObject **cmd_copy);
3429

30+
// Export for special main.c string compiling with source tracebacks
31+
extern PyObject* _PyRun_SimpleString(
32+
const char *command,
33+
PyObject* name,
34+
PyCompilerFlags *flags);
35+
36+
3537
#ifdef __cplusplus
3638
}
3739
#endif

Lib/test/test_embed.py

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@
5959
if not os.path.isfile(os.path.join(STDLIB_INSTALL, 'os.py')):
6060
STDLIB_INSTALL = None
6161

62+
CODE_EXITCODE_123 = 'raise SystemExit(123)'
63+
64+
6265
def debug_build(program):
6366
program = os.path.basename(program)
6467
name = os.path.splitext(program)[0]
@@ -108,12 +111,16 @@ def run_embedded_interpreter(self, *args, env=None,
108111
env = env.copy()
109112
env['SYSTEMROOT'] = os.environ['SYSTEMROOT']
110113

111-
p = subprocess.Popen(cmd,
112-
stdout=subprocess.PIPE,
113-
stderr=subprocess.PIPE,
114-
universal_newlines=True,
115-
env=env,
116-
cwd=cwd)
114+
kwargs = dict(
115+
stdout=subprocess.PIPE,
116+
stderr=subprocess.PIPE,
117+
universal_newlines=True,
118+
env=env,
119+
cwd=cwd,
120+
)
121+
if input is not None:
122+
kwargs['stdin'] = subprocess.PIPE
123+
p = subprocess.Popen(cmd, **kwargs)
117124
try:
118125
(out, err) = p.communicate(input=input, timeout=timeout)
119126
except:
@@ -530,6 +537,55 @@ def _nogil_filtered_err(err: str, mod_name: str) -> str:
530537
]
531538
return "\n".join(filtered_err_lines)
532539

540+
def check_program_exitcode(self, *args, check_stderr=True, **kwargs):
541+
out, err = self.run_embedded_interpreter(*args, **kwargs)
542+
self.assertEqual(out.rstrip(), 'ok! Py_RunMain() returned 123')
543+
if check_stderr:
544+
self.assertEqual(err, '')
545+
546+
def test_init_run_main_code_exitcode(self):
547+
code = CODE_EXITCODE_123
548+
self.check_program_exitcode("test_init_run_main_code_exitcode", code)
549+
550+
def test_init_run_main_script_exitcode(self):
551+
with tempfile.TemporaryDirectory() as tmpdir:
552+
filename = os.path.join(tmpdir, 'script.py')
553+
with open(filename, 'w') as fp:
554+
fp.write(CODE_EXITCODE_123)
555+
556+
self.check_program_exitcode("test_init_run_main_script_exitcode",
557+
filename)
558+
559+
def test_init_run_main_interactive_exitcode(self):
560+
code = CODE_EXITCODE_123
561+
self.check_program_exitcode("test_init_run_main_interactive_exitcode",
562+
input=code,
563+
check_stderr=False)
564+
565+
def test_init_run_main_startup_exitcode(self):
566+
with tempfile.TemporaryDirectory() as tmpdir:
567+
filename = os.path.join(tmpdir, 'startup.py')
568+
with open(filename, 'x') as fp:
569+
fp.write(CODE_EXITCODE_123)
570+
571+
env = dict(os.environ)
572+
env['PYTHONSTARTUP'] = filename
573+
self.check_program_exitcode("test_init_run_main_interactive_exitcode",
574+
env=env,
575+
check_stderr=False)
576+
577+
def test_init_run_main_module_exitcode(self):
578+
with tempfile.TemporaryDirectory() as tmpdir:
579+
modname = '_testembed_testmodule'
580+
filename = os.path.join(tmpdir, modname + '.py')
581+
with open(filename, 'x', encoding='utf8') as fp:
582+
fp.write(CODE_EXITCODE_123)
583+
584+
env = dict(os.environ)
585+
env['PYTHONPATH'] = tmpdir
586+
self.check_program_exitcode("test_init_run_main_module_exitcode",
587+
modname, env=env)
588+
533589

534590
@unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi")
535591
class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :c:func:`Py_RunMain` to return an exit code, rather than calling
2+
:c:func:`Py_Exit`, when running a script, a command, or the REPL. Patch by
3+
Victor Stinner.

Modules/_testcapi/run.c

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,6 @@ run_simplefile(PyObject *mod, PyObject *args)
129129
assert(_Py_IsValidFD(fd));
130130
fclose(fp);
131131

132-
if (res == -1 && PyErr_Occurred()) {
133-
return NULL;
134-
}
135132
assert(!PyErr_Occurred());
136133
return PyLong_FromLong(res);
137134
}
@@ -203,9 +200,6 @@ run_simplefileex(PyObject *mod, PyObject *args)
203200
return NULL;
204201
}
205202

206-
if (res == -1 && PyErr_Occurred()) {
207-
return NULL;
208-
}
209203
assert(!PyErr_Occurred());
210204
return PyLong_FromLong(res);
211205
}
@@ -243,9 +237,6 @@ run_simplefileexflags(PyObject *mod, PyObject *args)
243237
return NULL;
244238
}
245239

246-
if (res == -1 && PyErr_Occurred()) {
247-
return NULL;
248-
}
249240
assert(!PyErr_Occurred());
250241
return PyLong_FromLong(res);
251242
}
@@ -272,9 +263,6 @@ run_anyfile(PyObject *mod, PyObject *args)
272263
assert(_Py_IsValidFD(fd));
273264
fclose(fp);
274265

275-
if (res == -1 && PyErr_Occurred()) {
276-
return NULL;
277-
}
278266
assert(!PyErr_Occurred());
279267
return PyLong_FromLong(res);
280268
}
@@ -310,9 +298,6 @@ run_anyfileflags(PyObject *mod, PyObject *args)
310298
assert(_Py_IsValidFD(fd));
311299
fclose(fp);
312300

313-
if (res == -1 && PyErr_Occurred()) {
314-
return NULL;
315-
}
316301
assert(!PyErr_Occurred());
317302
return PyLong_FromLong(res);
318303
}
@@ -342,9 +327,6 @@ run_anyfileex(PyObject *mod, PyObject *args)
342327
return NULL;
343328
}
344329

345-
if (res == -1 && PyErr_Occurred()) {
346-
return NULL;
347-
}
348330
assert(!PyErr_Occurred());
349331
return PyLong_FromLong(res);
350332
}
@@ -382,9 +364,6 @@ run_anyfileexflags(PyObject *mod, PyObject *args)
382364
return NULL;
383365
}
384366

385-
if (res == -1 && PyErr_Occurred()) {
386-
return NULL;
387-
}
388367
assert(!PyErr_Occurred());
389368
return PyLong_FromLong(res);
390369
}
@@ -664,9 +643,6 @@ run_simplestring(PyObject *mod, PyObject *args)
664643
}
665644

666645
int res = PyRun_SimpleString(str);
667-
if (res == -1 && PyErr_Occurred()) {
668-
return NULL;
669-
}
670646
assert(!PyErr_Occurred());
671647
return PyLong_FromLong(res);
672648
}
@@ -689,9 +665,6 @@ run_simplestringflags(PyObject *mod, PyObject *args)
689665
}
690666

691667
int res = PyRun_SimpleStringFlags(str, pflags);
692-
if (res == -1 && PyErr_Occurred()) {
693-
return NULL;
694-
}
695668
assert(!PyErr_Occurred());
696669
return PyLong_FromLong(res);
697670
}

0 commit comments

Comments
 (0)