Skip to content

Commit 7afd600

Browse files
committed
Add readline workaround for libedit
We had a very similar workaround before for pyreadline, which had a similar issue: - Introduced in pytest-dev#1281 - Removed in pytest-dev#8848 for pytest-dev#8733 and pytest-dev#8847 This technically will regress the issues above, but those issues just mean that `import readline` is broken in general, so the user should fix it instead (by e.g. uninstalling pyreadline). Fixes pytest-dev#12888 Fixes pytest-dev#13170
1 parent 2f36984 commit 7afd600

File tree

3 files changed

+46
-0
lines changed

3 files changed

+46
-0
lines changed

changelog/12888.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed broken input when using Python 3.13+ and a ``libedit`` build of Python, such as on macOS or with uv-managed Python binaries from the ``python-build-standalone`` project. This could manifest e.g. by a broken prompt when using ``Pdb``, or seeing empty inputs with manual usage of ``input()`` and suspended capturing.

src/_pytest/capture.py

+15
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,20 @@ def _colorama_workaround() -> None:
8080
pass
8181

8282

83+
def _readline_workaround() -> None:
84+
"""Ensure readline is imported early so it attaches to the correct stdio handles.
85+
86+
This isn't a problem with the default GNU readline implementation, but in
87+
some configurations, Python uses libedit instead (on macOS, and for prebuilt
88+
binaries such as used by uv).
89+
90+
In theory this is only needed if readline.backend == "libedit", but the
91+
workaround consists of importing readline here, so we already worked around
92+
the issue by the time we could check if we need to.
93+
"""
94+
import readline # noqa: F401
95+
96+
8397
def _windowsconsoleio_workaround(stream: TextIO) -> None:
8498
"""Workaround for Windows Unicode console handling.
8599
@@ -141,6 +155,7 @@ def pytest_load_initial_conftests(early_config: Config) -> Generator[None]:
141155
if ns.capture == "fd":
142156
_windowsconsoleio_workaround(sys.stdout)
143157
_colorama_workaround()
158+
_readline_workaround()
144159
pluginmanager = early_config.pluginmanager
145160
capman = CaptureManager(ns.capture)
146161
pluginmanager.register(capman, "capturemanager")

testing/test_capture.py

+30
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from collections.abc import Generator
55
import contextlib
6+
import re
67
import io
78
from io import UnsupportedOperation
89
import os
@@ -1666,3 +1667,32 @@ def test_logging():
16661667
)
16671668
result.stdout.no_fnmatch_line("*Captured stderr call*")
16681669
result.stdout.no_fnmatch_line("*during collection*")
1670+
1671+
1672+
def test_libedit_workaround(pytester: Pytester) -> None:
1673+
pytester.makeconftest("""
1674+
import pytest
1675+
1676+
1677+
def pytest_terminal_summary(config):
1678+
capture = config.pluginmanager.getplugin("capturemanager")
1679+
capture.suspend_global_capture(in_=True)
1680+
1681+
print("Enter 'hi'")
1682+
value = input()
1683+
print(f"value: {value!r}")
1684+
1685+
capture.resume_global_capture()
1686+
""")
1687+
import readline
1688+
backend = getattr(readline, "backend", readline.__doc__) # added in Python 3.13
1689+
print(f"Readline backend: {backend}")
1690+
1691+
child = pytester.spawn_pytest("")
1692+
child.expect(r"Enter 'hi'")
1693+
child.sendline("hi")
1694+
rest = child.read().decode("utf8")
1695+
print(rest)
1696+
match = re.search(r"^value: '(.*)'\r?$", rest, re.MULTILINE)
1697+
assert match is not None
1698+
assert match.group(1) == "hi"

0 commit comments

Comments
 (0)