Skip to content

Commit 819482d

Browse files
committed
Capture stdout and stderr when quiet is on
Example usage: `stack-pr submit` returned with exit 1 due to an outdated version of `gh`. ``` $ stack-pr submit Adding cross-links to PRs Exitcode: 1 Stdout: None Stderr: GraphQL: Projects (classic) is being deprecated in favor of the new Projects experience, see: https://github.blog/changelog/2024-05-23-sunset-notice-projects-classic/. (repository.pullRequest.projectCards) subprocess.CalledProcessError: Command '['gh', 'pr', 'edit', 'modularml/modular#70936', '-t', 'Test GH', '-F', '-', '-B', 'main']' returned non-zero exit status 1. ``` Test Plan: ``` python -m pytest tests/test_shell_commands.py -v ```
1 parent f0cb20f commit 819482d

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

src/stack_pr/shell_commands.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,12 @@ def run_shell_command(
4545
raise ValueError("shell support has been removed")
4646
_ = subprocess.list2cmdline(cmd)
4747
if quiet:
48-
kwargs.update({"stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL})
48+
# If quiet, capture stdout and stderr so they are not printed to the console
49+
# But respects explicit stderr/stdout settings at the call sites
50+
if "stderr" not in kwargs:
51+
kwargs["stderr"] = subprocess.PIPE
52+
if "stdout" not in kwargs:
53+
kwargs["stdout"] = subprocess.PIPE
4954
logger.debug("Running: %s", cmd)
5055
return subprocess.run(list(map(str, cmd)), **kwargs, check=check)
5156

tests/test_shell_commands.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import subprocess
2+
import sys
3+
from pathlib import Path
4+
5+
sys.path.append(str(Path(__file__).parent.parent / "src"))
6+
7+
import pytest
8+
9+
from stack_pr.shell_commands import run_shell_command
10+
11+
12+
def test_cmd_success_quiet_false_print(capfd: pytest.CaptureFixture) -> None:
13+
"""Test that stdout and stderr are printed when quiet=False on success."""
14+
# Use a command that produces both stdout and stderr
15+
# sh -c 'echo "out" && echo "err" >&2' produces both
16+
result = run_shell_command(
17+
["sh", "-c", 'echo "stdout_msg" && echo "stderr_msg" >&2'],
18+
quiet=False,
19+
)
20+
21+
# stdout and stderr are not captured in memory
22+
assert result.returncode == 0
23+
assert result.stdout is None
24+
assert result.stderr is None
25+
26+
# stdout and stderr are printed to console
27+
captured = capfd.readouterr()
28+
assert "stdout_msg" in captured.out
29+
assert "stderr_msg" in captured.err
30+
31+
32+
def test_cmd_success_quiet_true_captured(capfd: pytest.CaptureFixture) -> None:
33+
"""Test that stdout and stderr are captured when quiet=True on success."""
34+
result = run_shell_command(
35+
["sh", "-c", 'echo "stdout_msg" && echo "stderr_msg" >&2'],
36+
quiet=True,
37+
)
38+
39+
# stdout and stderr are captured in memory
40+
assert result.returncode == 0
41+
assert "stdout_msg" in result.stdout.decode("utf-8")
42+
assert "stderr_msg" in result.stderr.decode("utf-8")
43+
44+
# stdout and stderr are not printed to console
45+
captured = capfd.readouterr()
46+
assert "stdout_msg" not in captured.out
47+
assert "stderr_msg" not in captured.err
48+
49+
50+
def test_cmd_fail_quiet_true_captured(capfd: pytest.CaptureFixture) -> None:
51+
"""Test that stdout and stderr are caught by exception handling when quiet=True on failure."""
52+
with pytest.raises(subprocess.CalledProcessError) as exc:
53+
run_shell_command(
54+
["sh", "-c", 'echo "stdout_msg" && echo "stderr_msg" >&2 && exit 1'],
55+
quiet=True,
56+
)
57+
58+
# stdout and stderr are captured in exception info
59+
exception = exc.value
60+
assert "stdout_msg" in exception.stdout.decode("utf-8")
61+
assert "stderr_msg" in exception.stderr.decode("utf-8")
62+
63+
# stdout and stderr are not printed to console
64+
captured = capfd.readouterr()
65+
assert captured.out == ""
66+
assert captured.err == ""
67+
68+
69+
def test_cmd_fail_quiet_false_print(capfd: pytest.CaptureFixture) -> None:
70+
"""Test that stdout and stderr are printed when quiet=False on failure."""
71+
with pytest.raises(subprocess.CalledProcessError):
72+
run_shell_command(
73+
["sh", "-c", 'echo "stdout_msg" && echo "stderr_msg" >&2 && exit 1'],
74+
quiet=False,
75+
)
76+
77+
# stdout and stderr are printed to console
78+
captured = capfd.readouterr()
79+
assert "stdout_msg" in captured.out
80+
assert "stderr_msg" in captured.err

0 commit comments

Comments
 (0)