Skip to content

Commit 857536f

Browse files
committed
gh-150544: Fix pdb _pyrepl_available() and make test_gen_colors skip at runtime
1 parent 9242700 commit 857536f

2 files changed

Lines changed: 56 additions & 16 deletions

File tree

Lib/pdb.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -374,16 +374,34 @@ def get_default_backend():
374374
return _default_backend
375375

376376

377-
def _pyrepl_available():
378-
"""return whether pdb should use _pyrepl for input"""
377+
def _pyrepl_available(stdin=None, stdout=None):
378+
"""Return whether pdb should use _pyrepl for input.
379+
380+
stdin and stdout default to sys.stdin and sys.stdout. Callers that pass
381+
explicit streams (such as Pdb) should pass the streams they will use.
382+
"""
379383
if os.getenv("PYTHON_BASIC_REPL"):
380-
CAN_USE_PYREPL = False
381-
else:
382-
try:
383-
from _pyrepl.main import CAN_USE_PYREPL
384-
except ModuleNotFoundError:
385-
CAN_USE_PYREPL = False
386-
return CAN_USE_PYREPL
384+
return False
385+
try:
386+
from _pyrepl.main import CAN_USE_PYREPL
387+
except ModuleNotFoundError:
388+
return False
389+
if not CAN_USE_PYREPL:
390+
return False
391+
if stdin is None:
392+
stdin = sys.stdin
393+
if stdout is None:
394+
stdout = sys.stdout
395+
# CAN_USE_PYREPL is fixed at import time; streams may no longer be usable
396+
# when this is called (e.g. in regrtest worker subprocesses). Doctests
397+
# may replace sys.stdin with a fake object that has no fileno().
398+
try:
399+
if not os.isatty(stdin.fileno()):
400+
return False
401+
stdout.fileno()
402+
return True
403+
except (AttributeError, ValueError, OSError):
404+
return False
387405

388406

389407
class PdbPyReplInput:
@@ -520,7 +538,7 @@ def __init__(self, completekey='tab', stdin=None, stdout=None, skip=None,
520538
pass
521539

522540
self.pyrepl_input = None
523-
if _pyrepl_available():
541+
if _pyrepl_available(self.stdin, self.stdout):
524542
try:
525543
self.pyrepl_input = PdbPyReplInput(self, self.stdin, self.stdout, self.prompt)
526544
except Exception:

Lib/test/test_pdb.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from test.support.import_helper import import_module
2626
from test.support.pty_helper import run_pty, FakeInput
2727
from test.support.script_helper import kill_python
28-
from unittest.mock import patch
28+
from unittest.mock import Mock, patch
2929

3030
SKIP_CORO_TESTS = False
3131

@@ -4775,14 +4775,27 @@ def foo(self):
47754775
self.assertIn("The specified object 'C.foo' is not a function", stdout)
47764776

47774777
def test_pyrepl_available(self):
4778+
tty_stdin = Mock()
4779+
tty_stdin.fileno.return_value = 0
4780+
tty_stdout = Mock()
4781+
tty_stdout.fileno.return_value = 1
4782+
47784783
with patch.dict(os.environ, {"PYTHON_BASIC_REPL": "1"}):
4779-
self.assertFalse(pdb._pyrepl_available())
4784+
self.assertFalse(pdb._pyrepl_available(tty_stdin, tty_stdout))
4785+
4786+
with patch.dict(os.environ, {}, clear=True):
4787+
mod = types.ModuleType("_pyrepl.main")
4788+
mod.CAN_USE_PYREPL = True
4789+
with patch.dict("sys.modules", {"_pyrepl.main": mod}), \
4790+
patch.object(os, "isatty", return_value=True):
4791+
self.assertTrue(pdb._pyrepl_available(tty_stdin, tty_stdout))
47804792

47814793
with patch.dict(os.environ, {}, clear=True):
47824794
mod = types.ModuleType("_pyrepl.main")
47834795
mod.CAN_USE_PYREPL = True
4784-
with patch.dict("sys.modules", {"_pyrepl.main": mod}):
4785-
self.assertTrue(pdb._pyrepl_available())
4796+
with patch.dict("sys.modules", {"_pyrepl.main": mod}), \
4797+
patch.object(os, "isatty", return_value=False):
4798+
self.assertFalse(pdb._pyrepl_available(tty_stdin, tty_stdout))
47864799

47874800

47884801
class ChecklineTests(unittest.TestCase):
@@ -5002,9 +5015,14 @@ def test_stack_entry(self):
50025015
p.set_trace(commands=['w', 'c'])
50035016
self.assertIn("\x1b", output.getvalue())
50045017

5005-
@unittest.skipIf(not pdb._pyrepl_available(), "pyrepl is not available")
50065018
def test_gen_colors(self):
5019+
# Do not use @unittest.skipIf(pdb._pyrepl_available()): that is
5020+
# evaluated at import time, before regrtest may redirect stdin.
5021+
if not pdb._pyrepl_available():
5022+
self.skipTest("pyrepl is not available")
50075023
p = pdb.Pdb()
5024+
if p.pyrepl_input is None:
5025+
self.skipTest("pyrepl input is not available")
50085026
gen_colors = p.pyrepl_input.gen_colors
50095027

50105028
test_cases = [
@@ -5265,8 +5283,12 @@ def test_interact_completion(self):
52655283
self.assertIn('84', output)
52665284

52675285

5268-
@unittest.skipIf(not pdb._pyrepl_available(), "pyrepl is not available")
52695286
class PdbTestReadlinePyREPL(PdbTestReadline):
5287+
@classmethod
5288+
def setUpClass(cls):
5289+
if not pdb._pyrepl_available():
5290+
raise unittest.SkipTest("pyrepl is not available")
5291+
52705292
def _run_pty(self, script, input):
52715293
# Override the env to make sure pyrepl is used in this test class
52725294
return super()._run_pty(script, input, env={**os.environ})

0 commit comments

Comments
 (0)