bpo-26228: Fix pty EOF handling (GH-12049) · python/cpython@81ab8db (original) (raw)
`@@ -5,8 +5,9 @@
`
5
5
`import_module('termios')
`
6
6
``
7
7
`import errno
`
8
``
`-
import pty
`
9
8
`import os
`
``
9
`+
import pty
`
``
10
`+
import tty
`
10
11
`import sys
`
11
12
`import select
`
12
13
`import signal
`
`@@ -123,12 +124,6 @@ def handle_sig(self, sig, frame):
`
123
124
``
124
125
`@staticmethod
`
125
126
`def handle_sighup(signum, frame):
`
126
``
`-
bpo-38547: if the process is the session leader, os.close(master_fd)
`
127
``
`-
of "master_fd, slave_name = pty.master_open()" raises SIGHUP
`
128
``
`-
signal: just ignore the signal.
`
129
``
`-
`
130
``
`-
NOTE: the above comment is from an older version of the test;
`
131
``
`-
master_open() is not being used anymore.
`
132
127
`pass
`
133
128
``
134
129
`@expectedFailureIfStdinIsTTY
`
`@@ -190,13 +185,6 @@ def test_openpty(self):
`
190
185
`self.assertEqual(_get_term_winsz(slave_fd), new_stdin_winsz,
`
191
186
`"openpty() failed to set slave window size")
`
192
187
``
193
``
`-
Solaris requires reading the fd before anything is returned.
`
194
``
`-
My guess is that since we open and close the slave fd
`
195
``
`-
in master_open(), we need to read the EOF.
`
196
``
`-
`
197
``
`-
NOTE: the above comment is from an older version of the test;
`
198
``
`-
master_open() is not being used anymore.
`
199
``
-
200
188
`# Ensure the fd is non-blocking in case there's nothing to read.
`
201
189
`blocking = os.get_blocking(master_fd)
`
202
190
`try:
`
`@@ -324,22 +312,40 @@ def test_master_read(self):
`
324
312
``
325
313
`self.assertEqual(data, b"")
`
326
314
``
``
315
`+
def test_spawn_doesnt_hang(self):
`
``
316
`+
pty.spawn([sys.executable, '-c', 'print("hi there")'])
`
``
317
+
327
318
`class SmallPtyTests(unittest.TestCase):
`
328
319
`"""These tests don't spawn children or hang."""
`
329
320
``
330
321
`def setUp(self):
`
331
322
`self.orig_stdin_fileno = pty.STDIN_FILENO
`
332
323
`self.orig_stdout_fileno = pty.STDOUT_FILENO
`
``
324
`+
self.orig_pty_close = pty.close
`
``
325
`+
self.orig_pty__copy = pty._copy
`
``
326
`+
self.orig_pty_fork = pty.fork
`
333
327
`self.orig_pty_select = pty.select
`
``
328
`+
self.orig_pty_setraw = pty.setraw
`
``
329
`+
self.orig_pty_tcgetattr = pty.tcgetattr
`
``
330
`+
self.orig_pty_tcsetattr = pty.tcsetattr
`
``
331
`+
self.orig_pty_waitpid = pty.waitpid
`
334
332
`self.fds = [] # A list of file descriptors to close.
`
335
333
`self.files = []
`
336
334
`self.select_rfds_lengths = []
`
337
335
`self.select_rfds_results = []
`
``
336
`+
self.tcsetattr_mode_setting = None
`
338
337
``
339
338
`def tearDown(self):
`
340
339
`pty.STDIN_FILENO = self.orig_stdin_fileno
`
341
340
`pty.STDOUT_FILENO = self.orig_stdout_fileno
`
``
341
`+
pty.close = self.orig_pty_close
`
``
342
`+
pty._copy = self.orig_pty__copy
`
``
343
`+
pty.fork = self.orig_pty_fork
`
342
344
`pty.select = self.orig_pty_select
`
``
345
`+
pty.setraw = self.orig_pty_setraw
`
``
346
`+
pty.tcgetattr = self.orig_pty_tcgetattr
`
``
347
`+
pty.tcsetattr = self.orig_pty_tcsetattr
`
``
348
`+
pty.waitpid = self.orig_pty_waitpid
`
343
349
`for file in self.files:
`
344
350
`try:
`
345
351
`file.close()
`
`@@ -367,6 +373,14 @@ def _mock_select(self, rfds, wfds, xfds, timeout=0):
`
367
373
`self.assertEqual(self.select_rfds_lengths.pop(0), len(rfds))
`
368
374
`return self.select_rfds_results.pop(0), [], []
`
369
375
``
``
376
`+
def _make_mock_fork(self, pid):
`
``
377
`+
def mock_fork():
`
``
378
`+
return (pid, 12)
`
``
379
`+
return mock_fork
`
``
380
+
``
381
`+
def _mock_tcsetattr(self, fileno, opt, mode):
`
``
382
`+
self.tcsetattr_mode_setting = mode
`
``
383
+
370
384
`def test__copy_to_each(self):
`
371
385
`"""Test the normal data case on both master_fd and stdin."""
`
372
386
`read_from_stdout_fd, mock_stdout_fd = self._pipe()
`
`@@ -407,20 +421,41 @@ def test__copy_eof_on_all(self):
`
407
421
`socketpair[1].close()
`
408
422
`os.close(write_to_stdin_fd)
`
409
423
``
410
``
`-
Expect two select calls, the last one will cause IndexError
`
411
424
`pty.select = self._mock_select
`
412
425
`self.select_rfds_lengths.append(2)
`
413
426
`self.select_rfds_results.append([mock_stdin_fd, masters[0]])
`
414
427
`# We expect that both fds were removed from the fds list as they
`
415
428
`# both encountered an EOF before the second select call.
`
416
429
`self.select_rfds_lengths.append(0)
`
417
430
``
418
``
`-
with self.assertRaises(IndexError):
`
419
``
`-
pty._copy(masters[0])
`
``
431
`+
We expect the function to return without error.
`
``
432
`+
self.assertEqual(pty._copy(masters[0]), None)
`
``
433
+
``
434
`+
def test__restore_tty_mode_normal_return(self):
`
``
435
`+
"""Test that spawn resets the tty mode no when _copy returns normally."""
`
``
436
+
``
437
`+
PID 1 is returned from mocked fork to run the parent branch
`
``
438
`+
of code
`
``
439
`+
pty.fork = self._make_mock_fork(1)
`
``
440
+
``
441
`+
status_sentinel = object()
`
``
442
`+
pty.waitpid = lambda _1, _2: [None, status_sentinel]
`
``
443
`+
pty.close = lambda _: None
`
``
444
+
``
445
`+
pty._copy = lambda _1, _2, _3: None
`
``
446
+
``
447
`+
mode_sentinel = object()
`
``
448
`+
pty.tcgetattr = lambda fd: mode_sentinel
`
``
449
`+
pty.tcsetattr = self._mock_tcsetattr
`
``
450
`+
pty.setraw = lambda _: None
`
``
451
+
``
452
`+
self.assertEqual(pty.spawn([]), status_sentinel, "pty.waitpid process status not returned by pty.spawn")
`
``
453
`+
self.assertEqual(self.tcsetattr_mode_setting, mode_sentinel, "pty.tcsetattr not called with original mode value")
`
420
454
``
421
455
``
422
456
`def tearDownModule():
`
423
457
`reap_children()
`
424
458
``
``
459
+
425
460
`if name == "main":
`
426
461
`unittest.main()
`