1414import io # readline
1515import unittest
1616
17+ import struct
18+ import tty
19+ import fcntl
20+ import platform
21+ import warnings
22+
1723TEST_STRING_1 = b"I wish to buy a fish license.\n "
1824TEST_STRING_2 = b"For my pet fish, Eric.\n "
1925
26+ try :
27+ _TIOCGWINSZ = tty .TIOCGWINSZ
28+ _TIOCSWINSZ = tty .TIOCSWINSZ
29+ _HAVE_WINSZ = True
30+ except AttributeError :
31+ _HAVE_WINSZ = False
32+
2033if verbose :
2134 def debug (msg ):
2235 print (msg )
@@ -60,6 +73,27 @@ def _readline(fd):
6073 reader = io .FileIO (fd , mode = 'rb' , closefd = False )
6174 return reader .readline ()
6275
76+ def expectedFailureIfStdinIsTTY (fun ):
77+ # avoid isatty() for now
78+ try :
79+ tty .tcgetattr (pty .STDIN_FILENO )
80+ return unittest .expectedFailure (fun )
81+ except tty .error :
82+ pass
83+ return fun
84+
85+ def expectedFailureOnBSD (fun ):
86+ PLATFORM = platform .system ()
87+ if PLATFORM .endswith ("BSD" ) or PLATFORM == "Darwin" :
88+ return unittest .expectedFailure (fun )
89+ return fun
90+
91+ def _get_term_winsz (fd ):
92+ s = struct .pack ("HHHH" , 0 , 0 , 0 , 0 )
93+ return fcntl .ioctl (fd , _TIOCGWINSZ , s )
94+
95+ def _set_term_winsz (fd , winsz ):
96+ fcntl .ioctl (fd , _TIOCSWINSZ , winsz )
6397
6498
6599# Marginal testing of pty suite. Cannot do extensive 'do or fail' testing
@@ -78,6 +112,20 @@ def setUp(self):
78112 self .addCleanup (signal .alarm , 0 )
79113 signal .alarm (10 )
80114
115+ # Save original stdin window size
116+ self .stdin_rows = None
117+ self .stdin_cols = None
118+ if _HAVE_WINSZ :
119+ try :
120+ stdin_dim = os .get_terminal_size (pty .STDIN_FILENO )
121+ self .stdin_rows = stdin_dim .lines
122+ self .stdin_cols = stdin_dim .columns
123+ old_stdin_winsz = struct .pack ("HHHH" , self .stdin_rows ,
124+ self .stdin_cols , 0 , 0 )
125+ self .addCleanup (_set_term_winsz , pty .STDIN_FILENO , old_stdin_winsz )
126+ except OSError :
127+ pass
128+
81129 def handle_sig (self , sig , frame ):
82130 self .fail ("isatty hung" )
83131
@@ -86,26 +134,65 @@ def handle_sighup(signum, frame):
86134 # bpo-38547: if the process is the session leader, os.close(master_fd)
87135 # of "master_fd, slave_name = pty.master_open()" raises SIGHUP
88136 # signal: just ignore the signal.
137+ #
138+ # NOTE: the above comment is from an older version of the test;
139+ # master_open() is not being used anymore.
89140 pass
90141
91- def test_basic (self ):
142+ @expectedFailureIfStdinIsTTY
143+ def test_openpty (self ):
92144 try :
93- debug ("Calling master_open()" )
94- master_fd , slave_name = pty .master_open ()
95- debug ("Got master_fd '%d', slave_name '%s'" %
96- (master_fd , slave_name ))
97- debug ("Calling slave_open(%r)" % (slave_name ,))
98- slave_fd = pty .slave_open (slave_name )
99- debug ("Got slave_fd '%d'" % slave_fd )
145+ mode = tty .tcgetattr (pty .STDIN_FILENO )
146+ except tty .error :
147+ # not a tty or bad/closed fd
148+ debug ("tty.tcgetattr(pty.STDIN_FILENO) failed" )
149+ mode = None
150+
151+ new_stdin_winsz = None
152+ if self .stdin_rows != None and self .stdin_cols != None :
153+ try :
154+ debug ("Setting pty.STDIN_FILENO window size" )
155+ # Set number of columns and rows to be the
156+ # floors of 1/5 of respective original values
157+ target_stdin_winsz = struct .pack ("HHHH" , self .stdin_rows // 5 ,
158+ self .stdin_cols // 5 , 0 , 0 )
159+ _set_term_winsz (pty .STDIN_FILENO , target_stdin_winsz )
160+
161+ # Were we able to set the window size
162+ # of pty.STDIN_FILENO successfully?
163+ new_stdin_winsz = _get_term_winsz (pty .STDIN_FILENO )
164+ self .assertEqual (new_stdin_winsz , target_stdin_winsz ,
165+ "pty.STDIN_FILENO window size unchanged" )
166+ except OSError :
167+ warnings .warn ("Failed to set pty.STDIN_FILENO window size" )
168+ pass
169+
170+ try :
171+ debug ("Calling pty.openpty()" )
172+ try :
173+ master_fd , slave_fd = pty .openpty (mode , new_stdin_winsz )
174+ except TypeError :
175+ master_fd , slave_fd = pty .openpty ()
176+ debug (f"Got master_fd '{ master_fd } ', slave_fd '{ slave_fd } '" )
100177 except OSError :
101178 # " An optional feature could not be imported " ... ?
102179 raise unittest .SkipTest ("Pseudo-terminals (seemingly) not functional." )
103180
104- self .assertTrue (os .isatty (slave_fd ), 'slave_fd is not a tty' )
181+ self .assertTrue (os .isatty (slave_fd ), "slave_fd is not a tty" )
182+
183+ if mode :
184+ self .assertEqual (tty .tcgetattr (slave_fd ), mode ,
185+ "openpty() failed to set slave termios" )
186+ if new_stdin_winsz :
187+ self .assertEqual (_get_term_winsz (slave_fd ), new_stdin_winsz ,
188+ "openpty() failed to set slave window size" )
105189
106190 # Solaris requires reading the fd before anything is returned.
107191 # My guess is that since we open and close the slave fd
108192 # in master_open(), we need to read the EOF.
193+ #
194+ # NOTE: the above comment is from an older version of the test;
195+ # master_open() is not being used anymore.
109196
110197 # Ensure the fd is non-blocking in case there's nothing to read.
111198 blocking = os .get_blocking (master_fd )
@@ -222,8 +309,20 @@ def test_fork(self):
222309
223310 os .close (master_fd )
224311
225- # pty.fork() passed.
312+ @expectedFailureOnBSD
313+ def test_master_read (self ):
314+ debug ("Calling pty.openpty()" )
315+ master_fd , slave_fd = pty .openpty ()
316+ debug (f"Got master_fd '{ master_fd } ', slave_fd '{ slave_fd } '" )
317+
318+ debug ("Closing slave_fd" )
319+ os .close (slave_fd )
226320
321+ debug ("Reading from master_fd" )
322+ with self .assertRaises (OSError ):
323+ os .read (master_fd , 1 )
324+
325+ os .close (master_fd )
227326
228327class SmallPtyTests (unittest .TestCase ):
229328 """These tests don't spawn children or hang."""
@@ -262,8 +361,9 @@ def _socketpair(self):
262361 self .files .extend (socketpair )
263362 return socketpair
264363
265- def _mock_select (self , rfds , wfds , xfds ):
364+ def _mock_select (self , rfds , wfds , xfds , timeout = 0 ):
266365 # This will raise IndexError when no more expected calls exist.
366+ # This ignores the timeout
267367 self .assertEqual (self .select_rfds_lengths .pop (0 ), len (rfds ))
268368 return self .select_rfds_results .pop (0 ), [], []
269369
0 commit comments