Skip to content

Commit e5d2de7

Browse files
authored
Consider self.stdout when creating main session. (#1601)
1 parent 5ab816e commit e5d2de7

File tree

2 files changed

+33
-9
lines changed

2 files changed

+33
-9
lines changed

cmd2/cmd2.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -680,8 +680,9 @@ def _should_continue_multiline(self) -> bool:
680680
def _create_main_session(self, auto_suggest: bool, completekey: str) -> PromptSession[str]:
681681
"""Create and return the main PromptSession for the application.
682682
683-
Builds an interactive session if stdin is a TTY. Otherwise, uses
684-
dummy drivers to support non-interactive streams like pipes or files.
683+
Builds an interactive session if self.stdin and self.stdout are TTYs.
684+
Otherwise, uses dummy drivers to support non-interactive streams like
685+
pipes or files.
685686
"""
686687
key_bindings = None
687688
if completekey != self.DEFAULT_COMPLETEKEY:
@@ -713,7 +714,7 @@ def _(event: Any) -> None: # pragma: no cover
713714
"rprompt": self.get_rprompt,
714715
}
715716

716-
if self.stdin.isatty():
717+
if self.stdin.isatty() and self.stdout.isatty():
717718
try:
718719
if self.stdin != sys.stdin:
719720
kwargs["input"] = create_input(stdin=self.stdin)
@@ -3245,7 +3246,8 @@ def _is_tty_session(session: PromptSession[str]) -> bool:
32453246
"""
32463247
# Validate against the session's assigned input driver rather than sys.stdin.
32473248
# This respects the fallback logic in _create_main_session() and allows unit
3248-
# tests to inject PipeInput for programmatic interaction.
3249+
# tests to inject PipeInput for programmatic interaction even if paired with
3250+
# a DummyOutput.
32493251
return not isinstance(session.input, DummyInput)
32503252

32513253
def _read_raw_input(

tests/test_cmd2.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3842,10 +3842,16 @@ def test_create_main_session_exception(monkeypatch):
38423842
mock_session = mock.MagicMock(side_effect=[ValueError, valid_session_mock])
38433843
monkeypatch.setattr("cmd2.cmd2.PromptSession", mock_session)
38443844

3845-
cmd2.Cmd()
3845+
# Mock isatty to ensure we enter the try block
3846+
with (
3847+
mock.patch('sys.stdin.isatty', return_value=True),
3848+
mock.patch('sys.stdout.isatty', return_value=True),
3849+
):
3850+
cmd2.Cmd()
38463851

38473852
# Check that fallback to DummyInput/Output happened
38483853
assert mock_session.call_count == 2
3854+
38493855
# Check args of second call
38503856
call_args = mock_session.call_args_list[1]
38513857
kwargs = call_args[1]
@@ -3931,7 +3937,12 @@ def test_create_main_session_no_console_error(monkeypatch):
39313937
mock_session = mock.MagicMock(side_effect=[NoConsoleScreenBufferError, valid_session_mock])
39323938
monkeypatch.setattr("cmd2.cmd2.PromptSession", mock_session)
39333939

3934-
cmd2.Cmd()
3940+
# Mock isatty to ensure we enter the try block
3941+
with (
3942+
mock.patch('sys.stdin.isatty', return_value=True),
3943+
mock.patch('sys.stdout.isatty', return_value=True),
3944+
):
3945+
cmd2.Cmd()
39353946

39363947
# Check that fallback to DummyInput/Output happened
39373948
assert mock_session.call_count == 2
@@ -3949,8 +3960,9 @@ def test_create_main_session_with_custom_tty() -> None:
39493960
custom_stdin.isatty.return_value = True
39503961
assert custom_stdin is not sys.stdin
39513962

3952-
# Create a mock stdout which is not sys.stdout
3963+
# Create a mock stdout with says it's a TTY
39533964
custom_stdout = mock.MagicMock(spec=io.TextIOWrapper)
3965+
custom_stdout.isatty.return_value = True
39543966
assert custom_stdout is not sys.stdout
39553967

39563968
# Check if the streams were wrapped
@@ -3967,8 +3979,8 @@ def test_create_main_session_with_custom_tty() -> None:
39673979
mock_create_output.assert_called_once_with(stdout=custom_stdout)
39683980

39693981

3970-
def test_create_main_session_non_interactive() -> None:
3971-
# Set up a mock for a non-TTY stream (like a pipe)
3982+
def test_create_main_session_stdin_non_tty() -> None:
3983+
# Set up a mock for a non-TTY stdin stream
39723984
mock_stdin = mock.MagicMock(spec=io.TextIOWrapper)
39733985
mock_stdin.isatty.return_value = False
39743986

@@ -3977,6 +3989,16 @@ def test_create_main_session_non_interactive() -> None:
39773989
assert isinstance(app.main_session.output, DummyOutput)
39783990

39793991

3992+
def test_create_main_session_stdout_non_tty() -> None:
3993+
# Set up a mock for a non-TTY stdout stream
3994+
mock_stdout = mock.MagicMock(spec=io.TextIOWrapper)
3995+
mock_stdout.isatty.return_value = False
3996+
3997+
app = cmd2.Cmd(stdout=mock_stdout)
3998+
assert isinstance(app.main_session.input, DummyInput)
3999+
assert isinstance(app.main_session.output, DummyOutput)
4000+
4001+
39804002
def test_no_console_screen_buffer_error_dummy():
39814003
from cmd2.cmd2 import NoConsoleScreenBufferError
39824004

0 commit comments

Comments
 (0)