66import termios
77from functools import partial
88from test .support import os_helper , force_not_colorized_test_class
9+ from test .support .strace_helper import requires_strace
910import subprocess
10- import time
1111import shutil
1212import signal
13+ import textwrap
1314
1415from unittest import TestCase
1516from unittest .mock import MagicMock , call , patch , ANY , Mock
@@ -313,29 +314,6 @@ def test_getheightwidth_with_invalid_environ(self, _os_write):
313314
314315class TestUnixConsoleEIOHandling (TestCase ):
315316
316- @patch ('_pyrepl.unix_console.tcsetattr' )
317- @patch ('_pyrepl.unix_console.tcgetattr' )
318- def test_eio_error_handling_in_prepare (self , mock_tcgetattr , mock_tcsetattr ):
319- mock_termios = Mock ()
320- mock_termios .iflag = 0
321- mock_termios .oflag = 0
322- mock_termios .cflag = 0
323- mock_termios .lflag = 0
324- mock_termios .cc = [0 ] * 32
325- mock_termios .copy .return_value = mock_termios
326- mock_tcgetattr .return_value = mock_termios
327-
328- mock_tcsetattr .side_effect = termios .error (errno .EIO , "Input/output error" )
329-
330- console = UnixConsole (term = "xterm" )
331-
332- try :
333- console .prepare ()
334- except termios .error as e :
335- if e .args [0 ] == errno .EIO :
336- self .fail ("EIO error should have been handled gracefully in prepare()" )
337- raise
338-
339317 @patch ('_pyrepl.unix_console.tcsetattr' )
340318 @patch ('_pyrepl.unix_console.tcgetattr' )
341319 def test_eio_error_handling_in_restore (self , mock_tcgetattr , mock_tcsetattr ):
@@ -361,48 +339,56 @@ def test_eio_error_handling_in_restore(self, mock_tcgetattr, mock_tcsetattr):
361339 self .fail ("EIO error should have been handled gracefully in restore()" )
362340 raise
363341
342+ @requires_strace ()
364343class TestEIOWithStrace (unittest .TestCase ):
365- def setUp (self ):
366- self .strace = shutil .which ("strace" )
367- if not self .strace :
368- self .skipTest ("strace" )
369-
370344 def _attach_strace (self , pid ):
371- cmd = [self .strace , "-qq" , "-p" , str (pid ), "-e" , "inject=read:error=EIO:when=1" , "-o" , "/dev/null" ]
345+ strace = shutil .which ("strace" )
346+ cmd = [strace , "-qq" , "-p" , str (pid ), "-e" , "inject=read:error=EIO:when=1" , "-o" , "/dev/null" ]
372347 try :
373348 p = subprocess .Popen (cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True )
374- time .sleep (0.15 )
375- if p .poll () is None :
349+ try :
350+ p .communicate (timeout = 0.15 )
351+ except subprocess .TimeoutExpired :
376352 return p
377- except Exception :
353+ except (OSError , subprocess .SubprocessError ):
354+ pass
355+ except (OSError , subprocess .SubprocessError ):
378356 pass
379357
380- cmd = [self .strace , "-qq" , "-p" , str (pid ), "-e" , "fault=read:error=EIO:when=1" , "-o" , "/dev/null" ]
358+ # Fallback command
359+ cmd = [strace , "-qq" , "-p" , str (pid ), "-e" , "fault=read:error=EIO:when=1" , "-o" , "/dev/null" ]
381360 p = subprocess .Popen (cmd , stdout = subprocess .PIPE , stderr = subprocess .PIPE , text = True )
382- time .sleep (0.15 )
361+ try :
362+ p .communicate (timeout = 0.15 )
363+ except subprocess .TimeoutExpired :
364+ pass
383365 return p
384366
385367 def test_repl_eio (self ):
386- pybin = sys .executable
387-
388- child_code = r"""
389- import signal, sys
390- signal.signal(signal.SIGUSR1, lambda *a: None)
391- print("READY", flush=True)
392- signal.pause()
393- input()
394- """
368+ child_code = textwrap .dedent ("""
369+ import signal, sys
370+ signal.signal(signal.SIGUSR1, lambda *a: None)
371+ print("READY", flush=True)
372+ signal.pause()
373+ input()
374+ """ ).strip ()
395375
396376 proc = subprocess .Popen (
397- [pybin , "-S" , "-c" , child_code ],
377+ [sys . executable , "-S" , "-c" , child_code ],
398378 stdin = subprocess .PIPE ,
399379 stdout = subprocess .PIPE ,
400380 stderr = subprocess .PIPE ,
401381 text = True
402382 )
403383
404- line = proc .stdout .readline ().strip ()
405- self .assertEqual (line , "READY" )
384+ ready = False
385+ while not ready :
386+ line = proc .stdout .readline ().strip ()
387+ if line == "READY" :
388+ ready = True
389+ break
390+ if proc .poll () is not None :
391+ self .fail ("Child process exited unexpectedly" )
406392
407393 tracer = self ._attach_strace (proc .pid )
408394
@@ -412,6 +398,5 @@ def test_repl_eio(self):
412398 finally :
413399 if tracer and tracer .poll () is None :
414400 tracer .terminate ()
415-
416401 self .assertNotEqual (proc .returncode , 0 )
417402 self .assertTrue ("Errno 5" in err or "Input/output error" in err or "EOFError" in err , err )
0 commit comments