Skip to content

gh-129205: Modernize test_eintr #129316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 27, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 48 additions & 69 deletions Lib/test/_test_eintr.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ class OSEINTRTest(EINTRBaseTest):
""" EINTR tests for the os module. """

def new_sleep_process(self):
code = 'import time; time.sleep(%r)' % self.sleep_time
code = f'import time; time.sleep({self.sleep_time!r})'
return self.subprocess(code)

def _test_wait_multiple(self, wait_func):
Expand Down Expand Up @@ -123,65 +123,45 @@ def test_waitpid(self):
def test_wait4(self):
self._test_wait_single(lambda pid: os.wait4(pid, 0))

def test_read(self):
def _interrupted_reads(self):
"""Make a fd which will force block on read of expected bytes."""
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"]

This comment was marked as duplicate.

data = [b"hello", b"world", b"spam"]

code = '\n'.join((
'import os, sys, time',
'',
'wr = int(sys.argv[1])',
'datas = %r' % datas,
'sleep_time = %r' % self.sleep_time,
f'data = {data!r}',
f'sleep_time = {self.sleep_time!r}',
'',
'for data in datas:',
'for item in data:',
' # let the parent block on read()',
' time.sleep(sleep_time)',
' os.write(wr, data)',
' os.write(wr, item)',
))

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)))
for datum in data:
yield rd, datum
self.assertEqual(proc.wait(), 0)

def test_readinto(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"]

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)',
))
def test_read(self):
for fd, expected in self._interrupted_reads():
self.assertEqual(expected, os.read(fd, len(expected)))

proc = self.subprocess(code, str(wr), pass_fds=[wr])
with kill_on_error(proc):
os.close(wr)
for data in datas:
buffer = bytearray(len(data))
self.assertEqual(os.readinto(rd, buffer), len(data))
self.assertEqual(buffer, data)
self.assertEqual(proc.wait(), 0)
def test_readinto(self):
for fd, expected in self._interrupted_reads():
buffer = bytearray(len(expected))
self.assertEqual(os.readinto(fd, buffer), len(expected))
self.assertEqual(buffer, expected)

def test_write(self):
rd, wr = os.pipe()
Expand All @@ -195,8 +175,8 @@ def test_write(self):
'import io, os, sys, time',
'',
'rd = int(sys.argv[1])',
'sleep_time = %r' % self.sleep_time,
'data = b"x" * %s' % support.PIPE_MAX_SIZE,
f'sleep_time = {self.sleep_time!r}',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: would it make sense to centralize the "make a string which sets sleep_time" (f'sleep_time = {self.sleep_time!r}') rather than the number of copies? (Possibly also data as well?)

Definitely nice having all the code for a test case in one place though, but feels fairly templated/repeated in structure at the moment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that it's worth it to have a helper just to format f'sleep_time = {self.sleep_time!r}' line.

f'data = b"x" * {support.PIPE_MAX_SIZE}',
'data_len = len(data)',
'',
'# let the parent block on write()',
Expand All @@ -209,8 +189,8 @@ def test_write(self):
'',
'value = read_data.getvalue()',
'if value != data:',
' raise Exception("read error: %s vs %s bytes"',
' % (len(value), data_len))',
' raise Exception(f"read error: {len(value)}'
' vs {data_len} bytes")',
))

proc = self.subprocess(code, str(rd), pass_fds=[rd])
Expand All @@ -233,33 +213,33 @@ def _test_recv(self, recv_func):
# wr closed explicitly by parent

# single-byte payload guard us against partial recv
datas = [b"x", b"y", b"z"]
data = [b"x", b"y", b"z"]

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,
f'family = {int(wr.family)}',
f'sock_type = {int(wr.type)}',
f'data = {data!r}',
f'sleep_time = {self.sleep_time!r}',
'',
'wr = socket.fromfd(fd, family, sock_type)',
'os.close(fd)',
'',
'with wr:',
' for data in datas:',
' for item in data:',
' # let the parent block on recv()',
' time.sleep(sleep_time)',
' wr.sendall(data)',
' wr.sendall(item)',
))

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)))
for item in data:
self.assertEqual(item, recv_func(rd, len(item)))
self.assertEqual(proc.wait(), 0)

def test_recv(self):
Expand All @@ -281,10 +261,10 @@ def _test_send(self, send_func):
'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),
f'family = {int(rd.family)}',
f'sock_type = {int(rd.type)}',
f'sleep_time = {self.sleep_time!r}',
f'data = b"xyz" * {support.SOCK_MAX_SIZE // 3}',
'data_len = len(data)',
'',
'rd = socket.fromfd(fd, family, sock_type)',
Expand All @@ -300,8 +280,8 @@ def _test_send(self, send_func):
' 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))',
' raise Exception(f"recv error: {len(received_data)}'
' vs {data_len} bytes")',
))

fd = rd.fileno()
Expand Down Expand Up @@ -333,9 +313,9 @@ def test_accept(self):
code = '\n'.join((
'import socket, time',
'',
'host = %r' % socket_helper.HOST,
'port = %s' % port,
'sleep_time = %r' % self.sleep_time,
f'host = {socket_helper.HOST!r}',
f'port = {port}',
f'sleep_time = {self.sleep_time!r}',
'',
'# let parent block on accept()',
'time.sleep(sleep_time)',
Expand Down Expand Up @@ -363,15 +343,15 @@ def _test_open(self, do_open_close_reader, do_open_close_writer):
os_helper.unlink(filename)
try:
os.mkfifo(filename)
except PermissionError as e:
self.skipTest('os.mkfifo(): %s' % e)
except PermissionError as exc:
self.skipTest(f'os.mkfifo(): {exc!r}')
self.addCleanup(os_helper.unlink, filename)

code = '\n'.join((
'import os, time',
'',
'path = %a' % filename,
'sleep_time = %r' % self.sleep_time,
f'path = {filename!a}',
f'sleep_time = {self.sleep_time!r}',
'',
'# let the parent block',
'time.sleep(sleep_time)',
Expand Down Expand Up @@ -427,21 +407,20 @@ class SignalEINTRTest(EINTRBaseTest):

def check_sigwait(self, wait_func):
signum = signal.SIGUSR1
pid = os.getpid()

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,
f'pid = {os.getpid()}',
f'signum = {int(signum)}',
f'sleep_time = {self.sleep_time!r}',
'time.sleep(sleep_time)',
'os.kill(pid, signum)',
))

old_mask = signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
self.addCleanup(signal.pthread_sigmask, signal.SIG_UNBLOCK, [signum])

proc = self.subprocess(code)
Expand Down
Loading