summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2015-09-18 09:29:16 (GMT)
committerVictor Stinner <victor.stinner@gmail.com>2015-09-18 09:29:16 (GMT)
commit4b352171d2b0a4a63cd711df9ebe840419137fa2 (patch)
treee9005271b7c405b2ec9ecb4b51ffb89fcdef24e5
parentcd6e69439c908a7d3aa0a5b13570c83d7cc57bf7 (diff)
downloadcpython-4b352171d2b0a4a63cd711df9ebe840419137fa2.zip
cpython-4b352171d2b0a4a63cd711df9ebe840419137fa2.tar.gz
cpython-4b352171d2b0a4a63cd711df9ebe840419137fa2.tar.bz2
Issue #25122: sync test_eintr with Python 3.6
* test_eintr: support verbose mode, don't redirect eintr_tester output into a pipe * eintr_tester: replace os.fork() with subprocess to have a cleaner child process (ex: don't inherit setitimer()) * eintr_tester: kill the process if the unit test fails * test_open/test_os_open(): write support.PIPE_MAX_SIZE bytes instead of support.PIPE_MAX_SIZE*3 bytes
-rw-r--r--Lib/test/eintrdata/eintr_tester.py287
-rw-r--r--Lib/test/test_eintr.py11
2 files changed, 200 insertions, 98 deletions
diff --git a/Lib/test/eintrdata/eintr_tester.py b/Lib/test/eintrdata/eintr_tester.py
index f755880..c2407ca 100644
--- a/Lib/test/eintrdata/eintr_tester.py
+++ b/Lib/test/eintrdata/eintr_tester.py
@@ -8,16 +8,29 @@ Signals are generated in-process using setitimer(ITIMER_REAL), which allows
sub-second periodicity (contrarily to signal()).
"""
+import contextlib
import io
import os
import select
import signal
import socket
+import subprocess
+import sys
import time
import unittest
from test import support
+@contextlib.contextmanager
+def kill_on_error(proc):
+ """Context manager killing the subprocess if a Python exception is raised."""
+ with proc:
+ try:
+ yield proc
+ except:
+ proc.kill()
+ raise
+
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
class EINTRBaseTest(unittest.TestCase):
@@ -28,7 +41,7 @@ class EINTRBaseTest(unittest.TestCase):
# signal delivery periodicity
signal_period = 0.1
# default sleep time for tests - should obviously have:
- # sleep_time > signal_period
+ # sleep_time > signal_period
sleep_time = 0.2
@classmethod
@@ -51,18 +64,22 @@ class EINTRBaseTest(unittest.TestCase):
# default sleep time
time.sleep(cls.sleep_time)
+ def subprocess(self, *args, **kw):
+ cmd_args = (sys.executable, '-c') + args
+ return subprocess.Popen(cmd_args, **kw)
+
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
class OSEINTRTest(EINTRBaseTest):
""" EINTR tests for the os module. """
+ def new_sleep_process(self):
+ code = 'import time; time.sleep(%r)' % self.sleep_time
+ return self.subprocess(code)
+
def _test_wait_multiple(self, wait_func):
num = 3
- for _ in range(num):
- pid = os.fork()
- if pid == 0:
- self._sleep()
- os._exit(0)
+ processes = [self.new_sleep_process() for _ in range(num)]
for _ in range(num):
wait_func()
@@ -74,12 +91,8 @@ class OSEINTRTest(EINTRBaseTest):
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)
+ proc = self.new_sleep_process()
+ wait_func(proc.pid)
def test_waitpid(self):
self._test_wait_single(lambda pid: os.waitpid(pid, 0))
@@ -97,19 +110,25 @@ class OSEINTRTest(EINTRBaseTest):
# 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)
+ code = '\n'.join((
+ 'import os, sys, time',
+ '',
+ 'wr = int(sys.argv[1])',
+ 'datas = %r' % datas,
+ 'sleep_time = %r' % self.sleep_time,
+ '',
+ 'for data in datas:',
+ ' # let the parent block on read()',
+ ' time.sleep(sleep_time)',
+ ' os.write(wr, data)',
+ ))
+
+ proc = self.subprocess(code, str(wr), pass_fds=[wr])
+ with kill_on_error(proc):
os.close(wr)
for data in datas:
self.assertEqual(data, os.read(rd, len(data)))
+ self.assertEqual(proc.wait(), 0)
def test_write(self):
rd, wr = os.pipe()
@@ -117,25 +136,37 @@ class OSEINTRTest(EINTRBaseTest):
# 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:
+ data = b"x" * support.PIPE_MAX_SIZE
+
+ code = '\n'.join((
+ 'import io, os, sys, time',
+ '',
+ 'rd = int(sys.argv[1])',
+ 'sleep_time = %r' % self.sleep_time,
+ 'data = b"x" * %s' % support.PIPE_MAX_SIZE,
+ 'data_len = len(data)',
+ '',
+ '# let the parent block on write()',
+ 'time.sleep(sleep_time)',
+ '',
+ 'read_data = io.BytesIO()',
+ 'while len(read_data.getvalue()) < data_len:',
+ ' chunk = os.read(rd, 2 * data_len)',
+ ' read_data.write(chunk)',
+ '',
+ 'value = read_data.getvalue()',
+ 'if value != data:',
+ ' raise Exception("read error: %s vs %s bytes"',
+ ' % (len(value), data_len))',
+ ))
+
+ proc = self.subprocess(code, str(rd), pass_fds=[rd])
+ with kill_on_error(proc):
os.close(rd)
written = 0
while written < len(data):
written += os.write(wr, memoryview(data)[written:])
- self.assertEqual(0, os.waitpid(pid, 0)[1])
+ self.assertEqual(proc.wait(), 0)
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
@@ -151,19 +182,32 @@ class SocketEINTRTest(EINTRBaseTest):
# 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)
+ code = '\n'.join((
+ 'import os, socket, sys, time',
+ '',
+ 'fd = int(sys.argv[1])',
+ 'family = %s' % int(wr.family),
+ 'sock_type = %s' % int(wr.type),
+ 'datas = %r' % datas,
+ 'sleep_time = %r' % self.sleep_time,
+ '',
+ 'wr = socket.fromfd(fd, family, sock_type)',
+ 'os.close(fd)',
+ '',
+ 'with wr:',
+ ' for data in datas:',
+ ' # let the parent block on recv()',
+ ' time.sleep(sleep_time)',
+ ' wr.sendall(data)',
+ ))
+
+ fd = wr.fileno()
+ proc = self.subprocess(code, str(fd), pass_fds=[fd])
+ with kill_on_error(proc):
wr.close()
for data in datas:
self.assertEqual(data, recv_func(rd, len(data)))
+ self.assertEqual(proc.wait(), 0)
def test_recv(self):
self._test_recv(socket.socket.recv)
@@ -180,25 +224,43 @@ class SocketEINTRTest(EINTRBaseTest):
# 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:
+ code = '\n'.join((
+ 'import os, socket, sys, time',
+ '',
+ 'fd = int(sys.argv[1])',
+ 'family = %s' % int(rd.family),
+ 'sock_type = %s' % int(rd.type),
+ 'sleep_time = %r' % self.sleep_time,
+ 'data = b"xyz" * %s' % (support.SOCK_MAX_SIZE // 3),
+ 'data_len = len(data)',
+ '',
+ 'rd = socket.fromfd(fd, family, sock_type)',
+ 'os.close(fd)',
+ '',
+ 'with rd:',
+ ' # let the parent block on send()',
+ ' time.sleep(sleep_time)',
+ '',
+ ' received_data = bytearray(data_len)',
+ ' n = 0',
+ ' while n < data_len:',
+ ' n += rd.recv_into(memoryview(received_data)[n:])',
+ '',
+ 'if received_data != data:',
+ ' raise Exception("recv error: %s vs %s bytes"',
+ ' % (len(received_data), data_len))',
+ ))
+
+ fd = rd.fileno()
+ proc = self.subprocess(code, str(fd), pass_fds=[fd])
+ with kill_on_error(proc):
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])
+ self.assertEqual(proc.wait(), 0)
def test_send(self):
self._test_send(socket.socket.send)
@@ -215,46 +277,75 @@ class SocketEINTRTest(EINTRBaseTest):
self.addCleanup(sock.close)
sock.bind((support.HOST, 0))
- _, port = sock.getsockname()
+ port = sock.getsockname()[1]
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)
+ code = '\n'.join((
+ 'import socket, time',
+ '',
+ 'host = %r' % support.HOST,
+ 'port = %s' % port,
+ 'sleep_time = %r' % self.sleep_time,
+ '',
+ '# let parent block on accept()',
+ 'time.sleep(sleep_time)',
+ 'with socket.create_connection((host, port)):',
+ ' time.sleep(sleep_time)',
+ ))
+
+ proc = self.subprocess(code)
+ with kill_on_error(proc):
client_sock, _ = sock.accept()
client_sock.close()
+ self.assertEqual(proc.wait(), 0)
+ # Issue #25122: There is a race condition in the FreeBSD kernel on
+ # handling signals in the FIFO device. Skip the test until the bug is
+ # fixed in the kernel. Bet that the bug will be fixed in FreeBSD 11.
+ # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=203162
+ @support.requires_freebsd_version(11)
@unittest.skipUnless(hasattr(os, 'mkfifo'), 'needs mkfifo()')
def _test_open(self, do_open_close_reader, do_open_close_writer):
+ filename = support.TESTFN
+
# 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)
+ support.unlink(filename)
+ os.mkfifo(filename)
+ self.addCleanup(support.unlink, filename)
+
+ code = '\n'.join((
+ 'import os, time',
+ '',
+ 'path = %a' % filename,
+ 'sleep_time = %r' % self.sleep_time,
+ '',
+ '# let the parent block',
+ 'time.sleep(sleep_time)',
+ '',
+ do_open_close_reader,
+ ))
+
+ proc = self.subprocess(code)
+ with kill_on_error(proc):
+ do_open_close_writer(filename)
+ self.assertEqual(proc.wait(), 0)
+
+ def python_open(self, path):
+ fp = open(path, 'w')
+ fp.close()
def test_open(self):
- self._test_open(lambda path: open(path, 'r').close(),
- lambda path: open(path, 'w').close())
+ self._test_open("fp = open(path, 'r')\nfp.close()",
+ self.python_open)
+
+ def os_open(self, path):
+ fd = os.open(path, os.O_WRONLY)
+ os.close(fd)
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)))
+ self._test_open("fd = os.open(path, os.O_RDONLY)\nos.close(fd)",
+ self.os_open)
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
@@ -290,20 +381,22 @@ class SignalEINTRTest(EINTRBaseTest):
old_handler = signal.signal(signum, lambda *args: None)
self.addCleanup(signal.signal, signum, old_handler)
+ code = '\n'.join((
+ 'import os, time',
+ 'pid = %s' % os.getpid(),
+ 'signum = %s' % int(signum),
+ 'sleep_time = %r' % self.sleep_time,
+ 'time.sleep(sleep_time)',
+ 'os.kill(pid, signum)',
+ ))
+
t0 = time.monotonic()
- child_pid = os.fork()
- if child_pid == 0:
- # child
- try:
- self._sleep()
- os.kill(pid, signum)
- finally:
- os._exit(0)
- else:
+ proc = self.subprocess(code)
+ with kill_on_error(proc):
# parent
signal.sigwaitinfo([signum])
dt = time.monotonic() - t0
- os.waitpid(child_pid, 0)
+ self.assertEqual(proc.wait(), 0)
self.assertGreaterEqual(dt, self.sleep_time)
diff --git a/Lib/test/test_eintr.py b/Lib/test/test_eintr.py
index 111ead3..aabad83 100644
--- a/Lib/test/test_eintr.py
+++ b/Lib/test/test_eintr.py
@@ -1,5 +1,7 @@
import os
import signal
+import subprocess
+import sys
import unittest
from test import support
@@ -14,7 +16,14 @@ class EINTRTests(unittest.TestCase):
# Run the tester in a sub-process, to make sure there is only one
# thread (for reliable signal delivery).
tester = support.findfile("eintr_tester.py", subdir="eintrdata")
- script_helper.assert_python_ok(tester)
+
+ if support.verbose:
+ args = [sys.executable, tester]
+ with subprocess.Popen(args) as proc:
+ exitcode = proc.wait()
+ self.assertEqual(exitcode, 0)
+ else:
+ script_helper.assert_python_ok(tester)
if __name__ == "__main__":