@@ -1493,47 +1493,31 @@ class PtyTests(unittest.TestCase):
14931493 """Tests that use a pseudo terminal to guarantee stdin and stdout are
14941494 terminals in the test environment"""
14951495
1496- def fork (self ):
1496+ def run_child (self , child , terminal_input ):
1497+ r , w = os .pipe () # Pipe test results from child back to parent
14971498 try :
1498- return pty .fork ()
1499+ pid , fd = pty .fork ()
14991500 except (OSError , AttributeError ) as e :
1500- self .skipTest ("pty.fork() raised {}" .format (e ))
1501-
1502- def check_input_tty (self , prompt , terminal_input , stdio_encoding = None ):
1503- if not sys .stdin .isatty () or not sys .stdout .isatty ():
1504- self .skipTest ("stdin and stdout must be ttys" )
1505- r , w = os .pipe ()
1506- try :
1507- pid , fd = self .fork ()
1508- except :
15091501 os .close (r )
15101502 os .close (w )
1503+ self .skipTest ("pty.fork() raised {}" .format (e ))
15111504 raise
15121505 if pid == 0 :
15131506 # Child
15141507 try :
15151508 # Make sure we don't get stuck if there's a problem
15161509 signal .alarm (2 )
15171510 os .close (r )
1518- # Check the error handlers are accounted for
1519- if stdio_encoding :
1520- sys .stdin = io .TextIOWrapper (sys .stdin .detach (),
1521- encoding = stdio_encoding ,
1522- errors = 'surrogateescape' )
1523- sys .stdout = io .TextIOWrapper (sys .stdout .detach (),
1524- encoding = stdio_encoding ,
1525- errors = 'replace' )
15261511 with open (w , "w" ) as wpipe :
1527- print ("tty =" , sys .stdin .isatty () and sys .stdout .isatty (), file = wpipe )
1528- print (ascii (input (prompt )), file = wpipe )
1512+ child (wpipe )
15291513 except :
15301514 traceback .print_exc ()
15311515 finally :
15321516 # We don't want to return to unittest...
15331517 os ._exit (0 )
15341518 # Parent
15351519 os .close (w )
1536- os .write (fd , terminal_input + b" \r \n " )
1520+ os .write (fd , terminal_input )
15371521 # Get results from the pipe
15381522 with open (r , "r" ) as rpipe :
15391523 lines = []
@@ -1546,10 +1530,38 @@ def check_input_tty(self, prompt, terminal_input, stdio_encoding=None):
15461530 # Check the result was got and corresponds to the user's terminal input
15471531 if len (lines ) != 2 :
15481532 # Something went wrong, try to get at stderr
1549- with open (fd , "r" , encoding = "ascii" , errors = "ignore" ) as child_output :
1550- self .fail ("got %d lines in pipe but expected 2, child output was:\n %s"
1551- % (len (lines ), child_output .read ()))
1533+ # Beware of Linux raising EIO when the slave is closed
1534+ child_output = bytearray ()
1535+ while True :
1536+ try :
1537+ chunk = os .read (fd , 3000 )
1538+ except OSError : # Assume EIO
1539+ break
1540+ if not chunk :
1541+ break
1542+ child_output .extend (chunk )
1543+ os .close (fd )
1544+ child_output = child_output .decode ("ascii" , "ignore" )
1545+ self .fail ("got %d lines in pipe but expected 2, child output was:\n %s"
1546+ % (len (lines ), child_output ))
15521547 os .close (fd )
1548+ return lines
1549+
1550+ def check_input_tty (self , prompt , terminal_input , stdio_encoding = None ):
1551+ if not sys .stdin .isatty () or not sys .stdout .isatty ():
1552+ self .skipTest ("stdin and stdout must be ttys" )
1553+ def child (wpipe ):
1554+ # Check the error handlers are accounted for
1555+ if stdio_encoding :
1556+ sys .stdin = io .TextIOWrapper (sys .stdin .detach (),
1557+ encoding = stdio_encoding ,
1558+ errors = 'surrogateescape' )
1559+ sys .stdout = io .TextIOWrapper (sys .stdout .detach (),
1560+ encoding = stdio_encoding ,
1561+ errors = 'replace' )
1562+ print ("tty =" , sys .stdin .isatty () and sys .stdout .isatty (), file = wpipe )
1563+ print (ascii (input (prompt )), file = wpipe )
1564+ lines = self .run_child (child , terminal_input + b"\r \n " )
15531565 # Check we did exercise the GNU readline path
15541566 self .assertIn (lines [0 ], {'tty = True' , 'tty = False' })
15551567 if lines [0 ] != 'tty = True' :
@@ -1577,26 +1589,17 @@ def test_input_tty_non_ascii_unicode_errors(self):
15771589 def test_input_no_stdout_fileno (self ):
15781590 # Issue #24402: If stdin is the original terminal but stdout.fileno()
15791591 # fails, do not use the original stdout file descriptor
1580- pid , pty = self .fork ()
1581- if pid : # Parent process
1582- # Ideally this should read and write concurrently using select()
1583- # or similar, to avoid the possibility of a deadlock.
1584- os .write (pty , b"quux\r " )
1585- _ , status = os .waitpid (pid , 0 )
1586- output = os .read (pty , 3000 ).decode ("ascii" , "backslashreplace" )
1587- os .close (pty )
1588- self .assertEqual (status , 0 , output )
1589- else : # Child process
1590- try :
1591- self .assertTrue (sys .stdin .isatty (), "stdin not a terminal" )
1592- sys .stdout = io .StringIO () # Does not support fileno()
1593- input ("prompt" )
1594- self .assertEqual (sys .stdout .getvalue (), "prompt" )
1595- os ._exit (0 ) # Success!
1596- except :
1597- sys .excepthook (* sys .exc_info ())
1598- finally :
1599- os ._exit (1 ) # Failure
1592+ def child (wpipe ):
1593+ print ("stdin.isatty():" , sys .stdin .isatty (), file = wpipe )
1594+ sys .stdout = io .StringIO () # Does not support fileno()
1595+ input ("prompt" )
1596+ print ("captured:" , ascii (sys .stdout .getvalue ()), file = wpipe )
1597+ lines = self .run_child (child , b"quux\r " )
1598+ expected = (
1599+ "stdin.isatty(): True" ,
1600+ "captured: 'prompt'" ,
1601+ )
1602+ self .assertSequenceEqual (lines , expected )
16001603
16011604class TestSorted (unittest .TestCase ):
16021605
0 commit comments