aps-agent/poc/pi-fallback/regression_driver.py

94 lines
3.6 KiB
Python
Raw Normal View History

# Agent-N 全量回归可续跑驱动器:逐文件跑 pytest,结果追加 JSONL。
# 用法:.venv/Scripts/python.exe poc/pi-fallback/regression_driver.py <清单文件> <结果.jsonl> [预算秒] [单文件超时秒]
# 已记录在结果文件中的条目自动跳过;单文件超时记录为 TIMEOUT 后交由人工拆分。
import json
import re
import subprocess
import sys
import time
from pathlib import Path
REPO = Path(__file__).resolve().parents[2]
PY = REPO / ".venv" / "Scripts" / "python.exe"
SUMMARY_RE = re.compile(
r"(?:(\d+) passed)?[,\s]*(?:(\d+) failed)?[,\s]*(?:(\d+) error[s]?)?[,\s]*"
r"(?:(\d+) skipped)?[,\s]*(?:(\d+) deselected)?")
def parse_summary(out: str) -> dict:
res = {"passed": 0, "failed": 0, "errors": 0, "skipped": 0}
for line in reversed(out.splitlines()):
line = line.strip()
if "passed" in line or "failed" in line or "error" in line:
m = SUMMARY_RE.search(line)
if m and ("in " in line and "s" in line):
res["passed"] = int(m.group(1) or 0)
res["failed"] = int(m.group(2) or 0)
res["errors"] = int(m.group(3) or 0)
res["skipped"] = int(m.group(4) or 0)
return res
return res
def failed_names(out: str) -> list[str]:
names = []
for line in out.splitlines():
s = line.strip()
if s.startswith(("FAILED ", "ERROR ")):
names.append(s[:300])
return names
def main() -> int:
list_file, results_file = Path(sys.argv[1]), Path(sys.argv[2])
budget = float(sys.argv[3]) if len(sys.argv) > 3 else 250.0
per_file_timeout = float(sys.argv[4]) if len(sys.argv) > 4 else 200.0
files = [x.strip() for x in list_file.read_text(encoding="utf-8").splitlines()
if x.strip()]
done = set()
if results_file.is_file():
for line in results_file.read_text(encoding="utf-8").splitlines():
if line.strip():
done.add(json.loads(line)["file"])
t0 = time.monotonic()
ran = 0
with open(results_file, "a", encoding="utf-8") as out:
for f in files:
if f in done:
continue
if time.monotonic() - t0 > budget:
print(f"[budget] 预算耗尽,停止(本轮完成 {ran} 个文件)")
break
ft0 = time.monotonic()
rec = {"file": f}
try:
proc = subprocess.run(
[str(PY), "-m", "pytest", f, "-q", "--tb=no", "-rfE",
"-p", "no:cacheprovider"],
cwd=str(REPO), capture_output=True, text=True,
timeout=per_file_timeout, check=False)
tail = proc.stdout + proc.stderr
s = parse_summary(tail)
rec.update(s)
rec["rc"] = proc.returncode
fails = failed_names(tail)
if fails:
rec["failedNames"] = fails
except subprocess.TimeoutExpired:
rec.update({"timeout": True, "passed": 0, "failed": 0,
"errors": 0, "skipped": 0})
rec["sec"] = round(time.monotonic() - ft0, 1)
out.write(json.dumps(rec, ensure_ascii=False) + "\n")
out.flush()
ran += 1
status = ("TIMEOUT" if rec.get("timeout")
else f"passed={rec['passed']} failed={rec['failed']} "
f"errors={rec['errors']}")
print(f"[{status}] {f} ({rec['sec']}s)")
print(f"[done] 本轮 {ran} 个文件,累计 {len(done) + ran}/{len(files)}")
return 0
if __name__ == "__main__":
sys.exit(main())