@@ -1561,21 +1561,6 @@ def test_class_getitems(self):
1561
1561
self .assertIsInstance (subprocess .Popen [bytes ], types .GenericAlias )
1562
1562
self .assertIsInstance (subprocess .CompletedProcess [str ], types .GenericAlias )
1563
1563
1564
- @unittest .skipIf (not sysconfig .get_config_var ("HAVE_VFORK" ),
1565
- "vfork() not enabled by configure." )
1566
- @mock .patch ("subprocess._fork_exec" )
1567
- def test__use_vfork (self , mock_fork_exec ):
1568
- self .assertTrue (subprocess ._USE_VFORK ) # The default value regardless.
1569
- mock_fork_exec .side_effect = RuntimeError ("just testing args" )
1570
- with self .assertRaises (RuntimeError ):
1571
- subprocess .run ([sys .executable , "-c" , "pass" ])
1572
- mock_fork_exec .assert_called_once ()
1573
- self .assertTrue (mock_fork_exec .call_args .args [- 1 ])
1574
- with mock .patch .object (subprocess , '_USE_VFORK' , False ):
1575
- with self .assertRaises (RuntimeError ):
1576
- subprocess .run ([sys .executable , "-c" , "pass" ])
1577
- self .assertFalse (mock_fork_exec .call_args_list [- 1 ].args [- 1 ])
1578
-
1579
1564
1580
1565
class RunFuncTestCase (BaseTestCase ):
1581
1566
def run_python (self , code , ** kwargs ):
@@ -3360,6 +3345,89 @@ def exit_handler():
3360
3345
self .assertEqual (out , b'' )
3361
3346
self .assertIn (b"preexec_fn not supported at interpreter shutdown" , err )
3362
3347
3348
+ @unittest .skipIf (not sysconfig .get_config_var ("HAVE_VFORK" ),
3349
+ "vfork() not enabled by configure." )
3350
+ @mock .patch ("subprocess._fork_exec" )
3351
+ def test__use_vfork (self , mock_fork_exec ):
3352
+ self .assertTrue (subprocess ._USE_VFORK ) # The default value regardless.
3353
+ mock_fork_exec .side_effect = RuntimeError ("just testing args" )
3354
+ with self .assertRaises (RuntimeError ):
3355
+ subprocess .run ([sys .executable , "-c" , "pass" ])
3356
+ mock_fork_exec .assert_called_once ()
3357
+ # NOTE: These assertions are *ugly* as they require the last arg
3358
+ # to remain the have_vfork boolean. We really need to refactor away
3359
+ # from the giant "wall of args" internal C extension API.
3360
+ self .assertTrue (mock_fork_exec .call_args .args [- 1 ])
3361
+ with mock .patch .object (subprocess , '_USE_VFORK' , False ):
3362
+ with self .assertRaises (RuntimeError ):
3363
+ subprocess .run ([sys .executable , "-c" , "pass" ])
3364
+ self .assertFalse (mock_fork_exec .call_args_list [- 1 ].args [- 1 ])
3365
+
3366
+ @unittest .skipIf (not sysconfig .get_config_var ("HAVE_VFORK" ),
3367
+ "vfork() not enabled by configure." )
3368
+ @unittest .skipIf (sys .platform != "linux" , "Linux only, requires strace." )
3369
+ def test_vfork_used_when_expected (self ):
3370
+ # This is a performance regression test to ensure we default to using
3371
+ # vfork() when possible.
3372
+ strace_binary = "/usr/bin/strace"
3373
+ # The only system calls we are interested in.
3374
+ strace_filter = "--trace=clone,clone2,clone3,fork,vfork,exit,exit_group"
3375
+ true_binary = "/bin/true"
3376
+ strace_command = [strace_binary , strace_filter ]
3377
+
3378
+ try :
3379
+ does_strace_work_process = subprocess .run (
3380
+ strace_command + [true_binary ],
3381
+ stderr = subprocess .PIPE ,
3382
+ stdout = subprocess .DEVNULL ,
3383
+ )
3384
+ rc = does_strace_work_process .returncode
3385
+ stderr = does_strace_work_process .stderr
3386
+ except OSError :
3387
+ rc = - 1
3388
+ stderr = ""
3389
+ if rc or (b"+++ exited with 0 +++" not in stderr ):
3390
+ self .skipTest ("strace not found or not working as expected." )
3391
+
3392
+ with self .subTest (name = "default_is_vfork" ):
3393
+ vfork_result = assert_python_ok (
3394
+ "-c" ,
3395
+ textwrap .dedent (f"""\
3396
+ import subprocess
3397
+ subprocess.check_call([{ true_binary !r} ])""" ),
3398
+ __run_using_command = strace_command ,
3399
+ )
3400
+ # Match both vfork() and clone(..., flags=...|CLONE_VFORK|...)
3401
+ self .assertRegex (vfork_result .err , br"(?i)vfork" )
3402
+ # Do NOT check that fork() or other clones did not happen.
3403
+ # If the OS denys the vfork it'll fallback to plain fork().
3404
+
3405
+ # Test that each individual thing that would disable the use of vfork
3406
+ # actually disables it.
3407
+ for sub_name , preamble , sp_kwarg , expect_permission_error in (
3408
+ ("!use_vfork" , "subprocess._USE_VFORK = False" , "" , False ),
3409
+ ("preexec" , "" , "preexec_fn=lambda: None" , False ),
3410
+ ("setgid" , "" , f"group={ os .getgid ()} " , True ),
3411
+ ("setuid" , "" , f"user={ os .getuid ()} " , True ),
3412
+ ("setgroups" , "" , "extra_groups=[]" , True ),
3413
+ ):
3414
+ with self .subTest (name = sub_name ):
3415
+ non_vfork_result = assert_python_ok (
3416
+ "-c" ,
3417
+ textwrap .dedent (f"""\
3418
+ import subprocess
3419
+ { preamble }
3420
+ try:
3421
+ subprocess.check_call(
3422
+ [{ true_binary !r} ], **dict({ sp_kwarg } ))
3423
+ except PermissionError:
3424
+ if not { expect_permission_error } :
3425
+ raise""" ),
3426
+ __run_using_command = strace_command ,
3427
+ )
3428
+ # Ensure neither vfork() or clone(..., flags=...|CLONE_VFORK|...).
3429
+ self .assertNotRegex (non_vfork_result .err , br"(?i)vfork" )
3430
+
3363
3431
3364
3432
@unittest .skipUnless (mswindows , "Windows specific tests" )
3365
3433
class Win32ProcessTestCase (BaseTestCase ):
0 commit comments