ReMe/tests/unit/test_shell_step.py
jinliyl a597d1a355
Some checks failed
CI / Python packages / Build and verify distributions (push) Waiting to run
CI / Python quality / GitHub Actions (push) Waiting to run
CI / Python quality / Pre-commit (push) Waiting to run
CI / Python tests / Unit Tests - py3.11 (push) Waiting to run
CI / Python tests / Unit Tests - py3.12 (push) Waiting to run
CI / Python tests / Unit Tests - py3.13 (push) Waiting to run
CI / Python tests / Unit Tests - py3.14 (push) Waiting to run
CI / Windows / CLI smoke - py3.11 (push) Waiting to run
Deploy / Documentation / Build documentation (push) Waiting to run
Deploy / Documentation / deploy (push) Blocked by required conditions
CI and Release / Docker / Build and test / amd64 (push) Waiting to run
CI and Release / Docker / Build and test / arm64 (push) Waiting to run
CI and Release / Docker / Publish multi-platform tags (push) Blocked by required conditions
Security / CodeQL / Analyze javascript-typescript (push) Waiting to run
Security / CodeQL / Analyze python (push) Waiting to run
CI / ReMe Studio / Studio checks (push) Has been cancelled
refactor: simplify async tests and shared response rendering (#662)
2026-10-23 13:10:45 +08:00

86 lines
2.7 KiB
Python

"""Tests for asynchronous shell command execution."""
import time
import pytest
from reme.components.application_context import ApplicationContext
from reme.enumeration import ComponentEnum
from reme.steps.common.shell import DEFAULT_TIMEOUT, ShellStep
from reme.components import R
def test_shell_step_is_registered():
"""Importing common steps makes shell_step discoverable."""
assert R.get(ComponentEnum.STEP, "shell_step") is ShellStep
def test_shell_step_default_timeout_is_one_day():
"""Shell commands may run for one day when no timeout is supplied."""
assert DEFAULT_TIMEOUT == 86400
@pytest.mark.asyncio
async def test_shell_step_returns_stdout_from_workspace(tmp_path):
"""Successful commands return stdout and run in the configured workspace."""
step = ShellStep(app_context=ApplicationContext(workspace_dir=str(tmp_path)))
response = await step(cmd="pwd")
assert response.success is True
assert response.answer.strip() == str(tmp_path)
assert response.metadata["returncode"] == 0
assert response.metadata["stderr"] == ""
@pytest.mark.asyncio
async def test_shell_step_reports_stderr_on_failure():
"""Failed commands expose stderr and their non-zero exit status."""
step = ShellStep()
response = await step(cmd="echo boom >&2; exit 7")
assert response.success is False
assert response.answer == "boom\n"
assert response.metadata["returncode"] == 7
assert response.metadata["stderr"] == "boom\n"
@pytest.mark.asyncio
async def test_shell_step_times_out():
"""Timeout terminates child processes as well as their parent shell."""
step = ShellStep()
started = time.monotonic()
response = await step(cmd="sleep 3 & wait", shell_timeout=0.01)
assert response.success is False
assert response.answer == "Shell command timed out after 0.01s"
assert response.metadata["shell_timeout"] == 0.01
assert time.monotonic() - started < 1
@pytest.mark.asyncio
async def test_shell_step_requires_a_command():
"""Blank commands are rejected without creating a subprocess."""
response = await ShellStep()(cmd=" ")
assert response.success is False
assert response.answer == "cmd is required"
@pytest.mark.asyncio
async def test_shell_step_does_not_accept_legacy_parameter_names():
"""Only cmd and shell_timeout configure shell execution."""
response = await ShellStep()(command="printf legacy", timeout=1)
assert response.success is False
assert response.answer == "cmd is required"
response = await ShellStep()(cmd="printf current", timeout=1)
assert response.success is True
assert response.answer == "current"
assert response.metadata["shell_timeout"] == DEFAULT_TIMEOUT