""" Test Runner - 测试执行 """ import subprocess import os from typing import Optional from dataclasses import dataclass from loguru import logger @dataclass class TestResult: """测试结果""" success: bool output: str failed_tests: list[str] passed_count: int failed_count: int class TestRunner: """负责运行测试""" def __init__(self, project_path: str, project_id: str): self.project_path = project_path self.project_id = project_id def _detect_test_command(self) -> list[str]: """检测测试命令""" # Django 项目 if os.path.exists(os.path.join(self.project_path, "manage.py")): return ["python", "manage.py", "test", "--keepdb", "-v", "2"] # Python pytest if os.path.exists(os.path.join(self.project_path, "pytest.ini")) or \ os.path.exists(os.path.join(self.project_path, "pyproject.toml")): return ["pytest", "-v"] # Node.js if os.path.exists(os.path.join(self.project_path, "package.json")): return ["npm", "test"] # 默认 pytest return ["pytest", "-v"] def run_full_suite(self, timeout: int = 300) -> TestResult: """ 运行完整测试套件 Args: timeout: 超时时间(秒) Returns: TestResult """ cmd = self._detect_test_command() logger.info(f"运行测试: {' '.join(cmd)}") try: result = subprocess.run( cmd, cwd=self.project_path, capture_output=True, text=True, timeout=timeout, ) output = result.stdout + result.stderr success = result.returncode == 0 # 简单解析失败的测试 failed_tests = [] for line in output.split("\n"): if "FAILED" in line or "ERROR" in line: failed_tests.append(line.strip()) logger.info(f"测试{'通过' if success else '失败'}") return TestResult( success=success, output=output, failed_tests=failed_tests, passed_count=0, # TODO: 解析具体数量 failed_count=len(failed_tests), ) except subprocess.TimeoutExpired: logger.error(f"测试超时 ({timeout}秒)") return TestResult( success=False, output="测试执行超时", failed_tests=[], passed_count=0, failed_count=0, ) except Exception as e: logger.error(f"测试执行失败: {e}") return TestResult( success=False, output=str(e), failed_tests=[], passed_count=0, failed_count=0, ) def run_specific_test( self, test_path: str, timeout: int = 60, ) -> TestResult: """ 运行特定测试 Args: test_path: 测试路径 (如 tests.test_user.TestUserLogin) timeout: 超时时间 Returns: TestResult """ if self.project_id == "rtc_backend": cmd = ["python", "manage.py", "test", test_path, "--keepdb", "-v", "2"] else: cmd = ["pytest", test_path, "-v"] logger.info(f"运行测试: {' '.join(cmd)}") try: result = subprocess.run( cmd, cwd=self.project_path, capture_output=True, text=True, timeout=timeout, ) return TestResult( success=result.returncode == 0, output=result.stdout + result.stderr, failed_tests=[], passed_count=0, failed_count=0 if result.returncode == 0 else 1, ) except Exception as e: logger.error(f"测试执行失败: {e}") return TestResult( success=False, output=str(e), failed_tests=[], passed_count=0, failed_count=0, )