SiteForge V8.21 回归验收
'''+info['status']+'
每轮 '+str(info['tests_per_round'])+' 项自动化测试 · '+str(args.repeat)+' 轮 · 无外部环境实测保证
| 批次 | 结果 | 通过 | 秒数 |
|---|
请参阅 JSON、Markdown 和原始测试日志。真实客户站点迁移仍需专项验收。
#!/usr/bin/env python3 """Deterministic, subprocess-isolated pytest regression for V8.21. All outcomes and raw stdout are preserved. A timed-out or failing batch is a hard failure, not silently retried into a green result. """ from __future__ import annotations import argparse import datetime import html import json import os import re import signal import subprocess import sys import time from pathlib import Path ROOT=Path(__file__).resolve().parents[1] def launch(modules,seconds): started=time.monotonic() try: process=subprocess.run([sys.executable,'-m','pytest','-q',*modules], cwd=ROOT,stdout=subprocess.PIPE,stderr=subprocess.STDOUT, text=True,timeout=seconds) output=process.stdout code=process.returncode status='PASS' if code==0 else 'FAIL' except subprocess.TimeoutExpired as exc: output=(exc.stdout or b'') if isinstance(output,bytes):output=output.decode('utf-8','replace') output+='\nTIMEOUT: pytest process terminated after deadline. Check Worker child processes.\n' code=None;status='TIMEOUT' m=re.search(r'(\d+) passed(?:\s|,)',output) return {'status':status,'passed':int(m.group(1)) if status=='PASS' and m else 0, 'duration_seconds':round(time.monotonic()-started,2),'exit_code':code,'output':output} def main(argv=None): p=argparse.ArgumentParser(description='SiteForge V8.21 independent pytest batch gate') p.add_argument('--batch-size',type=int,default=4) p.add_argument('--repeat',type=int,default=1) p.add_argument('--timeout',type=int,default=80) p.add_argument('--report',type=Path,default=Path('reports/v821-regression')) args=p.parse_args(argv) if not 1<=args.batch_size<=10 or not 1<=args.repeat<=5 or not 10<=args.timeout<=600:p.error('invalid bounds') modules=sorted('tests/'+f.name for f in (ROOT/'tests').glob('test_*.py')) if not modules:p.error('no pytest tests found') report=args.report if args.report.is_absolute() else ROOT/args.report logs=report.parent/(report.name+'_logs') logs.mkdir(parents=True,exist_ok=True) results=[] for round_num in range(1,args.repeat+1): for index in range(0,len(modules),args.batch_size): group=modules[index:index+args.batch_size] name=f'round{round_num}-batch{index//args.batch_size+1}' print('RUN',name,flush=True) result=launch(group,args.timeout) log=logs/(name+'.log') log.write_text('$ pytest -q '+' '.join(group)+'\n'+result.pop('output'),encoding='utf-8') result.update({'name':name,'modules':group,'log':str(log.relative_to(ROOT))}) results.append(result) print(result['status'],result['passed'],'passed',result['duration_seconds'],'sec',flush=True) if result['status']!='PASS':break if results[-1]['status']!='PASS':break success=len(results)==args.repeat*((len(modules)+args.batch_size-1)//args.batch_size) and all(x['status']=='PASS' for x in results) info={'version':'8.21.0-dev.1','generated_at':datetime.datetime.now(datetime.timezone.utc).isoformat(), 'status':'PASS' if success else 'FAIL','test_modules':len(modules),'repeats':args.repeat, 'tests_per_round':sum(x['passed'] for x in results if x['name'].startswith('round1-')), 'total_passes':sum(x['passed'] for x in results),'batches':results, 'environment_note':'pytest local fixtures only; Docker/PG/Redis/browser/real WordPress not inferred as passed.'} report.parent.mkdir(parents=True,exist_ok=True) report.with_suffix('.json').write_text(json.dumps(info,ensure_ascii=False,indent=2),encoding='utf-8') header='\n'.join(['# SiteForge V8.21 完整自动化回归','',f"**{info['status']}** — {info['test_modules']} 模块 / {info['tests_per_round']} 项每轮 / {args.repeat} 轮",'', '| 批次 | 结果 | 通过用例 | 耗时 | 日志 |','|---|---|---:|---:|---|']) table='\n'.join(f"| {r['name']} | {r['status']} | {r['passed']} | {r['duration_seconds']} 秒 | {r['log']} |" for r in results) report.with_suffix('.md').write_text(header+'\n'+table+'\n\n真实部署与外部服务另行验收。\n',encoding='utf-8') trs=''.join(f'
每轮 '+str(info['tests_per_round'])+' 项自动化测试 · '+str(args.repeat)+' 轮 · 无外部环境实测保证
| 批次 | 结果 | 通过 | 秒数 |
|---|
请参阅 JSON、Markdown 和原始测试日志。真实客户站点迁移仍需专项验收。