Skip to content

Commit 05e4886

Browse files
authored
gh-96853: Restore test coverage for Py_Initialize(Ex) (GH-98212)
* As most of `test_embed` now uses `Py_InitializeFromConfig`, add a specific test case to cover `Py_Initialize` (and `Py_InitializeEx`) * Rename `_testembed` init helper to clarify the API used * Add a `PyConfig_Clear` call in `Py_InitializeEx` to make the code more obviously correct (it already didn't leak as none of the dynamically allocated config fields were being populated, but it's clearer if the wrappers follow the documented API usage guidelines)
1 parent 76f989d commit 05e4886

File tree

5 files changed

+57
-19
lines changed

5 files changed

+57
-19
lines changed

Lib/test/test_embed.py

+6
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,12 @@ def test_finalize_structseq(self):
340340
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
341341
self.assertEqual(out, 'Tests passed\n' * INIT_LOOPS)
342342

343+
def test_simple_initialization_api(self):
344+
# _testembed now uses Py_InitializeFromConfig by default
345+
# This case specifically checks Py_Initialize(Ex) still works
346+
out, err = self.run_embedded_interpreter("test_repeated_simple_init")
347+
self.assertEqual(out, 'Finalized\n' * INIT_LOOPS)
348+
343349
def test_quickened_static_code_gets_unquickened_at_Py_FINALIZE(self):
344350
# https://github.com/python/cpython/issues/92031
345351

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
``Py_InitializeEx`` now correctly calls ``PyConfig_Clear`` after initializing
2+
the interpreter (the omission didn't cause a memory leak only because none
3+
of the dynamically allocated config fields are populated by the wrapper
4+
function)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Added explicit coverage of ``Py_Initialize`` (and hence ``Py_InitializeEx``)
2+
back to the embedding tests (all other embedding tests migrated to
3+
``Py_InitializeFromConfig`` in Python 3.11)

Programs/_testembed.c

+43-19
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ char **main_argv;
2222
/*********************************************************
2323
* Embedded interpreter tests that need a custom exe
2424
*
25-
* Executed via 'EmbeddingTests' in Lib/test/test_capi.py
25+
* Executed via Lib/test/test_embed.py
2626
*********************************************************/
2727

2828
// Use to display the usage
@@ -73,14 +73,20 @@ static void init_from_config_clear(PyConfig *config)
7373
}
7474

7575

76-
static void _testembed_Py_Initialize(void)
76+
static void _testembed_Py_InitializeFromConfig(void)
7777
{
7878
PyConfig config;
7979
_PyConfig_InitCompatConfig(&config);
8080
config_set_program_name(&config);
8181
init_from_config_clear(&config);
8282
}
8383

84+
static void _testembed_Py_Initialize(void)
85+
{
86+
Py_SetProgramName(PROGRAM_NAME);
87+
Py_Initialize();
88+
}
89+
8490

8591
/*****************************************************
8692
* Test repeated initialisation and subinterpreters
@@ -110,7 +116,7 @@ static int test_repeated_init_and_subinterpreters(void)
110116

111117
for (int i=1; i <= INIT_LOOPS; i++) {
112118
printf("--- Pass %d ---\n", i);
113-
_testembed_Py_Initialize();
119+
_testembed_Py_InitializeFromConfig();
114120
mainstate = PyThreadState_Get();
115121

116122
PyEval_ReleaseThread(mainstate);
@@ -168,7 +174,7 @@ static int test_repeated_init_exec(void)
168174
fprintf(stderr, "--- Loop #%d ---\n", i);
169175
fflush(stderr);
170176

171-
_testembed_Py_Initialize();
177+
_testembed_Py_InitializeFromConfig();
172178
int err = PyRun_SimpleString(code);
173179
Py_Finalize();
174180
if (err) {
@@ -178,6 +184,23 @@ static int test_repeated_init_exec(void)
178184
return 0;
179185
}
180186

187+
/****************************************************************************
188+
* Test the Py_Initialize(Ex) convenience/compatibility wrappers
189+
***************************************************************************/
190+
// This is here to help ensure there are no wrapper resource leaks (gh-96853)
191+
static int test_repeated_simple_init(void)
192+
{
193+
for (int i=1; i <= INIT_LOOPS; i++) {
194+
fprintf(stderr, "--- Loop #%d ---\n", i);
195+
fflush(stderr);
196+
197+
_testembed_Py_Initialize();
198+
Py_Finalize();
199+
printf("Finalized\n"); // Give test_embed some output to check
200+
}
201+
return 0;
202+
}
203+
181204

182205
/*****************************************************
183206
* Test forcing a particular IO encoding
@@ -199,7 +222,7 @@ static void check_stdio_details(const char *encoding, const char * errors)
199222
fflush(stdout);
200223
/* Force the given IO encoding */
201224
Py_SetStandardStreamEncoding(encoding, errors);
202-
_testembed_Py_Initialize();
225+
_testembed_Py_InitializeFromConfig();
203226
PyRun_SimpleString(
204227
"import sys;"
205228
"print('stdin: {0.encoding}:{0.errors}'.format(sys.stdin));"
@@ -308,7 +331,7 @@ static int test_pre_initialization_sys_options(void)
308331
dynamic_xoption = NULL;
309332

310333
_Py_EMBED_PREINIT_CHECK("Initializing interpreter\n");
311-
_testembed_Py_Initialize();
334+
_testembed_Py_InitializeFromConfig();
312335
_Py_EMBED_PREINIT_CHECK("Check sys module contents\n");
313336
PyRun_SimpleString("import sys; "
314337
"print('sys.warnoptions:', sys.warnoptions); "
@@ -352,7 +375,7 @@ static int test_bpo20891(void)
352375
return 1;
353376
}
354377

355-
_testembed_Py_Initialize();
378+
_testembed_Py_InitializeFromConfig();
356379

357380
unsigned long thrd = PyThread_start_new_thread(bpo20891_thread, &lock);
358381
if (thrd == PYTHREAD_INVALID_THREAD_ID) {
@@ -375,7 +398,7 @@ static int test_bpo20891(void)
375398

376399
static int test_initialize_twice(void)
377400
{
378-
_testembed_Py_Initialize();
401+
_testembed_Py_InitializeFromConfig();
379402

380403
/* bpo-33932: Calling Py_Initialize() twice should do nothing
381404
* (and not crash!). */
@@ -393,7 +416,7 @@ static int test_initialize_pymain(void)
393416
L"print(f'Py_Main() after Py_Initialize: "
394417
L"sys.argv={sys.argv}')"),
395418
L"arg2"};
396-
_testembed_Py_Initialize();
419+
_testembed_Py_InitializeFromConfig();
397420

398421
/* bpo-34008: Calling Py_Main() after Py_Initialize() must not crash */
399422
Py_Main(Py_ARRAY_LENGTH(argv), argv);
@@ -416,7 +439,7 @@ dump_config(void)
416439

417440
static int test_init_initialize_config(void)
418441
{
419-
_testembed_Py_Initialize();
442+
_testembed_Py_InitializeFromConfig();
420443
dump_config();
421444
Py_Finalize();
422445
return 0;
@@ -767,7 +790,7 @@ static int test_init_compat_env(void)
767790
/* Test initialization from environment variables */
768791
Py_IgnoreEnvironmentFlag = 0;
769792
set_all_env_vars();
770-
_testembed_Py_Initialize();
793+
_testembed_Py_InitializeFromConfig();
771794
dump_config();
772795
Py_Finalize();
773796
return 0;
@@ -803,7 +826,7 @@ static int test_init_env_dev_mode(void)
803826
/* Test initialization from environment variables */
804827
Py_IgnoreEnvironmentFlag = 0;
805828
set_all_env_vars_dev_mode();
806-
_testembed_Py_Initialize();
829+
_testembed_Py_InitializeFromConfig();
807830
dump_config();
808831
Py_Finalize();
809832
return 0;
@@ -816,7 +839,7 @@ static int test_init_env_dev_mode_alloc(void)
816839
Py_IgnoreEnvironmentFlag = 0;
817840
set_all_env_vars_dev_mode();
818841
putenv("PYTHONMALLOC=malloc");
819-
_testembed_Py_Initialize();
842+
_testembed_Py_InitializeFromConfig();
820843
dump_config();
821844
Py_Finalize();
822845
return 0;
@@ -1156,7 +1179,7 @@ static int test_open_code_hook(void)
11561179
}
11571180

11581181
Py_IgnoreEnvironmentFlag = 0;
1159-
_testembed_Py_Initialize();
1182+
_testembed_Py_InitializeFromConfig();
11601183
result = 0;
11611184

11621185
PyObject *r = PyFile_OpenCode("$$test-filename");
@@ -1220,7 +1243,7 @@ static int _test_audit(Py_ssize_t setValue)
12201243

12211244
Py_IgnoreEnvironmentFlag = 0;
12221245
PySys_AddAuditHook(_audit_hook, &sawSet);
1223-
_testembed_Py_Initialize();
1246+
_testembed_Py_InitializeFromConfig();
12241247

12251248
if (PySys_Audit("_testembed.raise", NULL) == 0) {
12261249
printf("No error raised");
@@ -1276,7 +1299,7 @@ static int test_audit_subinterpreter(void)
12761299
{
12771300
Py_IgnoreEnvironmentFlag = 0;
12781301
PySys_AddAuditHook(_audit_subinterpreter_hook, NULL);
1279-
_testembed_Py_Initialize();
1302+
_testembed_Py_InitializeFromConfig();
12801303

12811304
Py_NewInterpreter();
12821305
Py_NewInterpreter();
@@ -1871,13 +1894,13 @@ static int test_unicode_id_init(void)
18711894
_Py_IDENTIFIER(test_unicode_id_init);
18721895

18731896
// Initialize Python once without using the identifier
1874-
_testembed_Py_Initialize();
1897+
_testembed_Py_InitializeFromConfig();
18751898
Py_Finalize();
18761899

18771900
// Now initialize Python multiple times and use the identifier.
18781901
// The first _PyUnicode_FromId() call initializes the identifier index.
18791902
for (int i=0; i<3; i++) {
1880-
_testembed_Py_Initialize();
1903+
_testembed_Py_InitializeFromConfig();
18811904

18821905
PyObject *str1, *str2;
18831906

@@ -2021,7 +2044,7 @@ unwrap_allocator(PyMemAllocatorEx *allocator)
20212044
static int
20222045
test_get_incomplete_frame(void)
20232046
{
2024-
_testembed_Py_Initialize();
2047+
_testembed_Py_InitializeFromConfig();
20252048
PyMemAllocatorEx allocator;
20262049
wrap_allocator(&allocator);
20272050
// Force an allocation with an incomplete (generator) frame:
@@ -2053,6 +2076,7 @@ struct TestCase
20532076
static struct TestCase TestCases[] = {
20542077
// Python initialization
20552078
{"test_repeated_init_exec", test_repeated_init_exec},
2079+
{"test_repeated_simple_init", test_repeated_simple_init},
20562080
{"test_forced_io_encoding", test_forced_io_encoding},
20572081
{"test_repeated_init_and_subinterpreters", test_repeated_init_and_subinterpreters},
20582082
{"test_repeated_init_and_inittab", test_repeated_init_and_inittab},

Python/pylifecycle.c

+1
Original file line numberDiff line numberDiff line change
@@ -1313,6 +1313,7 @@ Py_InitializeEx(int install_sigs)
13131313
config.install_signal_handlers = install_sigs;
13141314

13151315
status = Py_InitializeFromConfig(&config);
1316+
PyConfig_Clear(&config);
13161317
if (_PyStatus_EXCEPTION(status)) {
13171318
Py_ExitStatusException(status);
13181319
}

0 commit comments

Comments
 (0)