|
6 | 6 | import termios |
7 | 7 | from functools import partial |
8 | 8 | from test.support import os_helper, force_not_colorized_test_class |
| 9 | +import subprocess |
| 10 | +import time |
| 11 | +import shutil |
| 12 | +import signal |
9 | 13 |
|
10 | 14 | from unittest import TestCase |
11 | 15 | from unittest.mock import MagicMock, call, patch, ANY, Mock |
@@ -356,3 +360,58 @@ def test_eio_error_handling_in_restore(self, mock_tcgetattr, mock_tcsetattr): |
356 | 360 | if e.args[0] == errno.EIO: |
357 | 361 | self.fail("EIO error should have been handled gracefully in restore()") |
358 | 362 | raise |
| 363 | + |
| 364 | +class TestEIOWithStrace(unittest.TestCase): |
| 365 | + def setUp(self): |
| 366 | + self.strace = shutil.which("strace") |
| 367 | + if not self.strace: |
| 368 | + self.skipTest("strace") |
| 369 | + |
| 370 | + def _attach_strace(self, pid): |
| 371 | + cmd = [self.strace, "-qq", "-p", str(pid), "-e", "inject=read:error=EIO:when=1", "-o", "/dev/null"] |
| 372 | + try: |
| 373 | + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) |
| 374 | + time.sleep(0.15) |
| 375 | + if p.poll() is None: |
| 376 | + return p |
| 377 | + except Exception: |
| 378 | + pass |
| 379 | + |
| 380 | + cmd = [self.strace, "-qq", "-p", str(pid), "-e", "fault=read:error=EIO:when=1", "-o", "/dev/null"] |
| 381 | + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) |
| 382 | + time.sleep(0.15) |
| 383 | + return p |
| 384 | + |
| 385 | + 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 | +""" |
| 395 | + |
| 396 | + proc = subprocess.Popen( |
| 397 | + [pybin, "-S", "-c", child_code], |
| 398 | + stdin=subprocess.PIPE, |
| 399 | + stdout=subprocess.PIPE, |
| 400 | + stderr=subprocess.PIPE, |
| 401 | + text=True |
| 402 | + ) |
| 403 | + |
| 404 | + line = proc.stdout.readline().strip() |
| 405 | + self.assertEqual(line, "READY") |
| 406 | + |
| 407 | + tracer = self._attach_strace(proc.pid) |
| 408 | + |
| 409 | + try: |
| 410 | + os.kill(proc.pid, signal.SIGUSR1) |
| 411 | + _, err = proc.communicate(timeout=5) |
| 412 | + finally: |
| 413 | + if tracer and tracer.poll() is None: |
| 414 | + tracer.terminate() |
| 415 | + |
| 416 | + self.assertNotEqual(proc.returncode, 0) |
| 417 | + self.assertTrue("Errno 5" in err or "Input/output error" in err or "EOFError" in err, err) |
0 commit comments