Skip to content

Commit 7e35c29

Browse files
Add readline tests for pdb completion
1 parent ba8aa1f commit 7e35c29

File tree

3 files changed

+102
-54
lines changed

3 files changed

+102
-54
lines changed

Lib/test/support/pty_helper.py

Lines changed: 60 additions & 0 deletions
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

Lines changed: 41 additions & 0 deletions
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
@@ -3235,6 +3237,45 @@ def test_checkline_is_not_executable(self):
32353237
self.assertFalse(db.checkline(os_helper.TESTFN, lineno))
32363238

32373239

3240+
@support.requires_subprocess()
3241+
class PdbTestReadline(unittest.TestCase):
3242+
def setUpClass():
3243+
# Ensure that the readline module is loaded
3244+
# If this fails, the test is skipped because SkipTest will be raised
3245+
import_module('readline')
3246+
3247+
def test_basic_completion(self):
3248+
script = textwrap.dedent("""
3249+
import pdb; pdb.Pdb().set_trace()
3250+
print('hello')
3251+
""")
3252+
3253+
input = b"co\t\tntin\t\n"
3254+
3255+
output = run_pty(script, input)
3256+
3257+
self.assertIn(b'cont', output)
3258+
self.assertIn(b'condition', output)
3259+
self.assertIn(b'continue', output)
3260+
3261+
def test_expression_completion(self):
3262+
script = textwrap.dedent("""
3263+
value = "speci"
3264+
import pdb; pdb.Pdb().set_trace()
3265+
""")
3266+
3267+
input = b"val\t + 'al'\n"
3268+
input += b"p val\t + 'es'\n"
3269+
input += b"$_fra\t\n"
3270+
input += b"c\n"
3271+
3272+
output = run_pty(script, input)
3273+
3274+
self.assertIn(b'special', output)
3275+
self.assertIn(b'species', output)
3276+
self.assertIn(b'$_frame', output)
3277+
3278+
32383279
def load_tests(loader, tests, pattern):
32393280
from test import test_pdb
32403281
tests.addTest(doctest.DocTestSuite(test_pdb))

Lib/test/test_readline.py

Lines changed: 1 addition & 54 deletions
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)