Skip to content

Commit a35e31e

Browse files
pythongh-132742: Improve tests for fcntl.ioctl()
* Do not skip ALL ioctl() tests when /dev/tty is not available. * Use better tests for integer argument. * Add also parallel tests for tcflush() and tcflow().
1 parent 09b624b commit a35e31e

File tree

2 files changed

+90
-34
lines changed

2 files changed

+90
-34
lines changed

Lib/test/test_ioctl.py

Lines changed: 60 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,35 @@
11
import array
2+
import os
3+
import struct
4+
import threading
25
import unittest
36
from test.support import get_attribute
7+
from test.support import threading_helper
48
from test.support.import_helper import import_module
5-
import os, struct
69
fcntl = import_module('fcntl')
710
termios = import_module('termios')
8-
get_attribute(termios, 'TIOCGPGRP') #Can't run tests without this feature
9-
10-
try:
11-
tty = open("/dev/tty", "rb")
12-
except OSError:
13-
raise unittest.SkipTest("Unable to open /dev/tty")
14-
else:
15-
with tty:
16-
# Skip if another process is in foreground
17-
r = fcntl.ioctl(tty, termios.TIOCGPGRP, struct.pack("i", 0))
18-
rpgrp = struct.unpack("i", r)[0]
19-
if rpgrp not in (os.getpgrp(), os.getsid(0)):
20-
raise unittest.SkipTest("Neither the process group nor the session "
21-
"are attached to /dev/tty")
22-
del tty, r, rpgrp
2311

2412
try:
2513
import pty
2614
except ImportError:
2715
pty = None
2816

29-
class IoctlTests(unittest.TestCase):
17+
class IoctlTestsTty(unittest.TestCase):
18+
@classmethod
19+
def setUpClass(cls):
20+
TIOCGPGRP = get_attribute(termios, 'TIOCGPGRP')
21+
try:
22+
tty = open("/dev/tty", "rb")
23+
except OSError:
24+
raise unittest.SkipTest("Unable to open /dev/tty")
25+
with tty:
26+
# Skip if another process is in foreground
27+
r = fcntl.ioctl(tty, TIOCGPGRP, struct.pack("i", 0))
28+
rpgrp = struct.unpack("i", r)[0]
29+
if rpgrp not in (os.getpgrp(), os.getsid(0)):
30+
raise unittest.SkipTest("Neither the process group nor the session "
31+
"are attached to /dev/tty")
32+
3033
def test_ioctl_immutable_buf(self):
3134
# If this process has been put into the background, TIOCGPGRP returns
3235
# the session ID instead of the process group id.
@@ -132,23 +135,48 @@ def test_ioctl_mutate_2048(self):
132135
self._check_ioctl_mutate_len(2048)
133136
self.assertRaises(ValueError, self._check_ioctl_not_mutate_len, 2048)
134137

135-
def test_ioctl_tcflush(self):
136-
with open("/dev/tty", "rb") as tty:
137-
r = fcntl.ioctl(tty, termios.TCFLSH, termios.TCIFLUSH)
138-
self.assertEqual(r, 0)
139138

140-
@unittest.skipIf(pty is None, 'pty module required')
139+
@unittest.skipIf(pty is None, 'pty module required')
140+
class IoctlTestsPty(unittest.TestCase):
141+
def setUp(self):
142+
self.master_fd, self.slave_fd = pty.openpty()
143+
self.addCleanup(os.close, self.slave_fd)
144+
self.addCleanup(os.close, self.master_fd)
145+
146+
def test_ioctl_clear_input(self):
147+
os.write(self.slave_fd, b'abcdef')
148+
self.assertEqual(os.read(self.master_fd, 2), b'ab')
149+
fcntl.ioctl(self.master_fd, termios.TCFLSH, termios.TCOFLUSH) # don't flush input
150+
self.assertEqual(os.read(self.master_fd, 2), b'cd')
151+
fcntl.ioctl(self.master_fd, termios.TCFLSH, termios.TCIFLUSH) # flush input
152+
os.write(self.slave_fd, b'ABCDEF')
153+
self.assertEqual(os.read(self.master_fd, 1024), b'ABCDEF')
154+
155+
def test_tcflow_suspend_and_resume_output(self):
156+
write_suspended = threading.Event()
157+
write_finished = threading.Event()
158+
159+
def writer():
160+
os.write(self.slave_fd, b'abc')
161+
write_suspended.wait()
162+
os.write(self.slave_fd, b'def')
163+
write_finished.set()
164+
165+
with threading_helper.start_threads([threading.Thread(target=writer)]):
166+
self.assertEqual(os.read(self.master_fd, 1024), b'abc')
167+
termios.tcflow(self.slave_fd, termios.TCOOFF)
168+
write_suspended.set()
169+
self.assertFalse(write_finished.wait(0.5))
170+
termios.tcflow(self.slave_fd, termios.TCOON)
171+
self.assertTrue(write_finished.wait(0.5))
172+
self.assertEqual(os.read(self.master_fd, 1024), b'def')
173+
141174
def test_ioctl_set_window_size(self):
142-
mfd, sfd = pty.openpty()
143-
try:
144-
# (rows, columns, xpixel, ypixel)
145-
our_winsz = struct.pack("HHHH", 20, 40, 0, 0)
146-
result = fcntl.ioctl(mfd, termios.TIOCSWINSZ, our_winsz)
147-
new_winsz = struct.unpack("HHHH", result)
148-
self.assertEqual(new_winsz[:2], (20, 40))
149-
finally:
150-
os.close(mfd)
151-
os.close(sfd)
175+
# (rows, columns, xpixel, ypixel)
176+
our_winsz = struct.pack("HHHH", 20, 40, 0, 0)
177+
result = fcntl.ioctl(self.master_fd, termios.TIOCSWINSZ, our_winsz)
178+
new_winsz = struct.unpack("HHHH", result)
179+
self.assertEqual(new_winsz[:2], (20, 40))
152180

153181

154182
if __name__ == "__main__":

Lib/test/test_termios.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
class TestFunctions(unittest.TestCase):
1414

1515
def setUp(self):
16-
master_fd, self.fd = os.openpty()
17-
self.addCleanup(os.close, master_fd)
16+
self.master_fd, self.fd = os.openpty()
17+
self.addCleanup(os.close, self.master_fd)
1818
self.stream = self.enterContext(open(self.fd, 'wb', buffering=0))
1919
tmp = self.enterContext(tempfile.TemporaryFile(mode='wb', buffering=0))
2020
self.bad_fd = tmp.fileno()
@@ -147,6 +147,15 @@ def test_tcflush_errors(self):
147147
self.assertRaises(TypeError, termios.tcflush, object(), termios.TCIFLUSH)
148148
self.assertRaises(TypeError, termios.tcflush, self.fd)
149149

150+
def test_tcflush_clear_input(self):
151+
os.write(self.fd, b'abcdef')
152+
self.assertEqual(os.read(self.master_fd, 2), b'ab')
153+
termios.tcflush(self.master_fd, termios.TCOFLUSH) # don't flush input
154+
self.assertEqual(os.read(self.master_fd, 2), b'cd')
155+
termios.tcflush(self.master_fd, termios.TCIFLUSH) # flush input
156+
os.write(self.fd, b'ABCDEF')
157+
self.assertEqual(os.read(self.master_fd, 1024), b'ABCDEF')
158+
150159
@support.skip_android_selinux('tcflow')
151160
def test_tcflow(self):
152161
termios.tcflow(self.fd, termios.TCOOFF)
@@ -165,6 +174,25 @@ def test_tcflow_errors(self):
165174
self.assertRaises(TypeError, termios.tcflow, object(), termios.TCOON)
166175
self.assertRaises(TypeError, termios.tcflow, self.fd)
167176

177+
def test_tcflow_suspend_and_resume_output(self):
178+
write_suspended = threading.Event()
179+
write_finished = threading.Event()
180+
181+
def writer():
182+
os.write(self.fd, b'abc')
183+
write_suspended.wait()
184+
os.write(self.fd, b'def')
185+
write_finished.set()
186+
187+
with threading_helper.start_threads([threading.Thread(target=writer)]):
188+
self.assertEqual(os.read(self.master_fd, 1024), b'abc')
189+
termios.tcflow(self.fd, termios.TCOOFF)
190+
write_suspended.set()
191+
self.assertFalse(write_finished.wait(0.5))
192+
termios.tcflow(self.fd, termios.TCOON)
193+
self.assertTrue(write_finished.wait(0.5))
194+
self.assertEqual(os.read(self.master_fd, 1024), b'def')
195+
168196
def test_tcgetwinsize(self):
169197
size = termios.tcgetwinsize(self.fd)
170198
self.assertIsInstance(size, tuple)

0 commit comments

Comments
 (0)