Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/stack_pr/shell_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,12 @@ def run_shell_command(
raise ValueError("shell support has been removed")
_ = subprocess.list2cmdline(cmd)
if quiet:
kwargs.update({"stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL})
# If quiet, capture stdout and stderr so they are not printed to the console
# But respects explicit stderr/stdout settings at the call sites
if "stderr" not in kwargs:
kwargs["stderr"] = subprocess.PIPE
if "stdout" not in kwargs:
kwargs["stdout"] = subprocess.PIPE
logger.debug("Running: %s", cmd)
return subprocess.run(list(map(str, cmd)), **kwargs, check=check)

Expand Down
83 changes: 83 additions & 0 deletions tests/test_shell_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import subprocess
import sys
from pathlib import Path

sys.path.append(str(Path(__file__).parent.parent / "src"))

import pytest

from stack_pr.shell_commands import run_shell_command


def test_cmd_success_quiet_false_print(capfd: pytest.CaptureFixture) -> None:
"""Test that stdout and stderr are printed when quiet=False on success."""
# Use a command that produces both stdout and stderr
# sh -c 'echo "out" && echo "err" >&2' produces both
result = run_shell_command(
["sh", "-c", 'echo "stdout_msg" && echo "stderr_msg" >&2'],
quiet=False,
)

# stdout and stderr are not captured in memory
assert result.returncode == 0
assert result.stdout is None
assert result.stderr is None

# stdout and stderr are printed to console
captured = capfd.readouterr()
assert "stdout_msg" in captured.out
assert "stderr_msg" in captured.err


def test_cmd_success_quiet_true_captured(capfd: pytest.CaptureFixture) -> None:
"""Test that stdout and stderr are captured when quiet=True on success."""
result = run_shell_command(
["sh", "-c", 'echo "stdout_msg" && echo "stderr_msg" >&2'],
quiet=True,
)

# stdout and stderr are captured in memory
assert result.returncode == 0
assert "stdout_msg" in result.stdout.decode("utf-8")
assert "stderr_msg" in result.stderr.decode("utf-8")

# stdout and stderr are not printed to console
captured = capfd.readouterr()
assert "stdout_msg" not in captured.out
assert "stderr_msg" not in captured.err


def test_cmd_fail_quiet_true_captured(capfd: pytest.CaptureFixture) -> None:
"""Test that stdout and stderr are caught by exception handling.

Tests behavior when quiet=True on failure.
"""
with pytest.raises(subprocess.CalledProcessError) as exc:
run_shell_command(
["sh", "-c", 'echo "stdout_msg" && echo "stderr_msg" >&2 && exit 1'],
quiet=True,
)

# stdout and stderr are captured in exception info
exception = exc.value
assert "stdout_msg" in exception.stdout.decode("utf-8")
assert "stderr_msg" in exception.stderr.decode("utf-8")

# stdout and stderr are not printed to console
captured = capfd.readouterr()
assert captured.out == ""
assert captured.err == ""


def test_cmd_fail_quiet_false_print(capfd: pytest.CaptureFixture) -> None:
"""Test that stdout and stderr are printed when quiet=False on failure."""
with pytest.raises(subprocess.CalledProcessError):
run_shell_command(
["sh", "-c", 'echo "stdout_msg" && echo "stderr_msg" >&2 && exit 1'],
quiet=False,
)

# stdout and stderr are printed to console
captured = capfd.readouterr()
assert "stdout_msg" in captured.out
assert "stderr_msg" in captured.err
Loading