ReMe/tests/unit/test_shell_step.py
jinliyl 2e87b7a52e
Some checks are pending
Pre-commit / run (ubuntu-latest) (push) Waiting to run
Tests ReMe / Unit Tests - py3.11 (push) Waiting to run
Tests ReMe / Unit Tests - py3.12 (push) Waiting to run
Tests ReMe / Unit Tests - py3.13 (push) Waiting to run
Windows Smoke / CLI smoke - py3.11 (push) Waiting to run
feat(core): add shell execution and runtime memory status (#344)
* feat(core): add shell command execution and memory status reporting

- Introduce ShellStep for executing shell commands with timeout support
- Add StatusStep to report memory estimates for stateful data components
- Register shell and status commands in default configuration
- Update documentation with new reme status and shell command capabilities
- Implement comprehensive unit tests for both new step types
- Add support for asynchronous command execution with proper error handling

* feat(config): add log_config option to suppress config loading logs

- Add log_config parameter to resolve_app_config function with default True
- Conditionally log config loading messages based on log_config flag
- Update reme.py and service_utils.py to use log_config=False for client calls
- Suppress config logging in user-facing contexts to avoid output pollution

refactor(shell): rename command parameter to cmd for clarity

- Change 'command' to 'cmd' in default.yaml configuration schema
- Rename 'timeout' to 'shell_timeout' to avoid parameter name collisions
- Update ShellStep to accept both legacy and new parameter names
- Maintain backward compatibility with existing command/timeout usage

test(shell): add comprehensive tests for shell step parameter handling

- Add test cases for new cmd and shell_timeout parameter names
- Verify legacy command and timeout parameters still work
- Test blank command rejection message updated to use cmd
- Create integration test for shell parameter payload passing

* fix(shell): ensure proper environment loading and process timeout handling

- Move load_env() call to execute before parse_args() in main function
- Add proper process group killing for timeout scenarios on POSIX systems
- Implement recursive child process termination on Windows for proper cleanup
- Change parameter name from 'timeout' to 'shell_timeout' in shell execution
- Remove support for legacy 'command' and 'timeout' parameter names
- Update test cases to verify new timeout behavior and parameter requirements
- Add comments explaining component size tracking implementation details
2026-07-14 16:31:41 +08:00

100 lines
2.9 KiB
Python

"""Tests for asynchronous shell command execution."""
import asyncio
import time
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 _run(coro):
"""Run a coroutine on a fresh event loop."""
asyncio.run(coro)
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
def test_shell_step_returns_stdout_from_workspace(tmp_path):
"""Successful commands return stdout and run in the configured workspace."""
async def run():
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"] == ""
_run(run())
def test_shell_step_reports_stderr_on_failure():
"""Failed commands expose stderr and their non-zero exit status."""
async def run():
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"
_run(run())
def test_shell_step_times_out():
"""Timeout terminates child processes as well as their parent shell."""
async def run():
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
_run(run())
def test_shell_step_requires_a_command():
"""Blank commands are rejected without creating a subprocess."""
async def run():
response = await ShellStep()(cmd=" ")
assert response.success is False
assert response.answer == "cmd is required"
_run(run())
def test_shell_step_does_not_accept_legacy_parameter_names():
"""Only cmd and shell_timeout configure shell execution."""
async def run():
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
_run(run())