Skip to content

Commit 37c03c5

Browse files
committed
Added unit tests for when to redirect/capture sys.stdout.
1 parent 1578fb3 commit 37c03c5

File tree

7 files changed

+168
-122
lines changed

7 files changed

+168
-122
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
- Added `Cmd.macro_arg_complete()` which tab completes arguments to a macro. Its default
1616
behavior is to perform path completion, but it can be overridden as needed.
1717

18+
- Bug Fixes
19+
- No longer redirecting `sys.stdout` if it's a different stream than `self.stdout`. This
20+
fixes issue where we overwrote an application's `sys.stdout` while redirecting.
21+
1822
## 2.7.0 (June 30, 2025)
1923

2024
- Enhancements

cmd2/py_bridge.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ def __call__(self, command: str, *, echo: Optional[bool] = None) -> CommandResul
110110
if echo is None:
111111
echo = self.cmd_echo
112112

113+
# Only capture sys.stdout if it's the same stream as self.stdout
113114
stdouts_match = self._cmd2_app.stdout == sys.stdout
114115

115116
# This will be used to capture _cmd2_app.stdout and sys.stdout

tests/conftest.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@
22

33
import argparse
44
import sys
5-
from contextlib import (
6-
redirect_stderr,
7-
redirect_stdout,
8-
)
5+
from contextlib import redirect_stderr
96
from typing import (
107
Optional,
118
Union,
@@ -116,8 +113,9 @@ def normalize(block):
116113

117114
def run_cmd(app, cmd):
118115
"""Clear out and err StdSim buffers, run the command, and return out and err"""
119-
saved_sysout = sys.stdout
120-
sys.stdout = app.stdout
116+
117+
# Only capture sys.stdout if it's the same stream as self.stdout
118+
stdouts_match = app.stdout == sys.stdout
121119

122120
# This will be used to capture app.stdout and sys.stdout
123121
copy_cmd_stdout = StdSim(app.stdout)
@@ -127,11 +125,14 @@ def run_cmd(app, cmd):
127125

128126
try:
129127
app.stdout = copy_cmd_stdout
130-
with redirect_stdout(copy_cmd_stdout), redirect_stderr(copy_stderr):
128+
if stdouts_match:
129+
sys.stdout = app.stdout
130+
with redirect_stderr(copy_stderr):
131131
app.onecmd_plus_hooks(cmd)
132132
finally:
133133
app.stdout = copy_cmd_stdout.inner_stream
134-
sys.stdout = saved_sysout
134+
if stdouts_match:
135+
sys.stdout = app.stdout
135136

136137
out = copy_cmd_stdout.getvalue()
137138
err = copy_stderr.getvalue()

tests/pyscript/stdout_capture.py

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,4 @@
1-
# This script demonstrates when output of a command finalization hook is captured by a pyscript app() call
2-
import sys
3-
4-
# The unit test framework passes in the string being printed by the command finalization hook
5-
hook_output = sys.argv[1]
6-
7-
# Run a help command which results in 1 call to onecmd_plus_hooks
8-
res = app('help')
9-
10-
# hook_output will not be captured because there are no nested calls to onecmd_plus_hooks
11-
if hook_output not in res.stdout:
12-
print("PASSED")
13-
else:
14-
print("FAILED")
15-
16-
# Run the last command in the history
17-
res = app('history -r -1')
18-
19-
# All output of the history command will be captured. This includes all output of the commands
20-
# started in do_history() using onecmd_plus_hooks(), including any output in those commands' hooks.
21-
# Therefore we expect the hook_output to show up this time.
22-
if hook_output in res.stdout:
23-
print("PASSED")
24-
else:
25-
print("FAILED")
1+
# This script demonstrates that cmd2 can capture sys.stdout and self.stdout when both point to the same stream.
2+
# Set base_app.self_in_py to True before running this script.
3+
print("print")
4+
self.poutput("poutput")

tests/test_argparse_completer.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@
1515
argparse_custom,
1616
with_argparser,
1717
)
18-
from cmd2.utils import (
19-
StdSim,
20-
align_right,
21-
)
18+
from cmd2.utils import align_right
2219

2320
from .conftest import (
2421
complete_tester,
@@ -334,9 +331,7 @@ def do_standalone(self, args: argparse.Namespace) -> None:
334331

335332
@pytest.fixture
336333
def ac_app():
337-
app = ArgparseCompleterTester()
338-
app.stdout = StdSim(app.stdout)
339-
return app
334+
return ArgparseCompleterTester()
340335

341336

342337
@pytest.mark.parametrize('command', ['music', 'music create', 'music create rock', 'music create jazz'])

0 commit comments

Comments
 (0)