Skip to content

Commit bc69d00

Browse files
authored
bpo-31173: Rewrite WSTOPSIG test of test_subprocess (#3055) (#3070)
The current test_child_terminated_in_stopped_state() function test creates a child process which calls ptrace(PTRACE_TRACEME, 0, 0) and then crash (SIGSEGV). The problem is that calling os.waitpid() in the parent process is not enough to close the process: the child process remains alive and so the unit test leaks a child process in a strange state. Closing the child process requires non-trivial code, maybe platform specific. Remove the functional test and replaces it with an unit test which mocks os.waitpid() using a new _testcapi.W_STOPCODE() function to test the WIFSTOPPED() path. (cherry picked from commit 7b7c6dc)
1 parent 270c3c6 commit bc69d00

File tree

2 files changed

+43
-35
lines changed

2 files changed

+43
-35
lines changed

Lib/test/test_subprocess.py

Lines changed: 22 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
except ImportError:
3030
threading = None
3131

32+
try:
33+
import _testcapi
34+
except ImportError:
35+
_testcapi = None
36+
3237
if support.PGO:
3338
raise unittest.SkipTest("test is not helpful for PGO")
3439

@@ -2567,42 +2572,24 @@ def test_communicate_BrokenPipeError_stdin_close_with_timeout(self):
25672572
proc.communicate(timeout=999)
25682573
mock_proc_stdin.close.assert_called_once_with()
25692574

2570-
@unittest.skipIf(not ctypes, 'ctypes module required')
2571-
@unittest.skipIf(not sys.executable, 'Test requires sys.executable')
2572-
def test_child_terminated_in_stopped_state(self):
2575+
@unittest.skipUnless(_testcapi is not None
2576+
and hasattr(_testcapi, 'W_STOPCODE'),
2577+
'need _testcapi.W_STOPCODE')
2578+
def test_stopped(self):
25732579
"""Test wait() behavior when waitpid returns WIFSTOPPED; issue29335."""
2574-
PTRACE_TRACEME = 0 # From glibc and MacOS (PT_TRACE_ME).
2575-
libc_name = ctypes.util.find_library('c')
2576-
libc = ctypes.CDLL(libc_name)
2577-
if not hasattr(libc, 'ptrace'):
2578-
raise unittest.SkipTest('ptrace() required')
2579-
2580-
code = textwrap.dedent(f"""
2581-
import ctypes
2582-
import faulthandler
2583-
from test.support import SuppressCrashReport
2584-
2585-
libc = ctypes.CDLL({libc_name!r})
2586-
libc.ptrace({PTRACE_TRACEME}, 0, 0)
2587-
""")
2588-
2589-
child = subprocess.Popen([sys.executable, '-c', code])
2590-
if child.wait() != 0:
2591-
raise unittest.SkipTest('ptrace() failed - unable to test')
2592-
2593-
code += textwrap.dedent(f"""
2594-
with SuppressCrashReport():
2595-
# Crash the process
2596-
faulthandler._sigsegv()
2597-
""")
2598-
child = subprocess.Popen([sys.executable, '-c', code])
2599-
try:
2600-
returncode = child.wait()
2601-
except:
2602-
child.kill() # Clean up the hung stopped process.
2603-
raise
2604-
self.assertNotEqual(0, returncode)
2605-
self.assertLess(returncode, 0) # signal death, likely SIGSEGV.
2580+
args = [sys.executable, '-c', 'pass']
2581+
proc = subprocess.Popen(args)
2582+
2583+
# Wait until the real process completes to avoid zombie process
2584+
pid = proc.pid
2585+
pid, status = os.waitpid(pid, 0)
2586+
self.assertEqual(status, 0)
2587+
2588+
status = _testcapi.W_STOPCODE(3)
2589+
with mock.patch('subprocess.os.waitpid', return_value=(pid, status)):
2590+
returncode = proc.wait()
2591+
2592+
self.assertEqual(returncode, -3)
26062593

26072594

26082595
@unittest.skipUnless(mswindows, "Windows specific tests")

Modules/_testcapimodule.c

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@
1818
# include <winsock2.h> /* struct timeval */
1919
#endif
2020

21+
#ifdef HAVE_SYS_WAIT_H
22+
#include <sys/wait.h> /* For W_STOPCODE */
23+
#endif
24+
2125
#ifdef WITH_THREAD
2226
#include "pythread.h"
2327
#endif /* WITH_THREAD */
@@ -4148,6 +4152,20 @@ raise_SIGINT_then_send_None(PyObject *self, PyObject *args)
41484152
}
41494153

41504154

4155+
#ifdef W_STOPCODE
4156+
static PyObject*
4157+
py_w_stopcode(PyObject *self, PyObject *args)
4158+
{
4159+
int sig, status;
4160+
if (!PyArg_ParseTuple(args, "i", &sig)) {
4161+
return NULL;
4162+
}
4163+
status = W_STOPCODE(sig);
4164+
return PyLong_FromLong(status);
4165+
}
4166+
#endif
4167+
4168+
41514169
static PyMethodDef TestMethods[] = {
41524170
{"raise_exception", raise_exception, METH_VARARGS},
41534171
{"raise_memoryerror", (PyCFunction)raise_memoryerror, METH_NOARGS},
@@ -4355,6 +4373,9 @@ static PyMethodDef TestMethods[] = {
43554373
{"pyobject_fastcalldict", test_pyobject_fastcalldict, METH_VARARGS},
43564374
{"pyobject_fastcallkeywords", test_pyobject_fastcallkeywords, METH_VARARGS},
43574375
{"raise_SIGINT_then_send_None", raise_SIGINT_then_send_None, METH_VARARGS},
4376+
#ifdef W_STOPCODE
4377+
{"W_STOPCODE", py_w_stopcode, METH_VARARGS},
4378+
#endif
43584379
{NULL, NULL} /* sentinel */
43594380
};
43604381

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy