# -*- coding: utf-8 -*- """统计引擎全量回归驱动:顺序串跑全量测试(Temp/claude 的 e2e_* 探针 + backend/scripts 的 tc 套件)。 用法: python run_regression.py # 全量(e2e 探针 + 正式 tc 套件) python run_regression.py gwas ocs # 只跑名字含 gwas / ocs 的测试 python run_regression.py --keep-logs # 保留上次日志(默认每次清空) 覆盖: - Temp/claude/e2e_*.py(排除 e2e_probe* 探查工具)——61 个统计引擎/业务链探针 - backend/scripts/test_bre_analysis_*_tc.py——正式 tc 套件(TestClient 走真实 API, ssGBLUP/OCS/MABC/孟德尔 golden 数值断言,新模块必测) 说明: - 每个测试完整输出写 Temp/claude/regression_logs/.log,控制台只回显尾部 + EXIT。 - 测试均为独立进程(不需后端 uvicorn 服务),自带 fixture 清理,顺序串跑最稳 (曾有过并发机器争用 RC=124 超时的历史,故不并发)。 - 最终按 EXIT 汇总:任一失败整体 exit != 0(供交付流程判断)。 - 解释器默认取 sys.executable(须用 dpb env python 运行本脚本);可用环境变量 DPB_PYTHON 覆盖(如 DPB_PYTHON=C:/ai/miniconda3/envs/dpb/python.exe)。 """ import glob import os import subprocess import sys sys.stdout.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8") ROOT = os.path.dirname(os.path.abspath(__file__)) PROBE_DIR = os.path.join(ROOT, "Temp", "claude") TC_DIR = os.path.join(ROOT, "backend", "scripts") LOG_DIR = os.path.join(PROBE_DIR, "regression_logs") PY = os.environ.get("DPB_PYTHON", sys.executable) KEEP_LOGS = "--keep-logs" in sys.argv def main() -> int: filters = [a for a in sys.argv[1:] if not a.startswith("--")] probes = sorted(glob.glob(os.path.join(PROBE_DIR, "e2e_*.py"))) probes = [p for p in probes if "e2e_probe" not in os.path.basename(p)] probes += sorted(glob.glob(os.path.join(TC_DIR, "test_bre_analysis_*_tc.py"))) if filters: probes = [p for p in probes if any(f in os.path.basename(p) for f in filters)] if not probes: print("未找到匹配探针") return 1 if not KEEP_LOGS and os.path.isdir(LOG_DIR): for old in glob.glob(os.path.join(LOG_DIR, "*.log")): os.remove(old) os.makedirs(LOG_DIR, exist_ok=True) env = dict(os.environ) env.setdefault("ENVIRONMENT", "dev") env["PYTHONPATH"] = os.pathsep.join([os.path.join(ROOT, "backend"), PROBE_DIR]) env.setdefault("PYTHONIOENCODING", "utf-8") passed, failed, timed_out = [], [], [] for i, p in enumerate(probes, 1): name = os.path.basename(p) log_path = os.path.join(LOG_DIR, name + ".log") print(f"[{i}/{len(probes)}] {name}") try: with open(log_path, "w", encoding="utf-8") as log: rc = subprocess.call( [PY, "-X", "utf8", p], cwd=os.path.dirname(p), env=env, stdout=log, stderr=subprocess.STDOUT, timeout=1800) except subprocess.TimeoutExpired: rc = 124 with open(log_path, "a", encoding="utf-8") as log: log.write("\n[回归驱动] 探针超时(>30min)被杀\n") with open(log_path, "r", encoding="utf-8") as log: tail = log.readlines()[-12:] for line in tail: print(" " + line.rstrip()) print(f" -> EXIT={rc}") if rc == 0: passed.append(name) elif rc == 124: timed_out.append(name) failed.append(name) else: failed.append(name) print("\n" + "=" * 40) print(f"全量回归:pass={len(passed)} fail={len(failed)}" + (f"(其中超时 {len(timed_out)})" if timed_out else "")) if failed: print("FAILED:") for f in failed: print(f" {f}") print(f"完整输出见 {LOG_DIR}/") return 1 print("全部通过") return 0 if __name__ == "__main__": sys.exit(main())