diff --git a/sandboxes/cli.py b/sandboxes/cli.py index b4f7c61..6c89bda 100644 --- a/sandboxes/cli.py +++ b/sandboxes/cli.py @@ -441,6 +441,7 @@ async def _test(): providers_to_test = [provider] if provider else ["e2b", "modal", "daytona"] results = [] + exit_code = 0 for p_name in providers_to_test: click.echo(f"\nšŸ”¬ Testing {p_name}...") @@ -461,19 +462,23 @@ async def _test(): # Test execute click.echo(" Executing command...") result = await p.execute_command(sandbox.id, "echo 'Hello from CLI test'") - if result.success and "Hello from CLI test" in result.stdout: + working = result.success and "Hello from CLI test" in result.stdout + if working: click.echo(" āœ… Execution successful") else: click.echo(" āŒ Execution failed", err=True) + exit_code = 1 # Test destroy click.echo(" Destroying sandbox...") - await p.destroy_sandbox(sandbox.id) + if not await p.destroy_sandbox(sandbox.id): + raise RuntimeError("Failed to destroy probe sandbox") click.echo(" āœ… Destroyed") - results.append((p_name, "āœ… Working")) + results.append((p_name, "āœ… Working" if working else "āŒ Failed: command test")) except Exception as e: + exit_code = 1 click.echo(f" āŒ Error: {e}", err=True) results.append((p_name, f"āŒ Failed: {str(e)[:50]}")) @@ -484,7 +489,9 @@ async def _test(): for p_name, status in results: click.echo(f"{p_name:10} {status}") - asyncio.run(_test()) + return exit_code + + sys.exit(asyncio.run(_test())) @cli.command() diff --git a/tests/test_cli_test_status.py b/tests/test_cli_test_status.py new file mode 100644 index 0000000..7dbcebc --- /dev/null +++ b/tests/test_cli_test_status.py @@ -0,0 +1,48 @@ +import pytest +from click.testing import CliRunner + +from sandboxes.base import ExecutionResult, Sandbox, SandboxState +from sandboxes.cli import cli + + +class ProbeProvider: + def __init__(self, exit_code, stdout, destroyed=True): + self.result = ExecutionResult(exit_code, stdout, "") + self.destroyed = destroyed + + async def create_sandbox(self, config): + return Sandbox(id="probe", provider="probe", state=SandboxState.RUNNING) + + async def execute_command(self, *args): + return self.result + + async def destroy_sandbox(self, sandbox_id): + return self.destroyed + + +@pytest.mark.parametrize("exit_code,stdout", [(1, ""), (0, "wrong output")]) +def test_failed_provider_probe_is_reported_as_failure(monkeypatch, exit_code, stdout): + monkeypatch.setattr("sandboxes.cli.get_provider", lambda name: ProbeProvider(exit_code, stdout)) + result = CliRunner().invoke(cli, ["test", "--provider", "e2b"]) + assert result.exit_code == 1 + assert "Working" not in result.stdout + + +def test_successful_provider_probe_returns_zero(monkeypatch): + monkeypatch.setattr( + "sandboxes.cli.get_provider", lambda name: ProbeProvider(0, "Hello from CLI test") + ) + result = CliRunner().invoke(cli, ["test", "--provider", "e2b"]) + assert result.exit_code == 0 + assert "Working" in result.stdout + + +def test_failed_probe_cleanup_returns_nonzero(monkeypatch): + monkeypatch.setattr( + "sandboxes.cli.get_provider", + lambda name: ProbeProvider(0, "Hello from CLI test", destroyed=False), + ) + result = CliRunner().invoke(cli, ["test", "--provider", "e2b"]) + assert result.exit_code == 1 + assert "Destroyed" not in result.stdout + assert "Working" not in result.stdout