Skip to content

Commit 57dd110

Browse files
[3.11] gh-96853: Restore test coverage for Py_Initialize(Ex) (GH-98874)
* 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) (cherry picked from commit 05e4886) Co-authored-by: Nick Coghlan <[email protected]>
1 parent 5efe2ee commit 57dd110

File tree

5 files changed

+57
-19
lines changed

5 files changed

+57
-19
lines changed

Lib/test/test_embed.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,12 @@ def test_finalize_structseq(self):
341341
out, err = self.run_embedded_interpreter("test_repeated_init_exec", code)
342342
self.assertEqual(out, 'Tests passed\n' * INIT_LOOPS)
343343

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

Lines changed: 4 additions & 0 deletions
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)
Lines changed: 3 additions & 0 deletions
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

Lines changed: 43 additions & 19 deletions
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;
@@ -765,7 +788,7 @@ static int test_init_compat_env(void)
765788
/* Test initialization from environment variables */
766789
Py_IgnoreEnvironmentFlag = 0;
767790
set_all_env_vars();
768-
_testembed_Py_Initialize();
791+
_testembed_Py_InitializeFromConfig();
769792
dump_config();
770793
Py_Finalize();
771794
return 0;
@@ -801,7 +824,7 @@ static int test_init_env_dev_mode(void)
801824
/* Test initialization from environment variables */
802825
Py_IgnoreEnvironmentFlag = 0;
803826
set_all_env_vars_dev_mode();
804-
_testembed_Py_Initialize();
827+
_testembed_Py_InitializeFromConfig();
805828
dump_config();
806829
Py_Finalize();
807830
return 0;
@@ -814,7 +837,7 @@ static int test_init_env_dev_mode_alloc(void)
814837
Py_IgnoreEnvironmentFlag = 0;
815838
set_all_env_vars_dev_mode();
816839
putenv("PYTHONMALLOC=malloc");
817-
_testembed_Py_Initialize();
840+
_testembed_Py_InitializeFromConfig();
818841
dump_config();
819842
Py_Finalize();
820843
return 0;
@@ -1154,7 +1177,7 @@ static int test_open_code_hook(void)
11541177
}
11551178

11561179
Py_IgnoreEnvironmentFlag = 0;
1157-
_testembed_Py_Initialize();
1180+
_testembed_Py_InitializeFromConfig();
11581181
result = 0;
11591182

11601183
PyObject *r = PyFile_OpenCode("$$test-filename");
@@ -1218,7 +1241,7 @@ static int _test_audit(Py_ssize_t setValue)
12181241

12191242
Py_IgnoreEnvironmentFlag = 0;
12201243
PySys_AddAuditHook(_audit_hook, &sawSet);
1221-
_testembed_Py_Initialize();
1244+
_testembed_Py_InitializeFromConfig();
12221245

12231246
if (PySys_Audit("_testembed.raise", NULL) == 0) {
12241247
printf("No error raised");
@@ -1274,7 +1297,7 @@ static int test_audit_subinterpreter(void)
12741297
{
12751298
Py_IgnoreEnvironmentFlag = 0;
12761299
PySys_AddAuditHook(_audit_subinterpreter_hook, NULL);
1277-
_testembed_Py_Initialize();
1300+
_testembed_Py_InitializeFromConfig();
12781301

12791302
Py_NewInterpreter();
12801303
Py_NewInterpreter();
@@ -1869,13 +1892,13 @@ static int test_unicode_id_init(void)
18691892
_Py_IDENTIFIER(test_unicode_id_init);
18701893

18711894
// Initialize Python once without using the identifier
1872-
_testembed_Py_Initialize();
1895+
_testembed_Py_InitializeFromConfig();
18731896
Py_Finalize();
18741897

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

18801903
PyObject *str1, *str2;
18811904

@@ -2007,7 +2030,7 @@ unwrap_allocator(PyMemAllocatorEx *allocator)
20072030
static int
20082031
test_get_incomplete_frame(void)
20092032
{
2010-
_testembed_Py_Initialize();
2033+
_testembed_Py_InitializeFromConfig();
20112034
PyMemAllocatorEx allocator;
20122035
wrap_allocator(&allocator);
20132036
// Force an allocation with an incomplete (generator) frame:
@@ -2039,6 +2062,7 @@ struct TestCase
20392062
static struct TestCase TestCases[] = {
20402063
// Python initialization
20412064
{"test_repeated_init_exec", test_repeated_init_exec},
2065+
{"test_repeated_simple_init", test_repeated_simple_init},
20422066
{"test_forced_io_encoding", test_forced_io_encoding},
20432067
{"test_repeated_init_and_subinterpreters", test_repeated_init_and_subinterpreters},
20442068
{"test_repeated_init_and_inittab", test_repeated_init_and_inittab},

Python/pylifecycle.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,7 @@ Py_InitializeEx(int install_sigs)
12891289
config.install_signal_handlers = install_sigs;
12901290

12911291
status = Py_InitializeFromConfig(&config);
1292+
PyConfig_Clear(&config);
12921293
if (_PyStatus_EXCEPTION(status)) {
12931294
Py_ExitStatusException(status);
12941295
}

0 commit comments

Comments
 (0)