@@ -1561,21 +1561,6 @@ def test_class_getitems(self):
15611561 self .assertIsInstance (subprocess .Popen [bytes ], types .GenericAlias )
15621562 self .assertIsInstance (subprocess .CompletedProcess [str ], types .GenericAlias )
15631563
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-
15791564
15801565class RunFuncTestCase (BaseTestCase ):
15811566 def run_python (self , code , ** kwargs ):
@@ -3360,6 +3345,89 @@ def exit_handler():
33603345 self .assertEqual (out , b'' )
33613346 self .assertIn (b"preexec_fn not supported at interpreter shutdown" , err )
33623347
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+
33633431
33643432@unittest .skipUnless (mswindows , "Windows specific tests" )
33653433class Win32ProcessTestCase (BaseTestCase ):
0 commit comments