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

94 lines
3.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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())