chore(test): 初始化回归测试仓(新增 .gitignore,TestsOverview.md 改为 README.md)
把 Test/ 纳入 https://git.gvsds.com/GVSDS/Viper_RegressionTesting 。 1) 新增 .gitignore —— 只保留测试源码,剔除全部构建产物: 目录 **/output*/(output / output_opt / output_orig / output_verify / output_dbg)、 **/temp*/(temp / temp_opt)、**/.tpv_build/ 二进制 *.exe *.obj *.o *.out *.a *.lib *.dll 中间物 *.ll *.vpi *.pyi *.deps.txt *.deps.json *.doc.json *_sha1_map.txt _pyi_cache_v1.pkl _pyi_cache_v1.sig.json _shared_sym.json _phase1_manifest.json 其它 build_all.txt(构建日志)、*.log、__pycache__/、*.bak、IDE 目录 保留 App/*.vp、project.json / project_opt.json / project.vpj、README.md、两个 .py 实测 `git add -A --dry-run`:196 个文件,无任何构建产物混入。 2) TestsOverview.md → README.md(作为仓库首页文档)。 3) SequentialRegressionTest.py:7 处 TestsOverview.md 引用同步改为 README.md (文档串、OVERVIEW 路径、read_test_order 文档与报错文案、argparse description、 启动横幅);同时修正入口路径 PROJECTRANS —— 旧值 <root>/Projectrans.py 已不存在, 改为 <root>/TransPyC/Projectrans.py(与 regression_test.py:45-48 记录一致), 否则本脚本会以 "can't open file" 全量失败。 regression_test.py 无需改动:它按目录扫描 project.json 找项目,不引用该文档。
This commit is contained in:
commit
3c6fb1d246
196 files changed
+24775
No files matched your search
@@ -0,0 +1,290 @@
|
||||
#!/usr/bin/env python3
|
||||
"""TransPyC 顺序回归测试脚本
|
||||
|
||||
与 regression_test.py 的区别:
|
||||
1. 按 Test/README.md「建议实现顺序」节(难度升序,68 个项目)逐个编译
|
||||
2. 每个项目编译成功后立即运行其可执行文件
|
||||
3. 编译或运行失败立刻结束(fail-fast),退出码非 0
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import re
|
||||
import subprocess
|
||||
import argparse
|
||||
|
||||
# 强制 stdout/stderr 使用 UTF-8 编码,避免 Windows GBK 编码在打印含替换字符的输出时崩溃
|
||||
try:
|
||||
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||||
sys.stderr.reconfigure(encoding='utf-8', errors='replace')
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ============================================================
|
||||
# 路径
|
||||
# ============================================================
|
||||
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
# 项目根目录(含 TransPyC/、TransPyV/、Test/ 等同级目录)
|
||||
PROJECT_ROOT = os.path.dirname(TEST_DIR)
|
||||
# Projectrans.py 路径:编译器入口在 <root>/TransPyC/Projectrans.py
|
||||
# (旧值 <root>/Projectrans.py 已不存在,与 regression_test.py:45-48 同一条记录)
|
||||
PROJECTRANS = os.path.join(PROJECT_ROOT, "TransPyC", "Projectrans.py")
|
||||
# README.md 路径(顺序来源,原 TestsOverview.md)
|
||||
OVERVIEW = os.path.join(TEST_DIR, "README.md")
|
||||
|
||||
# 编译超时(秒)
|
||||
COMPILE_TIMEOUT = 300
|
||||
# 运行超时(秒)
|
||||
RUN_TIMEOUT = 30
|
||||
|
||||
|
||||
def read_test_order():
|
||||
"""从 README.md「建议实现顺序」节提取项目名(难度升序)"""
|
||||
with open(OVERVIEW, encoding='utf-8') as f:
|
||||
text = f.read()
|
||||
m = re.search(r'##\s*二、\s*建议实现顺序(.*?)##\s*三、', text, re.S)
|
||||
if not m:
|
||||
raise SystemExit("README.md 中找不到「建议实现顺序」节")
|
||||
section = m.group(1)
|
||||
order = []
|
||||
for line in section.splitlines():
|
||||
mm = re.match(r'\s*\d+\.\s*\*\*(.+?)\*\*', line)
|
||||
if mm:
|
||||
order.append(mm.group(1))
|
||||
if not order:
|
||||
raise SystemExit("「建议实现顺序」节中未解析到任何项目名")
|
||||
return order
|
||||
|
||||
|
||||
def build_compile_cmd(args):
|
||||
"""根据 --exe/--compiler 构建编译命令模板({project} 为项目 json 路径占位)
|
||||
|
||||
--exe 指定编译器可执行文件(如 TransPyV.exe),优先于 --compiler;
|
||||
两者都未指定时使用 TPC(python Projectrans.py)。
|
||||
--rebuild-includes 时追加 --rebuild-includes(全量重编 includes,
|
||||
修复 includes.binary 缓存不一致导致的 undefined symbol)。
|
||||
"""
|
||||
suffix = " --rebuild-includes" if args.rebuild_includes else ""
|
||||
if args.exe:
|
||||
return f'"{args.exe}" --project {{project}} --clean{suffix}'
|
||||
if args.compiler:
|
||||
return args.compiler
|
||||
return f'D:\\Python312\\python.exe -u "{PROJECTRANS}" --project {{project}} --clean{suffix}'
|
||||
|
||||
|
||||
def compile_project(cmd, proj_dir):
|
||||
"""编译单个测试项目,返回 (success, elapsed, combined_output)"""
|
||||
start = time.time()
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
errors='replace',
|
||||
cwd=proj_dir,
|
||||
timeout=COMPILE_TIMEOUT,
|
||||
)
|
||||
elapsed = time.time() - start
|
||||
|
||||
combined = (result.stdout or "") + (result.stderr or "")
|
||||
return (result.returncode == 0, elapsed, combined)
|
||||
|
||||
|
||||
def find_executables(output_dir):
|
||||
"""在 output 目录中查找可执行文件"""
|
||||
exes = []
|
||||
if not os.path.isdir(output_dir):
|
||||
return exes
|
||||
for f in os.listdir(output_dir):
|
||||
fpath = os.path.join(output_dir, f)
|
||||
if os.path.isfile(fpath) and (f.endswith('.exe') or f.endswith('.bin')):
|
||||
exes.append(fpath)
|
||||
return exes
|
||||
|
||||
|
||||
def find_output_dirs(proj_dir, proj_config):
|
||||
"""收集候选输出目录(兼容 TPC 与 TPV 布局)
|
||||
|
||||
- project.json 显式 output_dir(TPC 常用)
|
||||
- project.json build_dir + '/output'(TPV 布局:.tpv_build/output)
|
||||
- 兜底 ./output
|
||||
保序去重返回。
|
||||
"""
|
||||
dirs = []
|
||||
od = proj_config.get('output_dir')
|
||||
if od:
|
||||
dirs.append(os.path.abspath(os.path.join(proj_dir, od)))
|
||||
bd = proj_config.get('build_dir')
|
||||
if bd:
|
||||
dirs.append(os.path.abspath(os.path.join(proj_dir, bd, 'output')))
|
||||
if not dirs:
|
||||
dirs.append(os.path.abspath(os.path.join(proj_dir, './output')))
|
||||
seen = set()
|
||||
uniq = []
|
||||
for d in dirs:
|
||||
if d not in seen:
|
||||
seen.add(d)
|
||||
uniq.append(d)
|
||||
return uniq
|
||||
|
||||
|
||||
def run_executable(exe_path):
|
||||
"""运行单个可执行文件,返回 (returncode, stdout, stderr, timed_out)
|
||||
|
||||
超时 RUN_TIMEOUT 秒强制结束。
|
||||
使用 CREATE_NO_WINDOW 避免弹出 cmd 窗口,通过管道捕获 stdout/stderr。
|
||||
"""
|
||||
try:
|
||||
CREATE_NO_WINDOW = 0x08000000
|
||||
result = subprocess.run(
|
||||
[exe_path],
|
||||
cwd=os.path.dirname(exe_path),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding='utf-8',
|
||||
errors='replace',
|
||||
timeout=RUN_TIMEOUT,
|
||||
creationflags=CREATE_NO_WINDOW,
|
||||
)
|
||||
return (result.returncode, result.stdout or "", result.stderr or "", False)
|
||||
except subprocess.TimeoutExpired:
|
||||
return (-1, "", "超时(强制终止)", True)
|
||||
except Exception as e:
|
||||
return (-1, "", str(e), False)
|
||||
|
||||
|
||||
def print_tail(output, max_lines=40):
|
||||
"""打印输出尾部(编译/运行失败时的诊断信息)"""
|
||||
lines = output.strip().splitlines()
|
||||
if len(lines) > max_lines:
|
||||
print(f" ... (省略前 {len(lines) - max_lines} 行) ...")
|
||||
lines = lines[-max_lines:]
|
||||
for line in lines:
|
||||
print(f" {line}")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="TransPyC 顺序回归测试(按 README.md 顺序,失败即停)")
|
||||
parser.add_argument("--exe", default=None,
|
||||
help="编译器可执行文件(如 TransPyV.exe),用它编译测试项目(替代 TPC)")
|
||||
parser.add_argument("--compiler", default=None,
|
||||
help="自定义编译命令模板,用 {project} 表示项目 json 路径;默认 TPC python")
|
||||
parser.add_argument("--rebuild-includes", action="store_true",
|
||||
help="每次编译追加 --rebuild-includes:清空 includes.binary 全量重编(修复缓存不一致)")
|
||||
parser.add_argument("--number", type=int, default=1,
|
||||
help="从第几个案例开始(1 开始编号),编译该编号及之后的项目;默认 1")
|
||||
args = parser.parse_args()
|
||||
compile_cmd = build_compile_cmd(args)
|
||||
|
||||
print("=" * 70)
|
||||
print("TransPyC 顺序回归测试(按 README.md 顺序,失败即停)")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
order = read_test_order()
|
||||
total = len(order)
|
||||
|
||||
start = max(1, args.number)
|
||||
if start > total:
|
||||
print(f"错误: --number {args.number} 超出总项目数 {total}")
|
||||
return 1
|
||||
print(f"按顺序共 {total} 个项目,从第 {start} 个({order[start - 1]})开始")
|
||||
print(f"编译命令: {compile_cmd}\n")
|
||||
|
||||
passed = 0
|
||||
for idx, name in enumerate(order, 1):
|
||||
if idx < start:
|
||||
continue
|
||||
proj_dir = os.path.join(TEST_DIR, name)
|
||||
proj_json = os.path.join(proj_dir, "project.json")
|
||||
|
||||
if not os.path.isfile(proj_json):
|
||||
print(f"[{idx}/{total}] {name}: 缺少 project.json,跳过")
|
||||
continue
|
||||
|
||||
# ---------- 编译 ----------
|
||||
print(f"[{idx}/{total}] 编译 {name} ...", end=" ", flush=True)
|
||||
cmd = compile_cmd.format(project=proj_json)
|
||||
try:
|
||||
compile_ok, elapsed, output = compile_project(cmd, proj_dir)
|
||||
except subprocess.TimeoutExpired:
|
||||
print("编译超时(5 分钟)")
|
||||
print_tail("编译超时(5 分钟)")
|
||||
print(f"\n在 {name} 编译超时,立即终止。")
|
||||
return 1
|
||||
except Exception as e:
|
||||
print(f"异常: {e}")
|
||||
print_tail(str(e))
|
||||
print(f"\n在 {name} 编译异常,立即终止。")
|
||||
return 1
|
||||
|
||||
if not compile_ok:
|
||||
print(f"失败 ({elapsed:.1f}s)")
|
||||
print_tail(output)
|
||||
print(f"\n在 {name} 编译失败,立即终止。")
|
||||
return 1
|
||||
print(f"通过 ({elapsed:.1f}s)")
|
||||
|
||||
# ---------- 编译成功后立即运行 ----------
|
||||
# project.json 解析失败必须**立刻终止并报错**:静默退化成 {} 会让下面拿不到
|
||||
# build_dir(TPV 布局 .tpv_build/output),只在 <项目>/output 里找可执行文件,
|
||||
# 于是明明编译成功却被判成"未找到可执行文件"。实测踩过:LLvmLiteTest 的
|
||||
# project.json 有一个尾逗号(第 18 行 `],`)——标准 JSON 非法(TPV 自带解析器
|
||||
# 宽容才编得过),此处不再吞异常。
|
||||
try:
|
||||
with open(proj_json, 'r', encoding='utf-8') as f:
|
||||
proj_config = json.load(f)
|
||||
except Exception as e:
|
||||
print(f" [运行] project.json 解析失败: {e}")
|
||||
print(f"\n在 {name} 读取 project.json 失败,立即终止(请修正该文件后重跑)。")
|
||||
return 1
|
||||
|
||||
output_dirs = find_output_dirs(proj_dir, proj_config)
|
||||
exes = []
|
||||
for od in output_dirs:
|
||||
exes = find_executables(od)
|
||||
if exes:
|
||||
break
|
||||
|
||||
if not exes:
|
||||
print(f" [运行] 未找到可执行文件(候选: {'; '.join(output_dirs)}),视为运行失败")
|
||||
return 1
|
||||
|
||||
run_ok = True
|
||||
for exe_path in exes:
|
||||
exe_name = os.path.basename(exe_path)
|
||||
print(f" [运行] {exe_name} ...", end=" ", flush=True)
|
||||
returncode, stdout, stderr, timed_out = run_executable(exe_path)
|
||||
|
||||
if timed_out:
|
||||
print("超时")
|
||||
print_tail(stdout + stderr)
|
||||
run_ok = False
|
||||
elif returncode == 0:
|
||||
print("通过")
|
||||
else:
|
||||
print(f"失败(退出码 {returncode})")
|
||||
print_tail(stdout + stderr)
|
||||
run_ok = False
|
||||
|
||||
if not run_ok:
|
||||
break
|
||||
|
||||
if not run_ok:
|
||||
print(f"\n在 {name} 运行失败,立即终止。")
|
||||
return 1
|
||||
|
||||
passed += 1
|
||||
|
||||
print()
|
||||
print("=" * 70)
|
||||
print(f"全部 {passed} 个项目编译并运行通过!")
|
||||
print("=" * 70)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user