#!/usr/bin/env python3 """ P0 测试报告整理输出 —— 最新计数口径(2026-06-23 与用户确认)。 用法: python3 scripts/gen-p0-report.py [日志路径] [输出md路径] 默认: reports/p0-all.log -> reports/P0_Android_真实结果.md 计数规则(口径): 1) 只取**最后一轮**(日志中最后一个 "P0 全量回归开始")到结尾的片段。 2) 添加用例(名含"添加"):按**用例最终结果**计 —— 重试后最终 PASS 记通过,并标注"第N次成功"; 全程未 PASS 才记真失败。(添加用例以最后结果为准,重试成功不计失败) 3) 控制/其他用例:**BLE 与 WiFi 各算一条,不去重**(含 vitest 重试占位中间态),按执行条数计。 4) 失败按类分组:灯/卡片控制 / 设备控制(其他) / 场景·自动化 / 平台·账号。 本脚本只生成**数据底稿**(reports/P0_Android_真实结果.md)。最终交付报告请按 docs/测试报告_P0过夜回归_*.md 的格式人工汇总到 docs/测试报告_P0回归_YYYY-MM-DD.md (总览/添加/控制/灯类/其他/已修复/结论与待办)。提示词见 memory: reference-p0-report-counting。 """ import re, sys from collections import defaultdict, Counter, OrderedDict LOG = sys.argv[1] if len(sys.argv) > 1 else 'reports/p0-all.log' OUT = sys.argv[2] if len(sys.argv) > 2 else 'reports/P0_Android_真实结果.md' lines = open(LOG, encoding='utf-8', errors='ignore').read().splitlines() starts = [i for i, l in enumerate(lines) if 'P0 全量回归开始' in l] start = max(starts) if starts else 0 seg = lines[start:] hdr = lines[start].strip() if starts else '(未找到回归开始标记,取全量)' rows = [] # (status, name, phase) 按出现顺序;phase: None=添加/setup, 'ble'/'wifi'=控制双轮, 'tail'=平台账号 phase = None for l in seg: if '控制 PROTO=ble' in l: phase = 'ble' elif '控制 PROTO=wifi' in l: phase = 'wifi' elif '平台 + 账号阶段' in l: phase = 'tail' m = re.search(r'[✓✗○] \[(PASS|FAIL|SKIP)\]\s*(.+?)\s*\(([\d.]+)s\)', l) if m: rows.append((m.group(1), re.sub(r'\s+', ' ', m.group(2)).strip(), phase)) # --- 添加用例:按用例最终结果归并 --- add_seq = OrderedDict() for s, n, ph in rows: if '添加' in n: add_seq.setdefault(n, []).append(s) add_first, add_retry, add_fail = [], [], [] for n, sts in add_seq.items(): if 'PASS' in sts: fb = 0 for s in sts: if s == 'PASS': break fb += 1 (add_retry if fb > 0 else add_first).append((n, fb)) else: add_fail.append(n) # --- 非添加:按执行计数(不去重) --- def cat(n): if '场景' in n or '自动化' in n: return '场景/自动化' if any(k in n for k in ['登录','登出','注册','注销','忘记','第三方','feedback','插件','房间','消息']): return '平台/账号' if '首页卡片' in n or '首页找到' in n: return '灯/卡片控制' return '设备控制(其他)' nonadd = [(s, n) for s, n, ph in rows if '添加' not in n] fg = defaultdict(Counter) for s, n in nonadd: if s == 'FAIL': fg[cat(n)][n] += 1 na_pass = sum(1 for s, n in nonadd if s == 'PASS') na_fail = sum(1 for s, n in nonadd if s == 'FAIL') na_skip = sum(1 for s, n in nonadd if s == 'SKIP') # --- 控制 BLE / WiFi 按阶段拆(按用例最终结果去重:每阶段同名用例取最后一次结果,得真实成功率,不受重试占位干扰)--- def phase_final(target): seq = OrderedDict() for s, n, ph in rows: if ph == target and '添加' not in n: seq.setdefault(n, []).append(s) p = f = sk = 0 for n, sts in seq.items(): last = sts[-1] if last == 'PASS': p += 1 elif last == 'FAIL': f += 1 else: sk += 1 return p, f, sk ble_p, ble_f, ble_sk = phase_final('ble') wifi_p, wifi_f, wifi_sk = phase_final('wifi') def rate(p, f): return f'{100 * p / (p + f):.1f}%' if (p + f) else 'N/A' o = [] o.append('# Android P0 真实结果\n') o.append(f'> 来源:{hdr}(最后一轮)。\n') o.append('> **计数口径**:① 添加用例按"用例最终结果"计,**重试后成功记通过**(标注第几次成功)、全程未过才记失败;' '② 控制/其他用例 **BLE 与 WiFi 各算一条,不去重**(含 vitest 重试占位中间态)。\n\n') add_total, add_ok = len(add_seq), len(add_first) + len(add_retry) o.append('## 汇总\n') o.append(f'- **添加(按用例):{add_total} 个 → ✅通过 {add_ok}(其中重试后成功 {len(add_retry)})· ❌失败 {len(add_fail)}**\n') o.append(f'- **控制/其他(按执行):✅PASS {na_pass} · ❌FAIL {na_fail} · ⏭SKIP {na_skip}**\n\n') # BLE / WiFi 控制成功率(仅 Android 双轮有效;iOS SKIP_PROTO_NET 单轮则两行多为 0/N·A) if (ble_p + ble_f + ble_sk + wifi_p + wifi_f + wifi_sk) > 0: o.append('### BLE / WiFi 控制成功率(按用例最终结果,每阶段去重)\n') o.append('> 区分依据:日志 `控制 PROTO=ble` / `控制 PROTO=wifi` 阶段标记;同名用例取该阶段最后一次结果,成功率=通过/(通过+失败)。\n\n') o.append('| 协议 | 通过 | 失败 | 跳过 | 成功率 |\n|---|---|---|---|---|\n') o.append(f'| BLE | {ble_p} | {ble_f} | {ble_sk} | {rate(ble_p, ble_f)} |\n') o.append(f'| WiFi | {wifi_p} | {wifi_f} | {wifi_sk} | {rate(wifi_p, wifi_f)} |\n\n') o.append(f'## 添加用例({add_total})\n') o.append(f'\n### 🔁 重试后成功({len(add_retry)})— 计通过,稳定性需关注\n') for n, f in sorted(add_retry, key=lambda x: -x[1]): o.append(f'- {n} — **第 {f+1} 次成功**(前 {f} 次失败)\n') o.append(f'\n### ❌ 真失败({len(add_fail)})\n') for n in add_fail: o.append(f'- {n}(全程未成功)\n') o.append(f'\n### ✅ 首次即通过({len(add_first)})\n') for n, _ in add_first: o.append(f'- {n}\n') o.append(f'\n## 控制/其他 失败明细(FAIL {na_fail} 条;`×N`=含重试占位)\n') for c in sorted(fg, key=lambda k: -sum(fg[k].values())): sub = fg[c] o.append(f'\n### {c}(失败 {sum(sub.values())} 条)\n') for n, cnt in sub.most_common(): o.append(f'- {n}' + (f' ×{cnt}\n' if cnt > 1 else '\n')) open(OUT, 'w', encoding='utf-8').write(''.join(o)) print(f'已生成 {OUT}') print(f'添加 {add_total}: 首通{len(add_first)} 重试成功{len(add_retry)} 真失败{len(add_fail)} | ' f'非添加 PASS{na_pass} FAIL{na_fail} SKIP{na_skip} | ' f'BLE {ble_p}/{ble_p+ble_f}({rate(ble_p,ble_f)}) WiFi {wifi_p}/{wifi_p+wifi_f}({rate(wifi_p,wifi_f)})')