Stack-2-9-finetuned / samples /integration /test_tool_chains.py
walidsobhie-code
refactor: Squeeze folders further - cleaner structure
65888d5
#!/usr/bin/env python3
"""
Integration Tests for Stack 2.9 Tool Chains
Multi-tool sequence tests.
"""
import pytest
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch
# Add stack_cli to path
sys.path.insert(0, str(Path(__file__).parent.parent / "stack_cli"))
from stack_cli.agent import StackAgent, create_agent
from stack_cli.tools import get_tool, list_tools
class TestToolChains:
"""Test tool chain sequences."""
def test_read_grep_chain(self):
"""Test read then grep workflow."""
call_log = []
def mock_tool(**kwargs):
call_log.append(kwargs)
if "path" in kwargs:
return {"success": True, "content": "line1\nline2\nline3"}
return {"success": True, "matches": []}
with patch('stack_cli.context.create_context_manager'):
with patch('stack_cli.tools.get_tool', return_value=mock_tool):
agent = StackAgent()
# Simulate reading then grepping
read_result = get_tool("read")(path="test.py")
grep_result = get_tool("grep")(path="test.py", pattern="line")
assert read_result["success"] is True
assert grep_result["success"] is True
def test_search_copy_chain(self):
"""Test search then copy workflow."""
with patch('stack_cli.context.create_context_manager'):
with patch('stack_cli.tools.get_tool') as mock_get_tool:
mock_tool = MagicMock(return_value={"success": True, "matches": ["file1.py"]})
mock_get_tool.return_value = mock_tool
agent = StackAgent()
# Search for files
search_result = get_tool("search")(path=".", pattern="*.py")
# If found, copy
if search_result.get("success") and search_result.get("matches"):
copy_result = get_tool("copy")(source="file1.py", destination="backup.py")
assert copy_result["success"] is True
def test_git_status_branch_chain(self):
"""Test git status then branch workflow."""
with patch('stack_cli.context.create_context_manager'):
with patch('stack_cli.tools.get_tool') as mock_get_tool:
mock_tool = MagicMock(return_value={"success": True, "files": []})
mock_get_tool.return_value = mock_tool
agent = StackAgent()
# Check status
status_result = get_tool("git_status")(repo_path=".")
# List branches
branch_result = get_tool("git_branch")(repo_path=".")
assert status_result["success"] is True
assert branch_result["success"] is True
class TestComplexToolSequences:
"""Test complex multi-tool sequences."""
def test_file_edit_save_sequence(self):
"""Test file edit and save sequence."""
with patch('stack_cli.context.create_context_manager'):
with patch('stack_cli.tools.get_tool') as mock_get_tool:
mock_tool = MagicMock(return_value={"success": True})
mock_get_tool.return_value = mock_tool
agent = StackAgent()
# Read file
read_result = get_tool("read")(path="test.py")
# Edit
edit_result = get_tool("edit")(
path="test.py",
old_text="old_content",
new_text="new_content"
)
# Write back
write_result = get_tool("write")(
path="test.py",
content="new_content"
)
assert edit_result["success"] is True
def test_code_test_lint_sequence(self):
"""Test code test and lint sequence."""
with patch('stack_cli.context.create_context_manager'):
with patch('stack_cli.tools.get_tool') as mock_get_tool:
mock_tool = MagicMock(return_value={"success": True, "output": "ok"})
mock_get_tool.return_value = mock_tool
agent = StackAgent()
# Run tests
test_result = get_tool("test")(path=".", pattern="test*.py")
# Lint
lint_result = get_tool("lint")(path=".")
assert test_result.get("success") or "output" in test_result
assert lint_result.get("success") or "output" in lint_result
def test_project_scan_context_sequence(self):
"""Test project scan then load context."""
with patch('stack_cli.context.create_context_manager'):
with patch('stack_cli.tools.get_tool') as mock_get_tool:
mock_tool = MagicMock(return_value={
"success": True,
"project": {"name": "test", "files": ["a.py", "b.py"]}
})
mock_get_tool.return_value = mock_tool
agent = StackAgent()
# Scan project
scan_result = get_tool("project_scan")(path=".")
# Load context
context_result = get_tool("context_load")()
assert scan_result["success"] is True
assert context_result["success"] is True
class TestToolChainErrors:
"""Test error handling in tool chains."""
def test_chain_continues_on_error(self):
"""Test that chain continues when one tool fails."""
call_count = [0]
def mock_tool(**kwargs):
call_count[0] += 1
if call_count[0] == 1:
return {"success": False, "error": "File not found"}
return {"success": True}
with patch('stack_cli.context.create_context_manager'):
with patch('stack_cli.tools.get_tool', return_value=mock_tool):
agent = StackAgent()
# First tool fails
result1 = get_tool("read")(path="missing.py")
# Second tool should still work
result2 = get_tool("write")(path="new.txt", content="x")
assert result1["success"] is False
assert result2["success"] is True
assert call_count[0] == 2
def test_rollback_on_error(self):
"""Test rollback behavior on error."""
with patch('stack_cli.context.create_context_manager'):
with patch('stack_cli.tools.get_tool') as mock_get_tool:
mock_tool = MagicMock(side_effect=[
{"success": True},
{"success": False, "error": "Operation failed"},
{"success": True} # rollback
])
mock_get_tool.return_value = mock_tool
# First operation
result1 = get_tool("write")(path="a.txt", content="x")
assert result1["success"] is True
# Second operation fails - rollback attempted
try:
result2 = get_tool("write")(path="b.txt", content="y")
except:
pass
class TestParallelToolExecution:
"""Test parallel tool execution."""
def test_parallel_file_operations(self):
"""Test parallel file operations."""
with patch('stack_cli.context.create_context_manager'):
with patch('stack_cli.tools.get_tool') as mock_get_tool:
mock_tool = MagicMock(return_value={"success": True})
mock_get_tool.return_value = mock_tool
# Would simulate parallel execution
# In real implementation, use asyncio
result1 = get_tool("read")(path="file1.txt")
result2 = get_tool("read")(path="file2.txt")
assert result1["success"] is True
assert result2["success"] is True
class TestToolDependencyResolution:
"""Test tool dependency resolution."""
def test_git_needs_repo(self):
"""Test that git tools need repo path."""
# Git operations require repo path
result = get_tool("git_status")(repo_path=".")
# Success depends on whether it's a valid git repo
# But it should not crash
assert "success" in result
def test_edit_needs_file(self):
"""Test that edit tool needs existing file."""
result = get_tool("edit")(
path="/nonexistent/file.txt",
old_text="old",
new_text="new"
)
assert result["success"] is False
def test_memory_needs_workspace(self):
"""Test that memory tools need workspace."""
result = get_tool("memory_save")(key="test", value="data")
# Should succeed or fail gracefully
assert "success" in result
class TestToolChainsPerformance:
"""Test tool chain performance."""
def test_rapid_tool_calls(self):
"""Test rapid sequential tool calls."""
import time
with patch('stack_cli.context.create_context_manager'):
with patch('stack_cli.tools.get_tool') as mock_get_tool:
mock_tool = MagicMock(return_value={"success": True})
mock_get_tool.return_value = mock_tool
start = time.time()
for _ in range(10):
get_tool("read")(path="test.py")
elapsed = time.time() - start
# Should complete reasonably fast
assert elapsed < 5.0 # 5 seconds for 10 calls
def test_memory_efficiency(self):
"""Test memory efficiency of tool chains."""
with patch('stack_cli.context.create_context_manager'):
agent = StackAgent()
# Process multiple queries
for _ in range(5):
agent.process("test query")
# Should not accumulate too much data
assert len(agent.conversation_history) >= 0
if __name__ == "__main__":
pytest.main([__file__, "-v"])