-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_run_python.py
More file actions
95 lines (71 loc) · 3.12 KB
/
Copy pathtest_run_python.py
File metadata and controls
95 lines (71 loc) · 3.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
"""Tests for run_python tool and Python guardrails."""
from __future__ import annotations
import pytest
from ai_agent.adapters.python_executor import SubprocessPythonExecutor
from ai_agent.domain.ports import CodeExecutionResult
from ai_agent.harness.python_guard import validate_python_code
from ai_agent.tools.run_python import RunPythonTool
class TestPythonGuard:
def test_allows_pure_math(self) -> None:
validate_python_code("print(sum(range(10)))")
def test_rejects_os_import(self) -> None:
with pytest.raises(ValueError, match="blocked module"):
validate_python_code("import os\nprint(os.getcwd())")
def test_rejects_eval(self) -> None:
with pytest.raises(ValueError, match="blocked call"):
validate_python_code("print(eval('1+1'))")
def test_rejects_open(self) -> None:
with pytest.raises(ValueError, match="blocked call"):
validate_python_code("open('/etc/passwd')")
def test_rejects_empty(self) -> None:
with pytest.raises(ValueError, match="non-empty"):
validate_python_code(" ")
class FakeExecutor:
def __init__(self, result: CodeExecutionResult) -> None:
self.result = result
self.calls: list[tuple[str, float]] = []
async def run(self, code: str, *, timeout_seconds: float = 5.0) -> CodeExecutionResult:
self.calls.append((code, timeout_seconds))
return self.result
@pytest.mark.asyncio
async def test_run_python_success() -> None:
tool = RunPythonTool(
FakeExecutor(CodeExecutionResult(success=True, stdout="42\n", exit_code=0))
)
result = await tool.execute({"code": "print(42)"})
assert result.success is True
assert result.output == "42"
@pytest.mark.asyncio
async def test_run_python_missing_code() -> None:
tool = RunPythonTool(FakeExecutor(CodeExecutionResult(success=True, stdout="")))
result = await tool.execute({})
assert result.success is False
@pytest.mark.asyncio
async def test_run_python_timeout_path() -> None:
tool = RunPythonTool(
FakeExecutor(
CodeExecutionResult(success=False, timed_out=True, error="execution timed out")
)
)
result = await tool.execute({"code": "while True: pass"})
assert result.success is False
assert "timed out" in (result.error or "")
@pytest.mark.asyncio
async def test_subprocess_executor_runs_snippet() -> None:
executor = SubprocessPythonExecutor()
result = await executor.run("print(2 + 2)")
assert result.success is True
assert result.stdout.strip() == "4"
@pytest.mark.asyncio
async def test_subprocess_executor_blocks_os() -> None:
executor = SubprocessPythonExecutor()
result = await executor.run("import os\nprint(1)")
assert result.success is False
assert result.error is not None
assert "blocked" in result.error
@pytest.mark.asyncio
async def test_subprocess_executor_timeout() -> None:
executor = SubprocessPythonExecutor()
result = await executor.run("import time\ntime.sleep(10)", timeout_seconds=0.2)
# time is not blocked; timeout should fire
assert result.timed_out is True or result.success is False