191 lines
7.1 KiB
Python
191 lines
7.1 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
三态控制成功率对比报告。
|
|
用法:
|
|
python3 scripts/gen-3state-report.py <输出md> \
|
|
--android-both reports/ctrl3-android-both.log \
|
|
--android-split reports/ctrl3-android-split.log \
|
|
--ios-both reports/ctrl3-ios-both.log \
|
|
--ios-split reports/ctrl3-ios-split.log
|
|
|
|
状态映射(与 run-ctrl-3states.sh / 老脚本一致):
|
|
both 日志 → 状态「BLE+WiFi」:计 `控制:单轮` 标记之后的控制用例。
|
|
split 日志 → 状态「仅BLE」(PROTO=ble 段)+「仅WiFi」(PROTO=wifi 段)。
|
|
|
|
口径(对齐 gen-p0-report.py):同一段内同名用例取**最后一次结果**去重(消除 vitest 重试占位),
|
|
成功率 = 通过 / (通过 + 失败),分母**剔除 SKIP**(SKIP 不算有效样本)。
|
|
"""
|
|
import re, sys
|
|
|
|
RESULT_RE = re.compile(r'[✓✗○] \[(PASS|FAIL|SKIP)\]\s*(.+?)\s*\(([\d.]+)s\)')
|
|
|
|
def parse_phase(path, phase):
|
|
"""phase ∈ {'both','ble','wifi'};返回 (通过, 失败, 跳过, 明细dict{name:last_status})。"""
|
|
if not path:
|
|
return None
|
|
try:
|
|
lines = open(path, encoding='utf-8', errors='ignore').read().splitlines()
|
|
except FileNotFoundError:
|
|
return None
|
|
cur = None # 当前所处段
|
|
seq = {} # name -> [status...] (保序)
|
|
order = []
|
|
for l in lines:
|
|
if '控制:单轮' in l:
|
|
cur = 'both'
|
|
elif '控制 PROTO=ble' in l:
|
|
cur = 'ble'
|
|
elif '控制 PROTO=wifi' in l:
|
|
cur = 'wifi'
|
|
elif '平台 + 账号阶段' in l:
|
|
cur = 'tail'
|
|
m = RESULT_RE.search(l)
|
|
if m and cur == phase:
|
|
name = re.sub(r'\s+', ' ', m.group(2)).strip()
|
|
if '添加' in name: # 控制态里理论上没有添加;保险起见剔除
|
|
continue
|
|
if name not in seq:
|
|
seq[name] = []
|
|
order.append(name)
|
|
seq[name].append(m.group(1))
|
|
p = f = sk = 0
|
|
detail = {}
|
|
for name in order:
|
|
last = seq[name][-1]
|
|
detail[name] = last
|
|
if last == 'PASS': p += 1
|
|
elif last == 'FAIL': f += 1
|
|
else: sk += 1
|
|
return (p, f, sk, detail)
|
|
|
|
|
|
def rate(p, f):
|
|
return f'{100 * p / (p + f):.1f}%' if (p + f) else 'N/A'
|
|
|
|
|
|
def main():
|
|
out = sys.argv[1]
|
|
argmap = {}
|
|
args = sys.argv[2:]
|
|
i = 0
|
|
while i < len(args):
|
|
if args[i].startswith('--'):
|
|
argmap[args[i][2:]] = args[i + 1]
|
|
i += 2
|
|
else:
|
|
i += 1
|
|
|
|
# (平台, 状态标签, 日志key, 段)
|
|
plan = [
|
|
('Android', 'BLE+WiFi', 'android-both', 'both'),
|
|
('Android', '仅BLE', 'android-split', 'ble'),
|
|
('Android', '仅WiFi', 'android-split', 'wifi'),
|
|
('iOS', 'BLE+WiFi', 'ios-both', 'both'),
|
|
('iOS', '仅BLE', 'ios-split', 'ble'),
|
|
('iOS', '仅WiFi', 'ios-split', 'wifi'),
|
|
]
|
|
|
|
results = {} # (平台,状态) -> (p,f,sk,detail) or None
|
|
for plat, state, key, phase in plan:
|
|
results[(plat, state)] = parse_phase(argmap.get(key), phase)
|
|
|
|
o = []
|
|
o.append('# 三态控制成功率对比(BLE+WiFi / 仅BLE / 仅WiFi)\n\n')
|
|
o.append('> 口径:同一状态内同名用例取最后一次结果去重(消除 vitest 重试占位);'
|
|
'**成功率 = 通过 /(通过+失败),分母剔除 SKIP**。\n')
|
|
o.append('> 状态来源:both 日志=BLE+WiFi(两 radio 都开);'
|
|
'split 日志=仅BLE(关WiFi/开蓝牙)+ 仅WiFi(开WiFi/关蓝牙)。\n\n')
|
|
|
|
# ---- 主表:每平台每状态一行 ----
|
|
o.append('## 汇总对比\n\n')
|
|
o.append('| 平台 | 状态 | 通过 | 失败 | 跳过 | 成功率(剔除SKIP) |\n')
|
|
o.append('|---|---|---|---|---|---|\n')
|
|
for plat, state in [(p, s) for p in ('Android', 'iOS') for s in ('BLE+WiFi', '仅BLE', '仅WiFi')]:
|
|
r = results[(plat, state)]
|
|
if r is None:
|
|
o.append(f'| {plat} | {state} | — | — | — | 无数据/未跑 |\n')
|
|
else:
|
|
p, f, sk, _ = r
|
|
o.append(f'| {plat} | {state} | {p} | {f} | {sk} | {rate(p, f)} |\n')
|
|
o.append('\n')
|
|
|
|
# ---- 每平台三态横向对比 ----
|
|
for plat in ('Android', 'iOS'):
|
|
o.append(f'## {plat} 三态横向对比\n\n')
|
|
o.append('| 状态 | 有效样本(通过+失败) | 成功率 |\n|---|---|---|\n')
|
|
for state in ('BLE+WiFi', '仅BLE', '仅WiFi'):
|
|
r = results[(plat, state)]
|
|
if r is None:
|
|
o.append(f'| {state} | — | 无数据 |\n')
|
|
else:
|
|
p, f, sk, _ = r
|
|
o.append(f'| {state} | {p + f} | {rate(p, f)} |\n')
|
|
o.append('\n')
|
|
|
|
# ---- 各状态失败明细 + 跨态差异(哪些用例只在某态失败)----
|
|
o.append('## 失败明细(按平台/状态)\n\n')
|
|
for plat in ('Android', 'iOS'):
|
|
for state in ('BLE+WiFi', '仅BLE', '仅WiFi'):
|
|
r = results[(plat, state)]
|
|
if r is None:
|
|
continue
|
|
p, f, sk, detail = r
|
|
fails = [n for n, s in detail.items() if s == 'FAIL']
|
|
skips = [n for n, s in detail.items() if s == 'SKIP']
|
|
o.append(f'### {plat} · {state}(FAIL {len(fails)} · SKIP {len(skips)})\n')
|
|
if fails:
|
|
for n in fails:
|
|
o.append(f'- ❌ {n}\n')
|
|
else:
|
|
o.append('- (无失败)\n')
|
|
o.append('\n')
|
|
|
|
# ---- 跨态差异分析:同平台内,某用例在不同态结果不一致的挑出来 ----
|
|
o.append('## 跨态差异(同平台同用例在不同连接态结果不一致)\n\n')
|
|
for plat in ('Android', 'iOS'):
|
|
states = ['BLE+WiFi', '仅BLE', '仅WiFi']
|
|
details = {}
|
|
any_data = False
|
|
for st in states:
|
|
r = results[(plat, st)]
|
|
if r is not None:
|
|
details[st] = r[3]
|
|
any_data = True
|
|
if not any_data:
|
|
continue
|
|
# 收集所有用例名
|
|
allnames = set()
|
|
for d in details.values():
|
|
allnames.update(d.keys())
|
|
diffs = []
|
|
for n in sorted(allnames):
|
|
row = [details.get(st, {}).get(n, '·') for st in states]
|
|
if len({x for x in row if x != '·'}) > 1: # 出现过 ≥2 种不同结果
|
|
diffs.append((n, row))
|
|
o.append(f'### {plat}\n')
|
|
if diffs:
|
|
o.append('| 用例 | BLE+WiFi | 仅BLE | 仅WiFi |\n|---|---|---|---|\n')
|
|
for n, row in diffs:
|
|
o.append(f'| {n} | {row[0]} | {row[1]} | {row[2]} |\n')
|
|
else:
|
|
o.append('- (无差异,或数据不足以对比)\n')
|
|
o.append('\n')
|
|
|
|
open(out, 'w', encoding='utf-8').write(''.join(o))
|
|
print(f'已生成 {out}')
|
|
# 控制台速览
|
|
for plat in ('Android', 'iOS'):
|
|
parts = []
|
|
for state in ('BLE+WiFi', '仅BLE', '仅WiFi'):
|
|
r = results[(plat, state)]
|
|
if r is None:
|
|
parts.append(f'{state}:无数据')
|
|
else:
|
|
p, f, sk, _ = r
|
|
parts.append(f'{state}:{p}/{p+f}({rate(p, f)})')
|
|
print(f'{plat} ' + ' '.join(parts))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|