mirror of
https://github.com/open-jarvis/OpenJarvis.git
synced 2026-07-28 14:07:55 +00:00
113 lines
3.5 KiB
Python
113 lines
3.5 KiB
Python
"""Tests for tool execution timeout (Phase 14.1)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
|
|
from openjarvis.core.events import EventBus, EventType
|
|
from openjarvis.core.types import ToolCall, ToolResult
|
|
from openjarvis.tools._stubs import BaseTool, ToolExecutor, ToolSpec
|
|
|
|
|
|
class SlowTool(BaseTool):
|
|
"""A tool that sleeps for a configurable duration."""
|
|
|
|
tool_id = "slow_tool"
|
|
|
|
def __init__(self, delay: float = 5.0):
|
|
self._delay = delay
|
|
|
|
@property
|
|
def spec(self) -> ToolSpec:
|
|
return ToolSpec(
|
|
name="slow_tool",
|
|
description="A tool that takes a long time.",
|
|
timeout_seconds=1.0, # 1-second timeout
|
|
)
|
|
|
|
def execute(self, **params) -> ToolResult:
|
|
time.sleep(self._delay)
|
|
return ToolResult(tool_name="slow_tool", content="Done", success=True)
|
|
|
|
|
|
class FastTool(BaseTool):
|
|
"""A tool that returns immediately."""
|
|
|
|
tool_id = "fast_tool"
|
|
|
|
@property
|
|
def spec(self) -> ToolSpec:
|
|
return ToolSpec(
|
|
name="fast_tool",
|
|
description="A fast tool.",
|
|
timeout_seconds=10.0,
|
|
)
|
|
|
|
def execute(self, **params) -> ToolResult:
|
|
return ToolResult(
|
|
tool_name="fast_tool",
|
|
content=f"Result: {params.get('input', '')}",
|
|
success=True,
|
|
)
|
|
|
|
|
|
class TestToolTimeout:
|
|
def test_fast_tool_succeeds(self):
|
|
executor = ToolExecutor([FastTool()])
|
|
call = ToolCall(id="1", name="fast_tool", arguments='{"input": "hello"}')
|
|
result = executor.execute(call)
|
|
assert result.success
|
|
assert "hello" in result.content
|
|
|
|
def test_slow_tool_times_out(self):
|
|
executor = ToolExecutor([SlowTool(delay=5.0)])
|
|
call = ToolCall(id="1", name="slow_tool", arguments="{}")
|
|
result = executor.execute(call)
|
|
assert not result.success
|
|
assert "timed out" in result.content
|
|
|
|
def test_timeout_event_emitted(self):
|
|
bus = EventBus(record_history=True)
|
|
executor = ToolExecutor([SlowTool(delay=5.0)], bus=bus)
|
|
call = ToolCall(id="1", name="slow_tool", arguments="{}")
|
|
executor.execute(call)
|
|
|
|
timeout_events = [
|
|
e for e in bus.history if e.event_type == EventType.TOOL_TIMEOUT
|
|
]
|
|
assert len(timeout_events) == 1
|
|
assert timeout_events[0].data["tool"] == "slow_tool"
|
|
|
|
def test_default_timeout_used(self):
|
|
"""When ToolSpec has no timeout, the executor default is used."""
|
|
|
|
class NoTimeoutTool(BaseTool):
|
|
tool_id = "no_timeout"
|
|
|
|
@property
|
|
def spec(self):
|
|
return ToolSpec(
|
|
name="no_timeout",
|
|
description="test",
|
|
timeout_seconds=0,
|
|
)
|
|
|
|
def execute(self, **params):
|
|
return ToolResult(tool_name="no_timeout", content="ok")
|
|
|
|
executor = ToolExecutor([NoTimeoutTool()], default_timeout=60.0)
|
|
call = ToolCall(id="1", name="no_timeout", arguments="{}")
|
|
result = executor.execute(call)
|
|
assert result.success
|
|
|
|
def test_timeout_seconds_on_toolspec(self):
|
|
spec = ToolSpec(name="test", description="test", timeout_seconds=42.0)
|
|
assert spec.timeout_seconds == 42.0
|
|
|
|
def test_unknown_tool(self):
|
|
executor = ToolExecutor([FastTool()])
|
|
call = ToolCall(id="1", name="nonexistent", arguments="{}")
|
|
result = executor.execute(call)
|
|
assert not result.success
|
|
assert "Unknown tool" in result.content
|