gh-110944: Move pty helper to test.support and add basic pdb completion test (GH-111826)

This commit is contained in:
Tian Gao 2023-11-13 02:23:06 -08:00 committed by GitHub
parent c61de456db
commit 1c7ed7e9eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 91 additions and 54 deletions

View File

@ -0,0 +1,60 @@
"""
Helper to run a script in a pseudo-terminal.
"""
import os
import selectors
import subprocess
import sys
from contextlib import ExitStack
from errno import EIO
from test.support.import_helper import import_module
def run_pty(script, input=b"dummy input\r", env=None):
pty = import_module('pty')
output = bytearray()
[master, slave] = pty.openpty()
args = (sys.executable, '-c', script)
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
os.close(slave)
with ExitStack() as cleanup:
cleanup.enter_context(proc)
def terminate(proc):
try:
proc.terminate()
except ProcessLookupError:
# Workaround for Open/Net BSD bug (Issue 16762)
pass
cleanup.callback(terminate, proc)
cleanup.callback(os.close, master)
# Avoid using DefaultSelector and PollSelector. Kqueue() does not
# work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
# BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
# either (Issue 20472). Hopefully the file descriptor is low enough
# to use with select().
sel = cleanup.enter_context(selectors.SelectSelector())
sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
os.set_blocking(master, False)
while True:
for [_, events] in sel.select():
if events & selectors.EVENT_READ:
try:
chunk = os.read(master, 0x10000)
except OSError as err:
# Linux raises EIO when slave is closed (Issue 5380)
if err.errno != EIO:
raise
chunk = b""
if not chunk:
return output
output.extend(chunk)
if events & selectors.EVENT_WRITE:
try:
input = input[os.write(master, input):]
except OSError as err:
# Apparently EIO means the slave was closed
if err.errno != EIO:
raise
input = b"" # Stop writing
if not input:
sel.modify(master, selectors.EVENT_READ)

View File

@ -15,6 +15,8 @@ from contextlib import ExitStack, redirect_stdout
from io import StringIO from io import StringIO
from test import support from test import support
from test.support import os_helper from test.support import os_helper
from test.support.import_helper import import_module
from test.support.pty_helper import run_pty
# This little helper class is essential for testing pdb under doctest. # This little helper class is essential for testing pdb under doctest.
from test.test_doctest import _FakeInput from test.test_doctest import _FakeInput
from unittest.mock import patch from unittest.mock import patch
@ -3260,6 +3262,34 @@ class ChecklineTests(unittest.TestCase):
self.assertFalse(db.checkline(os_helper.TESTFN, lineno)) self.assertFalse(db.checkline(os_helper.TESTFN, lineno))
@support.requires_subprocess()
class PdbTestReadline(unittest.TestCase):
def setUpClass():
# Ensure that the readline module is loaded
# If this fails, the test is skipped because SkipTest will be raised
readline = import_module('readline')
if readline.__doc__ and "libedit" in readline.__doc__:
raise unittest.SkipTest("libedit readline is not supported for pdb")
def test_basic_completion(self):
script = textwrap.dedent("""
import pdb; pdb.Pdb().set_trace()
# Concatenate strings so that the output doesn't appear in the source
print('hello' + '!')
""")
# List everything starting with 'co', there should be multiple matches
# then add ntin and complete 'contin' to 'continue'
input = b"co\t\tntin\t\n"
output = run_pty(script, input)
self.assertIn(b'commands', output)
self.assertIn(b'condition', output)
self.assertIn(b'continue', output)
self.assertIn(b'hello!', output)
def load_tests(loader, tests, pattern): def load_tests(loader, tests, pattern):
from test import test_pdb from test import test_pdb
tests.addTest(doctest.DocTestSuite(test_pdb)) tests.addTest(doctest.DocTestSuite(test_pdb))

View File

@ -1,18 +1,15 @@
""" """
Very minimal unittests for parts of the readline module. Very minimal unittests for parts of the readline module.
""" """
from contextlib import ExitStack
from errno import EIO
import locale import locale
import os import os
import selectors
import subprocess
import sys import sys
import tempfile import tempfile
import unittest import unittest
from test.support import verbose from test.support import verbose
from test.support.import_helper import import_module from test.support.import_helper import import_module
from test.support.os_helper import unlink, temp_dir, TESTFN from test.support.os_helper import unlink, temp_dir, TESTFN
from test.support.pty_helper import run_pty
from test.support.script_helper import assert_python_ok from test.support.script_helper import assert_python_ok
# Skip tests if there is no readline module # Skip tests if there is no readline module
@ -304,55 +301,5 @@ readline.write_history_file(history_file)
self.assertEqual(lines[-1].strip(), b"last input") self.assertEqual(lines[-1].strip(), b"last input")
def run_pty(script, input=b"dummy input\r", env=None):
pty = import_module('pty')
output = bytearray()
[master, slave] = pty.openpty()
args = (sys.executable, '-c', script)
proc = subprocess.Popen(args, stdin=slave, stdout=slave, stderr=slave, env=env)
os.close(slave)
with ExitStack() as cleanup:
cleanup.enter_context(proc)
def terminate(proc):
try:
proc.terminate()
except ProcessLookupError:
# Workaround for Open/Net BSD bug (Issue 16762)
pass
cleanup.callback(terminate, proc)
cleanup.callback(os.close, master)
# Avoid using DefaultSelector and PollSelector. Kqueue() does not
# work with pseudo-terminals on OS X < 10.9 (Issue 20365) and Open
# BSD (Issue 20667). Poll() does not work with OS X 10.6 or 10.4
# either (Issue 20472). Hopefully the file descriptor is low enough
# to use with select().
sel = cleanup.enter_context(selectors.SelectSelector())
sel.register(master, selectors.EVENT_READ | selectors.EVENT_WRITE)
os.set_blocking(master, False)
while True:
for [_, events] in sel.select():
if events & selectors.EVENT_READ:
try:
chunk = os.read(master, 0x10000)
except OSError as err:
# Linux raises EIO when slave is closed (Issue 5380)
if err.errno != EIO:
raise
chunk = b""
if not chunk:
return output
output.extend(chunk)
if events & selectors.EVENT_WRITE:
try:
input = input[os.write(master, input):]
except OSError as err:
# Apparently EIO means the slave was closed
if err.errno != EIO:
raise
input = b"" # Stop writing
if not input:
sel.modify(master, selectors.EVENT_READ)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()