2015-02-07 09:27:50 -04:00
|
|
|
|
"""
|
|
|
|
|
This test suite exercises some system calls subject to interruption with EINTR,
|
|
|
|
|
to check that it is actually handled transparently.
|
|
|
|
|
It is intended to be run by the main test suite within a child process, to
|
|
|
|
|
ensure there is no background thread running (so that signals are delivered to
|
|
|
|
|
the correct thread).
|
|
|
|
|
Signals are generated in-process using setitimer(ITIMER_REAL), which allows
|
|
|
|
|
sub-second periodicity (contrarily to signal()).
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import io
|
|
|
|
|
import os
|
|
|
|
|
import signal
|
|
|
|
|
import socket
|
|
|
|
|
import time
|
|
|
|
|
import unittest
|
|
|
|
|
|
|
|
|
|
from test import support
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
|
|
|
|
|
class EINTRBaseTest(unittest.TestCase):
|
|
|
|
|
""" Base class for EINTR tests. """
|
|
|
|
|
|
|
|
|
|
# delay for initial signal delivery
|
|
|
|
|
signal_delay = 0.1
|
|
|
|
|
# signal delivery periodicity
|
|
|
|
|
signal_period = 0.1
|
|
|
|
|
# default sleep time for tests - should obviously have:
|
|
|
|
|
# sleep_time > signal_period
|
|
|
|
|
sleep_time = 0.2
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def setUpClass(cls):
|
|
|
|
|
cls.orig_handler = signal.signal(signal.SIGALRM, lambda *args: None)
|
|
|
|
|
signal.setitimer(signal.ITIMER_REAL, cls.signal_delay,
|
|
|
|
|
cls.signal_period)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def tearDownClass(cls):
|
|
|
|
|
signal.setitimer(signal.ITIMER_REAL, 0, 0)
|
|
|
|
|
signal.signal(signal.SIGALRM, cls.orig_handler)
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def _sleep(cls):
|
|
|
|
|
# default sleep time
|
|
|
|
|
time.sleep(cls.sleep_time)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
|
|
|
|
|
class OSEINTRTest(EINTRBaseTest):
|
|
|
|
|
""" EINTR tests for the os module. """
|
|
|
|
|
|
|
|
|
|
def _test_wait_multiple(self, wait_func):
|
|
|
|
|
num = 3
|
|
|
|
|
for _ in range(num):
|
|
|
|
|
pid = os.fork()
|
|
|
|
|
if pid == 0:
|
|
|
|
|
self._sleep()
|
|
|
|
|
os._exit(0)
|
|
|
|
|
for _ in range(num):
|
|
|
|
|
wait_func()
|
|
|
|
|
|
|
|
|
|
def test_wait(self):
|
|
|
|
|
self._test_wait_multiple(os.wait)
|
|
|
|
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'wait3'), 'requires wait3()')
|
|
|
|
|
def test_wait3(self):
|
|
|
|
|
self._test_wait_multiple(lambda: os.wait3(0))
|
|
|
|
|
|
|
|
|
|
def _test_wait_single(self, wait_func):
|
|
|
|
|
pid = os.fork()
|
|
|
|
|
if pid == 0:
|
|
|
|
|
self._sleep()
|
|
|
|
|
os._exit(0)
|
|
|
|
|
else:
|
|
|
|
|
wait_func(pid)
|
|
|
|
|
|
|
|
|
|
def test_waitpid(self):
|
|
|
|
|
self._test_wait_single(lambda pid: os.waitpid(pid, 0))
|
|
|
|
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'wait4'), 'requires wait4()')
|
|
|
|
|
def test_wait4(self):
|
|
|
|
|
self._test_wait_single(lambda pid: os.wait4(pid, 0))
|
|
|
|
|
|
|
|
|
|
def test_read(self):
|
|
|
|
|
rd, wr = os.pipe()
|
|
|
|
|
self.addCleanup(os.close, rd)
|
|
|
|
|
# wr closed explicitly by parent
|
|
|
|
|
|
|
|
|
|
# the payload below are smaller than PIPE_BUF, hence the writes will be
|
|
|
|
|
# atomic
|
|
|
|
|
datas = [b"hello", b"world", b"spam"]
|
|
|
|
|
|
|
|
|
|
pid = os.fork()
|
|
|
|
|
if pid == 0:
|
|
|
|
|
os.close(rd)
|
|
|
|
|
for data in datas:
|
|
|
|
|
# let the parent block on read()
|
|
|
|
|
self._sleep()
|
|
|
|
|
os.write(wr, data)
|
|
|
|
|
os._exit(0)
|
|
|
|
|
else:
|
|
|
|
|
self.addCleanup(os.waitpid, pid, 0)
|
|
|
|
|
os.close(wr)
|
|
|
|
|
for data in datas:
|
|
|
|
|
self.assertEqual(data, os.read(rd, len(data)))
|
|
|
|
|
|
|
|
|
|
def test_write(self):
|
|
|
|
|
rd, wr = os.pipe()
|
|
|
|
|
self.addCleanup(os.close, wr)
|
|
|
|
|
# rd closed explicitly by parent
|
|
|
|
|
|
|
|
|
|
# we must write enough data for the write() to block
|
|
|
|
|
data = b"xyz" * support.PIPE_MAX_SIZE
|
|
|
|
|
|
|
|
|
|
pid = os.fork()
|
|
|
|
|
if pid == 0:
|
|
|
|
|
os.close(wr)
|
|
|
|
|
read_data = io.BytesIO()
|
|
|
|
|
# let the parent block on write()
|
|
|
|
|
self._sleep()
|
|
|
|
|
while len(read_data.getvalue()) < len(data):
|
|
|
|
|
chunk = os.read(rd, 2 * len(data))
|
|
|
|
|
read_data.write(chunk)
|
|
|
|
|
self.assertEqual(read_data.getvalue(), data)
|
|
|
|
|
os._exit(0)
|
|
|
|
|
else:
|
|
|
|
|
os.close(rd)
|
|
|
|
|
written = 0
|
|
|
|
|
while written < len(data):
|
|
|
|
|
written += os.write(wr, memoryview(data)[written:])
|
|
|
|
|
self.assertEqual(0, os.waitpid(pid, 0)[1])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
|
|
|
|
|
class SocketEINTRTest(EINTRBaseTest):
|
|
|
|
|
""" EINTR tests for the socket module. """
|
|
|
|
|
|
|
|
|
|
@unittest.skipUnless(hasattr(socket, 'socketpair'), 'needs socketpair()')
|
|
|
|
|
def _test_recv(self, recv_func):
|
|
|
|
|
rd, wr = socket.socketpair()
|
|
|
|
|
self.addCleanup(rd.close)
|
|
|
|
|
# wr closed explicitly by parent
|
|
|
|
|
|
|
|
|
|
# single-byte payload guard us against partial recv
|
|
|
|
|
datas = [b"x", b"y", b"z"]
|
|
|
|
|
|
|
|
|
|
pid = os.fork()
|
|
|
|
|
if pid == 0:
|
|
|
|
|
rd.close()
|
|
|
|
|
for data in datas:
|
|
|
|
|
# let the parent block on recv()
|
|
|
|
|
self._sleep()
|
|
|
|
|
wr.sendall(data)
|
|
|
|
|
os._exit(0)
|
|
|
|
|
else:
|
|
|
|
|
self.addCleanup(os.waitpid, pid, 0)
|
|
|
|
|
wr.close()
|
|
|
|
|
for data in datas:
|
|
|
|
|
self.assertEqual(data, recv_func(rd, len(data)))
|
|
|
|
|
|
|
|
|
|
def test_recv(self):
|
|
|
|
|
self._test_recv(socket.socket.recv)
|
|
|
|
|
|
|
|
|
|
@unittest.skipUnless(hasattr(socket.socket, 'recvmsg'), 'needs recvmsg()')
|
|
|
|
|
def test_recvmsg(self):
|
|
|
|
|
self._test_recv(lambda sock, data: sock.recvmsg(data)[0])
|
|
|
|
|
|
|
|
|
|
def _test_send(self, send_func):
|
|
|
|
|
rd, wr = socket.socketpair()
|
|
|
|
|
self.addCleanup(wr.close)
|
|
|
|
|
# rd closed explicitly by parent
|
|
|
|
|
|
|
|
|
|
# we must send enough data for the send() to block
|
|
|
|
|
data = b"xyz" * (support.SOCK_MAX_SIZE // 3)
|
|
|
|
|
|
|
|
|
|
pid = os.fork()
|
|
|
|
|
if pid == 0:
|
|
|
|
|
wr.close()
|
|
|
|
|
# let the parent block on send()
|
|
|
|
|
self._sleep()
|
|
|
|
|
received_data = bytearray(len(data))
|
|
|
|
|
n = 0
|
|
|
|
|
while n < len(data):
|
|
|
|
|
n += rd.recv_into(memoryview(received_data)[n:])
|
|
|
|
|
self.assertEqual(received_data, data)
|
|
|
|
|
os._exit(0)
|
|
|
|
|
else:
|
|
|
|
|
rd.close()
|
|
|
|
|
written = 0
|
|
|
|
|
while written < len(data):
|
|
|
|
|
sent = send_func(wr, memoryview(data)[written:])
|
|
|
|
|
# sendall() returns None
|
|
|
|
|
written += len(data) if sent is None else sent
|
|
|
|
|
self.assertEqual(0, os.waitpid(pid, 0)[1])
|
|
|
|
|
|
|
|
|
|
def test_send(self):
|
|
|
|
|
self._test_send(socket.socket.send)
|
|
|
|
|
|
|
|
|
|
def test_sendall(self):
|
|
|
|
|
self._test_send(socket.socket.sendall)
|
|
|
|
|
|
|
|
|
|
@unittest.skipUnless(hasattr(socket.socket, 'sendmsg'), 'needs sendmsg()')
|
|
|
|
|
def test_sendmsg(self):
|
|
|
|
|
self._test_send(lambda sock, data: sock.sendmsg([data]))
|
|
|
|
|
|
|
|
|
|
def test_accept(self):
|
|
|
|
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
|
self.addCleanup(sock.close)
|
|
|
|
|
|
|
|
|
|
sock.bind((support.HOST, 0))
|
|
|
|
|
_, port = sock.getsockname()
|
|
|
|
|
sock.listen()
|
|
|
|
|
|
|
|
|
|
pid = os.fork()
|
|
|
|
|
if pid == 0:
|
|
|
|
|
# let parent block on accept()
|
|
|
|
|
self._sleep()
|
|
|
|
|
with socket.create_connection((support.HOST, port)):
|
|
|
|
|
self._sleep()
|
|
|
|
|
os._exit(0)
|
|
|
|
|
else:
|
|
|
|
|
self.addCleanup(os.waitpid, pid, 0)
|
|
|
|
|
client_sock, _ = sock.accept()
|
|
|
|
|
client_sock.close()
|
|
|
|
|
|
|
|
|
|
@unittest.skipUnless(hasattr(os, 'mkfifo'), 'needs mkfifo()')
|
|
|
|
|
def _test_open(self, do_open_close_reader, do_open_close_writer):
|
|
|
|
|
# Use a fifo: until the child opens it for reading, the parent will
|
|
|
|
|
# block when trying to open it for writing.
|
|
|
|
|
support.unlink(support.TESTFN)
|
|
|
|
|
os.mkfifo(support.TESTFN)
|
|
|
|
|
self.addCleanup(support.unlink, support.TESTFN)
|
|
|
|
|
|
|
|
|
|
pid = os.fork()
|
|
|
|
|
if pid == 0:
|
|
|
|
|
# let the parent block
|
|
|
|
|
self._sleep()
|
|
|
|
|
do_open_close_reader(support.TESTFN)
|
|
|
|
|
os._exit(0)
|
|
|
|
|
else:
|
|
|
|
|
self.addCleanup(os.waitpid, pid, 0)
|
|
|
|
|
do_open_close_writer(support.TESTFN)
|
|
|
|
|
|
|
|
|
|
def test_open(self):
|
|
|
|
|
self._test_open(lambda path: open(path, 'r').close(),
|
|
|
|
|
lambda path: open(path, 'w').close())
|
|
|
|
|
|
|
|
|
|
def test_os_open(self):
|
|
|
|
|
self._test_open(lambda path: os.close(os.open(path, os.O_RDONLY)),
|
|
|
|
|
lambda path: os.close(os.open(path, os.O_WRONLY)))
|
|
|
|
|
|
|
|
|
|
|
2015-03-19 17:54:09 -03:00
|
|
|
|
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
|
|
|
|
|
class TimeEINTRTest(EINTRBaseTest):
|
|
|
|
|
""" EINTR tests for the time module. """
|
|
|
|
|
|
|
|
|
|
def test_sleep(self):
|
|
|
|
|
t0 = time.monotonic()
|
2015-03-19 21:42:20 -03:00
|
|
|
|
time.sleep(self.sleep_time)
|
2015-03-19 17:54:09 -03:00
|
|
|
|
signal.alarm(0)
|
|
|
|
|
dt = time.monotonic() - t0
|
2015-03-19 21:42:20 -03:00
|
|
|
|
self.assertGreaterEqual(dt, self.sleep_time)
|
2015-03-19 17:54:09 -03:00
|
|
|
|
|
|
|
|
|
|
2015-03-20 08:54:28 -03:00
|
|
|
|
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
|
|
|
|
|
class SignalEINTRTest(EINTRBaseTest):
|
|
|
|
|
""" EINTR tests for the signal module. """
|
|
|
|
|
|
2015-03-20 09:42:52 -03:00
|
|
|
|
@unittest.skipUnless(hasattr(signal, 'sigtimedwait'),
|
|
|
|
|
'need signal.sigtimedwait()')
|
2015-03-20 08:54:28 -03:00
|
|
|
|
def test_sigtimedwait(self):
|
|
|
|
|
t0 = time.monotonic()
|
2015-03-27 10:32:22 -03:00
|
|
|
|
signal.sigtimedwait([signal.SIGUSR1], self.sleep_time)
|
2015-03-20 08:54:28 -03:00
|
|
|
|
dt = time.monotonic() - t0
|
|
|
|
|
self.assertGreaterEqual(dt, self.sleep_time)
|
|
|
|
|
|
2015-03-20 09:42:52 -03:00
|
|
|
|
@unittest.skipUnless(hasattr(signal, 'sigwaitinfo'),
|
|
|
|
|
'need signal.sigwaitinfo()')
|
2015-03-20 08:54:28 -03:00
|
|
|
|
def test_sigwaitinfo(self):
|
|
|
|
|
signum = signal.SIGUSR1
|
|
|
|
|
pid = os.getpid()
|
|
|
|
|
|
|
|
|
|
old_handler = signal.signal(signum, lambda *args: None)
|
|
|
|
|
self.addCleanup(signal.signal, signum, old_handler)
|
|
|
|
|
|
|
|
|
|
t0 = time.monotonic()
|
|
|
|
|
child_pid = os.fork()
|
|
|
|
|
if child_pid == 0:
|
|
|
|
|
# child
|
|
|
|
|
try:
|
|
|
|
|
self._sleep()
|
|
|
|
|
os.kill(pid, signum)
|
|
|
|
|
finally:
|
|
|
|
|
os._exit(0)
|
|
|
|
|
else:
|
|
|
|
|
# parent
|
|
|
|
|
signal.sigwaitinfo([signum])
|
|
|
|
|
dt = time.monotonic() - t0
|
|
|
|
|
os.waitpid(child_pid, 0)
|
|
|
|
|
|
|
|
|
|
self.assertGreaterEqual(dt, self.sleep_time)
|
|
|
|
|
|
|
|
|
|
|
2015-02-07 09:27:50 -04:00
|
|
|
|
def test_main():
|
2015-03-19 17:54:09 -03:00
|
|
|
|
support.run_unittest(
|
|
|
|
|
OSEINTRTest,
|
|
|
|
|
SocketEINTRTest,
|
2015-03-20 08:54:28 -03:00
|
|
|
|
TimeEINTRTest,
|
|
|
|
|
SignalEINTRTest)
|
2015-02-07 09:27:50 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
test_main()
|