Merge pull request #3423 from deantvv/os-waitstatus-to-exitcode

os: implement `os.waitstatus_to_exitcode`
This commit is contained in:
Jeong YunWon
2021-11-07 00:00:28 +09:00
committed by GitHub
3 changed files with 119 additions and 15 deletions

View File

@@ -122,6 +122,48 @@ __all__ = [
"ALWAYS_EQ", "LARGEST", "SMALLEST"
]
# Timeout in seconds for tests using a network server listening on the network
# local loopback interface like 127.0.0.1.
#
# The timeout is long enough to prevent test failure: it takes into account
# that the client and the server can run in different threads or even different
# processes.
#
# The timeout should be long enough for connect(), recv() and send() methods
# of socket.socket.
LOOPBACK_TIMEOUT = 5.0
if sys.platform == 'win32' and ' 32 bit (ARM)' in sys.version:
# bpo-37553: test_socket.SendfileUsingSendTest is taking longer than 2
# seconds on Windows ARM32 buildbot
LOOPBACK_TIMEOUT = 10
elif sys.platform == 'vxworks':
LOOPBACK_TIMEOUT = 10
# Timeout in seconds for network requests going to the internet. The timeout is
# short enough to prevent a test to wait for too long if the internet request
# is blocked for whatever reason.
#
# Usually, a timeout using INTERNET_TIMEOUT should not mark a test as failed,
# but skip the test instead: see transient_internet().
INTERNET_TIMEOUT = 60.0
# Timeout in seconds to mark a test as failed if the test takes "too long".
#
# The timeout value depends on the regrtest --timeout command line option.
#
# If a test using SHORT_TIMEOUT starts to fail randomly on slow buildbots, use
# LONG_TIMEOUT instead.
SHORT_TIMEOUT = 30.0
# Timeout in seconds to detect when a test hangs.
#
# It is long enough to reduce the risk of test failure on the slowest Python
# buildbots. It should not be used to mark a test as failed if the test takes
# "too long". The timeout value depends on the regrtest --timeout command line
# option.
LONG_TIMEOUT = 5 * 60.0
class Error(Exception):
"""Base class for regression test exceptions."""
@@ -3378,3 +3420,54 @@ class catch_threading_exception:
del self.exc_value
del self.exc_traceback
del self.thread
def wait_process(pid, *, exitcode, timeout=None):
"""
Wait until process pid completes and check that the process exit code is
exitcode.
Raise an AssertionError if the process exit code is not equal to exitcode.
If the process runs longer than timeout seconds (SHORT_TIMEOUT by default),
kill the process (if signal.SIGKILL is available) and raise an
AssertionError. The timeout feature is not available on Windows.
"""
if os.name != "nt":
import signal
if timeout is None:
timeout = SHORT_TIMEOUT
t0 = time.monotonic()
sleep = 0.001
max_sleep = 0.1
while True:
pid2, status = os.waitpid(pid, os.WNOHANG)
if pid2 != 0:
break
# process is still running
dt = time.monotonic() - t0
if dt > SHORT_TIMEOUT:
try:
os.kill(pid, signal.SIGKILL)
os.waitpid(pid, 0)
except OSError:
# Ignore errors like ChildProcessError or PermissionError
pass
raise AssertionError(f"process {pid} is still running "
f"after {dt:.1f} seconds")
sleep = min(sleep * 2, max_sleep)
time.sleep(sleep)
else:
# Windows implementation
pid2, status = os.waitpid(pid, 0)
exitcode2 = os.waitstatus_to_exitcode(status)
if exitcode2 != exitcode:
raise AssertionError(f"process {pid} exited with code {exitcode2}, "
f"but exit code {exitcode} is expected")
# sanity check: it should not fail in practice
if pid2 != pid:
raise AssertionError(f"pid {pid2} != pid {pid}")

View File

@@ -1538,8 +1538,6 @@ class _PosixSpawnMixin:
# test_close_file() to fail.
return (sys.executable, '-I', '-S', *args)
# TODO: RUSTPYTHON: AttributeError: module 'test.support' has no attribute 'wait_process'
@unittest.expectedFailure
def test_returns_pid(self):
pidfile = support.TESTFN
self.addCleanup(support.unlink, pidfile)
@@ -1586,8 +1584,6 @@ class _PosixSpawnMixin:
with open(envfile) as f:
self.assertEqual(f.read(), 'bar')
# TODO: RUSTPYTHON: AttributeError: module 'test.support' has no attribute 'wait_process'
@unittest.expectedFailure
def test_none_file_actions(self):
pid = self.spawn_func(
self.NOOP_PROGRAM[0],
@@ -1597,8 +1593,6 @@ class _PosixSpawnMixin:
)
support.wait_process(pid, exitcode=0)
# TODO: RUSTPYTHON: AttributeError: module 'test.support' has no attribute 'wait_process'
@unittest.expectedFailure
def test_empty_file_actions(self):
pid = self.spawn_func(
self.NOOP_PROGRAM[0],
@@ -1795,8 +1789,6 @@ class _PosixSpawnMixin:
)
support.wait_process(pid, exitcode=0)
# TODO: RUSTPYTHON: AttributeError: module 'test.support' has no attribute 'wait_process'
@unittest.expectedFailure
def test_multiple_file_actions(self):
file_actions = [
(os.POSIX_SPAWN_OPEN, 3, os.path.realpath(__file__), os.O_RDONLY, 0),
@@ -1838,8 +1830,6 @@ class _PosixSpawnMixin:
3, __file__ + '\0',
os.O_RDONLY, 0)])
# TODO: RUSTPYTHON: AttributeError: module 'test.support' has no attribute 'wait_process'
@unittest.expectedFailure
def test_open_file(self):
outfile = support.TESTFN
self.addCleanup(support.unlink, outfile)
@@ -1860,7 +1850,7 @@ class _PosixSpawnMixin:
with open(outfile) as f:
self.assertEqual(f.read(), 'hello')
# TODO: RUSTPYTHON: AttributeError: module 'test.support' has no attribute 'wait_process'
# TODO: RUSTPYTHON: FileNotFoundError: [Errno 2] No such file or directory (os error 2): '@test_55144_tmp' -> 'None'
@unittest.expectedFailure
def test_close_file(self):
closefile = support.TESTFN
@@ -1881,8 +1871,6 @@ class _PosixSpawnMixin:
with open(closefile) as f:
self.assertEqual(f.read(), 'is closed %d' % errno.EBADF)
# TODO: RUSTPYTHON: AttributeError: module 'test.support' has no attribute 'wait_process'
@unittest.expectedFailure
def test_dup2(self):
dupfile = support.TESTFN
self.addCleanup(support.unlink, dupfile)
@@ -1911,8 +1899,6 @@ class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin):
class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin):
spawn_func = getattr(posix, 'posix_spawnp', None)
# TODO: RUSTPYTHON: AttributeError: module 'test.support' has no attribute 'wait_process'
@unittest.expectedFailure
@support.skip_unless_symlink
def test_posix_spawnp(self):
# Use a symlink to create a program in its own temporary directory

View File

@@ -1668,6 +1668,31 @@ pub(super) mod _os {
Ok((loadavg[0], loadavg[1], loadavg[2]))
}
#[cfg(any(unix, windows))]
#[pyfunction]
fn waitstatus_to_exitcode(status: i32, vm: &VirtualMachine) -> PyResult<i32> {
let status = u32::try_from(status)
.map_err(|_| vm.new_value_error(format!("invalid WEXITSTATUS: {}", status)))?;
cfg_if::cfg_if! {
if #[cfg(not(windows))] {
let status = status as libc::c_int;
if libc::WIFEXITED(status) {
return Ok(libc::WEXITSTATUS(status));
}
if libc::WIFSIGNALED(status) {
return Ok(-libc::WTERMSIG(status));
}
Err(vm.new_value_error(format!("Invalid wait status: {}", status)))
} else {
i32::try_from(status.rotate_right(8))
.map_err(|_| vm.new_value_error(format!("invalid wait status: {}", status)))
}
}
}
#[pyattr]
#[pyclass(module = "os", name = "terminal_size")]
#[derive(PyStructSequence)]