129 lines
6.4 KiB
Python
129 lines
6.4 KiB
Python
#!/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)})')
|