test_e2e_stitcher.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. """端到端测试:CodingAgent 自主完成图片拼接工具的编写、测试和注册
  2. 运行方式:
  3. uv run python -m tests.test_e2e_stitcher
  4. 前置条件:
  5. - ANTHROPIC_API_KEY 已设置
  6. - uv 已安装
  7. """
  8. import asyncio
  9. import json
  10. import logging
  11. import sys
  12. from pathlib import Path
  13. # 确保项目根目录在 sys.path 中
  14. ROOT = Path(__file__).resolve().parent.parent
  15. sys.path.insert(0, str(ROOT / "src"))
  16. from tool_agent.config import settings
  17. from tool_agent.tool.agent import CodingAgent
  18. from tool_agent.registry.registry import ToolRegistry
  19. logging.basicConfig(
  20. level=logging.INFO,
  21. format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
  22. )
  23. logger = logging.getLogger("e2e_test")
  24. TOOL_ID = "image_stitcher"
  25. TASK_SPEC = f"""\
  26. 请创建一个「图片拼接工具」,使用本地 uv 环境。
  27. ## 功能需求
  28. 将多张图片按指定方式拼接成一张大图。
  29. ## 输入参数(POST /stitch,JSON Body)
  30. - images: list[str] — Base64 编码的图片列表(至少 2 张)
  31. - direction: str — 拼接方向,可选 "horizontal" | "vertical" | "grid",默认 "horizontal"
  32. - columns: int — grid 模式下每行列数,默认 2
  33. - spacing: int — 图片间距(像素),默认 0
  34. - background_color: str — 间距填充色,默认 "#FFFFFF"
  35. - resize_mode: str — "none" 不缩放 | "fit_width" 统一宽度 | "fit_height" 统一高度,默认 "none"
  36. ## 输出(JSON)
  37. - image: str — 拼接结果,Base64 编码的 PNG
  38. - width: int — 结果图宽度
  39. - height: int — 结果图高度
  40. ## 技术要求
  41. 1. 使用 uv 环境,项目名 {TOOL_ID}
  42. 2. 核心依赖:Pillow
  43. 3. HTTP 接口:FastAPI + uvicorn,端口自选
  44. 4. 路由:POST /stitch(拼接)、GET /health(健康检查,返回 {{"status":"ok"}})
  45. 5. 编写一个自测脚本 test_stitch.py:生成几张纯色小图 → 调用拼接函数 → 验证输出尺寸正确
  46. 6. 自测通过后,使用 register_tool 注册,tool_id = "{TOOL_ID}"
  47. ## 注意
  48. - 这是 uv 本地项目,不需要 Docker
  49. - 先跑通自测脚本再注册,确保核心逻辑正确
  50. """
  51. async def run_test() -> bool:
  52. """执行端到端测试,返回是否通过"""
  53. logger.info("=" * 60)
  54. logger.info("E2E Test: Image Stitcher Tool")
  55. logger.info("=" * 60)
  56. # ---- 1. 执行任务 ----
  57. agent = CodingAgent()
  58. logger.info("Sending task to CodingAgent...")
  59. result = await agent.execute(TASK_SPEC)
  60. logger.info("Agent finished. Result:")
  61. logger.info(result)
  62. # ---- 2. 验证注册 ----
  63. logger.info("-" * 60)
  64. logger.info("Verifying registration...")
  65. registry = ToolRegistry()
  66. tool = registry.get(TOOL_ID)
  67. if not tool:
  68. logger.error(f"FAIL: tool '{TOOL_ID}' not found in registry")
  69. return False
  70. logger.info(f"OK: tool registered — {tool.tool_id} ({tool.name})")
  71. logger.info(f" runtime: {tool.runtime.type.value}")
  72. logger.info(f" status: {tool.status.value}")
  73. endpoint = registry.get_endpoint(TOOL_ID)
  74. if endpoint:
  75. logger.info(f" endpoint: {json.dumps(endpoint, ensure_ascii=False)}")
  76. else:
  77. logger.warning(" endpoint: None (may be expected for local stdio tool)")
  78. # ---- 3. 验证项目文件 ----
  79. logger.info("-" * 60)
  80. logger.info("Verifying project files...")
  81. project_dir = settings.tools_dir / "local" / TOOL_ID
  82. checks = {
  83. "project dir exists": project_dir.is_dir(),
  84. "pyproject.toml": (project_dir / "pyproject.toml").exists(),
  85. }
  86. all_pass = True
  87. for label, ok in checks.items():
  88. status = "OK" if ok else "FAIL"
  89. logger.info(f" {status}: {label}")
  90. if not ok:
  91. all_pass = False
  92. # 列出项目内文件
  93. if project_dir.is_dir():
  94. files = sorted(p.relative_to(project_dir) for p in project_dir.rglob("*") if p.is_file())
  95. logger.info(f" project files ({len(files)}):")
  96. for f in files:
  97. logger.info(f" {f}")
  98. # ---- 4. 汇总 ----
  99. logger.info("=" * 60)
  100. if all_pass and tool:
  101. logger.info("E2E TEST PASSED")
  102. else:
  103. logger.error("E2E TEST FAILED")
  104. return all_pass and tool is not None
  105. if __name__ == "__main__":
  106. ok = asyncio.run(run_test())
  107. sys.exit(0 if ok else 1)