Skip to content

Commit e73216d

Browse files
[3.11] gh-110944: Move pty helper to test.support and add basic pdb completion test (GH-111826) (GH-112025)
gh-110944: Move pty helper to test.support and add basic pdb completion test (GH-111826) (cherry picked from commit 1c7ed7e) Co-authored-by: Tian Gao <[email protected]>
1 parent 46081fe commit e73216d

File tree

3 files changed

+91
-54
lines changed

3 files changed

+91
-54
lines changed

Lib/test/support/pty_helper.py

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""
2+
Helper to run a script in a pseudo-terminal.
3+
"""
4+
import os
5+
import selectors
6+
import subprocess
7+
import sys
8+
from contextlib import ExitStack
9+
from errno import EIO
10+
11+
from test.support.import_helper import import_module
12+
13+
def run_pty(script, input=b"dummy input\r", env=None):
14+
pty = import_module('pty')
15+
output = bytearray()
16+
[master, slave] = pty.openpty()
17+
args = (sys.executable, '-c', script)
18+
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
19+
os.close(slave)
20+
with ExitStack() as cleanup:
21+
cleanup.enter_context(proc)
22+
def terminate(proc):
23+
try:
24+
proc.terminate()
25+
except ProcessLookupError:
26+
# Workaround for Open/Net BSD bug (Issue 16762)
27+
pass
28+
cleanup.callback(terminate, proc)
29+
cleanup.callback(os.close, master)
30+
# Avoid using DefaultSelector and PollSelector. Kqueue() does not
31+
# work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
32+
# BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
33+
# either (Issue 20472). Hopefully the file descriptor is low enough
34+
# to use with select().
35+
sel = cleanup.enter_context(selectors.SelectSelector())
36+
sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
37+
os.set_blocking(master, False)
38+
while True:
39+
for [_, events] in sel.select():
40+
if events & selectors.EVENT_READ:
41+
try:
42+
chunk = os.read(master, 0x10000)
43+
except OSError as err:
44+
# Linux raises EIO when slave is closed (Issue 5380)
45+
if err.errno != EIO:
46+
raise
47+
chunk = b""
48+
if not chunk:
49+
return output
50+
output.extend(chunk)
51+
if events & selectors.EVENT_WRITE:
52+
try:
53+
input = input[os.write(master, input):]
54+
except OSError as err:
55+
# Apparently EIO means the slave was closed
56+
if err.errno != EIO:
57+
raise
58+
input = b"" # Stop writing
59+
if not input:
60+
sel.modify(master, selectors.EVENT_READ)

Lib/test/test_pdb.py

+30
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from io import StringIO
1616
from test import support
1717
from test.support import os_helper
18+
from test.support.import_helper import import_module
19+
from test.support.pty_helper import run_pty
1820
# This little helper class is essential for testing pdb under doctest.
1921
from test.test_doctest import _FakeInput
2022
from unittest.mock import patch
@@ -2485,6 +2487,34 @@ def test_checkline_is_not_executable(self):
24852487
self.assertFalse(db.checkline(os_helper.TESTFN, lineno))
24862488

24872489

2490+
@support.requires_subprocess()
2491+
class PdbTestReadline(unittest.TestCase):
2492+
def setUpClass():
2493+
# Ensure that the readline module is loaded
2494+
# If this fails, the test is skipped because SkipTest will be raised
2495+
readline = import_module('readline')
2496+
if readline.__doc__ and "libedit" in readline.__doc__:
2497+
raise unittest.SkipTest("libedit readline is not supported for pdb")
2498+
2499+
def test_basic_completion(self):
2500+
script = textwrap.dedent("""
2501+
import pdb; pdb.Pdb().set_trace()
2502+
# Concatenate strings so that the output doesn't appear in the source
2503+
print('hello' + '!')
2504+
""")
2505+
2506+
# List everything starting with 'co', there should be multiple matches
2507+
# then add ntin and complete 'contin' to 'continue'
2508+
input = b"co\t\tntin\t\n"
2509+
2510+
output = run_pty(script, input)
2511+
2512+
self.assertIn(b'commands', output)
2513+
self.assertIn(b'condition', output)
2514+
self.assertIn(b'continue', output)
2515+
self.assertIn(b'hello!', output)
2516+
2517+
24882518
def load_tests(loader, tests, pattern):
24892519
from test import test_pdb
24902520
tests.addTest(doctest.DocTestSuite(test_pdb))

Lib/test/test_readline.py

+1-54
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
"""
22
Very minimal unittests for parts of the readline module.
33
"""
4-
from contextlib import ExitStack
5-
from errno import EIO
64
import locale
75
import os
8-
import selectors
9-
import subprocess
106
import sys
117
import tempfile
128
import unittest
139
from test.support import verbose
1410
from test.support.import_helper import import_module
1511
from test.support.os_helper import unlink, temp_dir, TESTFN
12+
from test.support.pty_helper import run_pty
1613
from test.support.script_helper import assert_python_ok
1714

1815
# Skip tests if there is no readline module
@@ -304,55 +301,5 @@ def test_history_size(self):
304301
self.assertEqual(lines[-1].strip(), b"last input")
305302

306303

307-
def run_pty(script, input=b"dummy input\r", env=None):
308-
pty = import_module('pty')
309-
output = bytearray()
310-
[master, slave] = pty.openpty()
311-
args = (sys.executable, '-c', script)
312-
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
313-
os.close(slave)
314-
with ExitStack() as cleanup:
315-
cleanup.enter_context(proc)
316-
def terminate(proc):
317-
try:
318-
proc.terminate()
319-
except ProcessLookupError:
320-
# Workaround for Open/Net BSD bug (Issue 16762)
321-
pass
322-
cleanup.callback(terminate, proc)
323-
cleanup.callback(os.close, master)
324-
# Avoid using DefaultSelector and PollSelector. Kqueue() does not
325-
# work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
326-
# BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
327-
# either (Issue 20472). Hopefully the file descriptor is low enough
328-
# to use with select().
329-
sel = cleanup.enter_context(selectors.SelectSelector())
330-
sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
331-
os.set_blocking(master, False)
332-
while True:
333-
for [_, events] in sel.select():
334-
if events & selectors.EVENT_READ:
335-
try:
336-
chunk = os.read(master, 0x10000)
337-
except OSError as err:
338-
# Linux raises EIO when slave is closed (Issue 5380)
339-
if err.errno != EIO:
340-
raise
341-
chunk = b""
342-
if not chunk:
343-
return output
344-
output.extend(chunk)
345-
if events & selectors.EVENT_WRITE:
346-
try:
347-
input = input[os.write(master, input):]
348-
except OSError as err:
349-
# Apparently EIO means the slave was closed
350-
if err.errno != EIO:
351-
raise
352-
input = b"" # Stop writing
353-
if not input:
354-
sel.modify(master, selectors.EVENT_READ)
355-
356-
357304
if __name__ == "__main__":
358305
unittest.main()

0 commit comments

Comments
 (0)