409 lines
15 KiB
Python
409 lines
15 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Прямой тест ducklm через RuntimeController (без HTTP сервера).
|
||
Позволяет ИИ-кодеру тестировать систему через отправку запросов и проверку выполнения.
|
||
"""
|
||
|
||
import json
|
||
import time
|
||
import sys
|
||
from pathlib import Path
|
||
from typing import Dict, Any
|
||
|
||
# Добавляем текущую директорию в путь для импорта app
|
||
sys.path.insert(0, '.')
|
||
|
||
from app.runtime.runtime_controller import RuntimeController
|
||
from app.core.contracts import UserTask
|
||
|
||
|
||
class DuckLMDirectTester:
|
||
def __init__(self, base_dir: str = "."):
|
||
self.base_dir = Path(base_dir)
|
||
self.test_results = []
|
||
self.controller = None
|
||
|
||
def setup(self):
|
||
"""Инициализировать контроллер"""
|
||
try:
|
||
print("Инициализация RuntimeController...")
|
||
self.controller = RuntimeController(base_dir=self.base_dir)
|
||
print("RuntimeController инициализирован успешно")
|
||
return True
|
||
except Exception as e:
|
||
print(f"Ошибка инициализации RuntimeController: {e}")
|
||
return False
|
||
|
||
def log_test(self, test_name: str, passed: bool, details: str = ""):
|
||
"""Записать результат теста"""
|
||
result = {
|
||
"test": test_name,
|
||
"passed": passed,
|
||
"details": details,
|
||
"timestamp": time.time()
|
||
}
|
||
self.test_results.append(result)
|
||
status = "✓ PASS" if passed else "✗ FAIL"
|
||
print(f"{status}: {test_name}")
|
||
if details:
|
||
print(f" Details: {details}")
|
||
|
||
def test_health(self) -> bool:
|
||
"""Проверить что контроллер работает"""
|
||
try:
|
||
if self.controller is None:
|
||
self.log_test("Health Check", False, "Controller not initialized")
|
||
return False
|
||
|
||
# Проверяем что основные компоненты присутствуют
|
||
components = [
|
||
("event_bus", self.controller.event_bus),
|
||
("permission_service", self.controller.permission_service),
|
||
("task_state_store", self.controller.task_state_store),
|
||
("checkpoint_store", self.controller.checkpoint_store),
|
||
("context_builder", self.controller.context_builder),
|
||
("router", self.controller.router),
|
||
("execution_engine", self.controller.execution_engine),
|
||
]
|
||
|
||
missing = []
|
||
for name, component in components:
|
||
if component is None:
|
||
missing.append(name)
|
||
|
||
if missing:
|
||
self.log_test("Health Check", False, f"Missing components: {missing}")
|
||
return False
|
||
else:
|
||
self.log_test("Health Check", True, "Все компоненты инициализированы")
|
||
return True
|
||
|
||
except Exception as e:
|
||
self.log_test("Health Check", False, f"Error: {str(e)}")
|
||
return False
|
||
|
||
def test_simple_task(self) -> bool:
|
||
"""Простой тест задачи"""
|
||
try:
|
||
if self.controller is None:
|
||
self.log_test("Simple Task", False, "Controller not initialized")
|
||
return False
|
||
|
||
# Создаем простую задачу
|
||
task = UserTask(input="Привет, как дела?")
|
||
|
||
# Выполняем задачу через контроллер
|
||
result = self.controller.handle_task(task)
|
||
|
||
status = result.get("status")
|
||
if status in ["completed", "awaiting_permission", "awaiting_input"]:
|
||
self.log_test(
|
||
"Simple Task",
|
||
True,
|
||
f"Status: {status}, Task ID: {result.get('task_id')}"
|
||
)
|
||
return True
|
||
else:
|
||
self.log_test(
|
||
"Simple Task",
|
||
False,
|
||
f"Unexpected status: {status}"
|
||
)
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.log_test("Simple Task", False, f"Request error: {str(e)}")
|
||
return False
|
||
|
||
def test_tool_task(self) -> bool:
|
||
"""Тест задачи с инструментом"""
|
||
try:
|
||
if self.controller is None:
|
||
self.log_test("Tool Task", False, "Controller not initialized")
|
||
return False
|
||
|
||
# Тест простой команды shell через контекст
|
||
task = UserTask(
|
||
input="Выполни простую команду",
|
||
context={
|
||
"requested_tool": "shell_exec",
|
||
"tool_args": {"command": "echo 'hello from test'"}
|
||
}
|
||
)
|
||
|
||
result = self.controller.handle_task(task)
|
||
|
||
status = result.get("status")
|
||
if status == "completed":
|
||
output = result.get("result", {}).get("output", "")
|
||
if "hello from test" in output:
|
||
self.log_test(
|
||
"Tool Task",
|
||
True,
|
||
f"Command executed successfully: {output.strip()}"
|
||
)
|
||
return True
|
||
else:
|
||
self.log_test(
|
||
"Tool Task",
|
||
False,
|
||
f"Unexpected output: {output}"
|
||
)
|
||
return False
|
||
elif status == "awaiting_permission":
|
||
self.log_test(
|
||
"Tool Task",
|
||
True,
|
||
"Permission required (expected for some commands)"
|
||
)
|
||
return True
|
||
else:
|
||
self.log_test(
|
||
"Tool Task",
|
||
False,
|
||
f"Unexpected status: {status}"
|
||
)
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.log_test("Tool Task", False, f"Request error: {str(e)}")
|
||
return False
|
||
|
||
def test_memory_tools(self) -> bool:
|
||
"""Тест инструментов памяти"""
|
||
try:
|
||
if self.controller is None:
|
||
self.log_test("Memory Tools", False, "Controller not initialized")
|
||
return False
|
||
|
||
# Тест вставки в память
|
||
task_insert = UserTask(
|
||
input="Запомни эту информацию: тестовое значение 123",
|
||
context={
|
||
"requested_tool": "memory",
|
||
"tool_args": {
|
||
"operation": "insert",
|
||
"text": "тестовое значение 123",
|
||
"kind": "fact",
|
||
"weight": 0.8
|
||
}
|
||
}
|
||
)
|
||
|
||
result_insert = self.controller.handle_task(task_insert)
|
||
|
||
if result_insert.get("status") != "completed":
|
||
self.log_test(
|
||
"Memory Tools Insert",
|
||
False,
|
||
f"Insert failed: {result_insert.get('status')}"
|
||
)
|
||
return False
|
||
|
||
# Тест поиска в памяти
|
||
task_search = UserTask(
|
||
input="Найди запомненную информацию",
|
||
context={
|
||
"requested_tool": "memory",
|
||
"tool_args": {
|
||
"operation": "search",
|
||
"query": "тестовое значение",
|
||
"limit": 5
|
||
}
|
||
}
|
||
)
|
||
|
||
result_search = self.controller.handle_task(task_search)
|
||
|
||
if result_search.get("status") == "completed":
|
||
output = result_search.get("result", {}).get("output", "")
|
||
self.log_test(
|
||
"Memory Tools",
|
||
True,
|
||
f"Memory search successful: {output[:100]}..."
|
||
)
|
||
return True
|
||
else:
|
||
self.log_test(
|
||
"Memory Tools Search",
|
||
False,
|
||
f"Search failed: {result_search.get('status')}"
|
||
)
|
||
return False
|
||
|
||
except Exception as e:
|
||
self.log_test("Memory Tools", False, f"Request error: {str(e)}")
|
||
return False
|
||
|
||
def test_file_operations(self) -> bool:
|
||
"""Тест операций с файлами"""
|
||
try:
|
||
if self.controller is None:
|
||
self.log_test("File Operations", False, "Controller not initialized")
|
||
return False
|
||
|
||
import tempfile
|
||
import os
|
||
|
||
# Создаем временный файл для теста
|
||
with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
|
||
temp_path = f.name
|
||
f.write("initial content for testing")
|
||
|
||
try:
|
||
# Тест чтения файла
|
||
task_read = UserTask(
|
||
input="Прочитай файл",
|
||
context={
|
||
"requested_tool": "file_read",
|
||
"tool_args": {"path": temp_path}
|
||
}
|
||
)
|
||
|
||
result_read = self.controller.handle_task(task_read)
|
||
|
||
if result_read.get("status") != "completed":
|
||
self.log_test(
|
||
"File Read",
|
||
False,
|
||
f"Read failed: {result_read.get('status')}"
|
||
)
|
||
return False
|
||
|
||
# Тест записи файла
|
||
new_content = "updated content from test"
|
||
task_write = UserTask(
|
||
input="Запиши в файл",
|
||
context={
|
||
"requested_tool": "file_write",
|
||
"tool_args": {
|
||
"path": temp_path,
|
||
"content": new_content
|
||
}
|
||
}
|
||
)
|
||
|
||
result_write = self.controller.handle_task(task_write)
|
||
|
||
if result_write.get("status") != "completed":
|
||
self.log_test(
|
||
"File Write",
|
||
False,
|
||
f"Write failed: {result_write.get('status')}"
|
||
)
|
||
return False
|
||
|
||
# Проверяем что файл действительно обновился
|
||
with open(temp_path, 'r') as f:
|
||
actual_content = f.read()
|
||
|
||
if actual_content == new_content:
|
||
self.log_test(
|
||
"File Operations",
|
||
True,
|
||
f"File read/write successful: {actual_content}"
|
||
)
|
||
return True
|
||
else:
|
||
self.log_test(
|
||
"File Operations",
|
||
False,
|
||
f"File content mismatch. Expected: {new_content}, Got: {actual_content}"
|
||
)
|
||
return False
|
||
|
||
finally:
|
||
# Очищаем временный файл
|
||
if os.path.exists(temp_path):
|
||
os.unlink(temp_path)
|
||
|
||
except Exception as e:
|
||
self.log_test("File Operations", False, f"Request error: {str(e)}")
|
||
return False
|
||
|
||
def run_all_tests(self) -> Dict[str, Any]:
|
||
"""Запустить все тесты"""
|
||
print("Starting direct ducklm tests...")
|
||
print("=" * 50)
|
||
|
||
if not self.setup():
|
||
print("Failed to setup controller")
|
||
return {"error": "Setup failed"}
|
||
|
||
tests = [
|
||
self.test_health,
|
||
self.test_simple_task,
|
||
self.test_tool_task,
|
||
self.test_memory_tools,
|
||
self.test_file_operations,
|
||
]
|
||
|
||
passed = 0
|
||
total = len(tests)
|
||
|
||
for test in tests:
|
||
if test():
|
||
passed += 1
|
||
time.sleep(0.5) # Небольшая пауза между тестами
|
||
|
||
print("=" * 50)
|
||
print(f"Tests completed: {passed}/{total} passed")
|
||
|
||
# Сводка результатов
|
||
summary = {
|
||
"total_tests": total,
|
||
"passed_tests": passed,
|
||
"failed_tests": total - passed,
|
||
"success_rate": passed / total if total > 0 else 0,
|
||
"test_results": self.test_results
|
||
}
|
||
|
||
return summary
|
||
|
||
|
||
def main():
|
||
"""Основная функция"""
|
||
import argparse
|
||
|
||
parser = argparse.ArgumentParser(description="Тест ducklm системы (прямой доступ)")
|
||
parser.add_argument("--basedir", default=".", help="Base directory for ducklm")
|
||
parser.add_argument("--test", choices=["health", "simple", "tool", "memory", "file", "all"],
|
||
default="all", help="Specific test to run")
|
||
|
||
args = parser.parse_args()
|
||
|
||
tester = DuckLMDirectTester(args.basedir)
|
||
|
||
if args.test == "all":
|
||
results = tester.run_all_tests()
|
||
print("\nFINAL RESULTS:")
|
||
print(f"Passed: {results['passed_tests']}/{results['total_tests']}")
|
||
print(f"Success Rate: {results['success_rate']*100:.1f}%")
|
||
|
||
# Возвращаем код выхода basado на результатах
|
||
sys.exit(0 if results['failed_tests'] == 0 else 1)
|
||
else:
|
||
# Запуск конкретного теста
|
||
if not tester.setup():
|
||
print("Failed to setup controller")
|
||
sys.exit(1)
|
||
|
||
test_map = {
|
||
"health": tester.test_health,
|
||
"simple": tester.test_simple_task,
|
||
"tool": tester.test_tool_task,
|
||
"memory": tester.test_memory_tools,
|
||
"file": tester.test_file_operations,
|
||
}
|
||
|
||
test_func = test_map[args.test]
|
||
if test_func():
|
||
print(f"Test {args.test}: PASSED")
|
||
sys.exit(0)
|
||
else:
|
||
print(f"Test {args.test}: FAILED")
|
||
sys.exit(1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main() |