K1Guitar/tools/k1_harness/cli.py

141 lines
5.6 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
"""k1_harness CLI.
python -m k1_harness selftest
python -m k1_harness run --suite app --transport com --port COM5
python -m k1_harness run --suite app --transport ble
python -m k1_harness run --suite ble
python -m k1_harness run --suite keys,packs,ui --device AT32F403AC
python -m k1_harness run --suite all --profile nightly
"""
from __future__ import annotations
import argparse
import os
import sys
from dataclasses import dataclass
from datetime import datetime
from .paths import default_report_dir
from .report import HarnessReport
from .suites import SUITES, ALL_ORDER
@dataclass
class Ctx:
transport: str = "ble"
port: str = ""
baud: int = 115200
device: str = "AT32F403AC"
allow_poweroff: bool = False
dump_png: str = ""
def _run_selftest() -> int:
report = HarnessReport(title="K1 harness selftest无硬件")
# 1) 协议帧编解码自检(复用现有 selftest 逻辑,零硬件)
import io
import contextlib
import test_protocol_app_sim as appsim
buf = io.StringIO()
with contextlib.redirect_stdout(buf):
code = appsim.selftest()
for line in buf.getvalue().splitlines():
line = line.strip()
if line.startswith("[") and "]" in line:
status = line[1:line.index("]")].strip()
name = line[line.index("]") + 1:].split("|")[0].strip()
report.add("selftest", name, status if status in ("PASS", "FAIL", "SKIP", "SENT") else "FAIL")
report.add("selftest", "app_sim selftest 退出码", "PASS" if code == 0 else "FAIL", f"exit={code}")
# 2) pitch 分析器离线自检:喂一条合法/非法 PITCH 行
from .transports.rtt_session import analyze_lines
good = ("[PITCH] on ch8(bass) 52->40 d=-12 deg=3 chord=8 xp=2 xf=0 fl=0x03 FIX\n"
"[PITCH] on ch1(chord) 64->64 d=0 deg=1 chord=1 xp=0 xf=0 fl=0x00 PASS\n")
res = analyze_lines(good.splitlines())
report.add("selftest", "pitch 分析器(合法样本)", "PASS" if res.fail == 0 and res.ok >= 2 else "FAIL",
f"ok={res.ok} fail={res.fail}")
bad = "[PITCH] on ch8(bass) 52->30 d=-22 deg=3 chord=8 xp=2 xf=0 fl=0x03 FIX\n"
res2 = analyze_lines(bad.splitlines())
report.add("selftest", "pitch 分析器(非法样本检出)", "PASS" if res2.fail >= 1 else "FAIL",
f"fail={res2.fail}")
# 3) 报告落盘自检
out = os.path.join(default_report_dir(), "k1_selftest.md")
report.write_markdown(out)
report.print_summary()
return report.exit_code()
def _run_suites(args) -> int:
names = []
for part in args.suite.split(","):
part = part.strip().lower()
if part == "all":
names.extend(n for n in ALL_ORDER if n not in names)
elif part in SUITES:
if part not in names:
names.append(part)
else:
print(f"未知套件: {part}(可选: {', '.join(SUITES)} / all", file=sys.stderr)
return 2
ctx = Ctx(
transport=args.transport,
port=args.port or "",
baud=args.baud,
device=args.device,
allow_poweroff=args.allow_poweroff or args.profile == "destructive",
dump_png=args.dump_png or "",
)
report = HarnessReport(title="K1 HIL 自动化测试报告")
report.notes.append(f"套件: {', '.join(names)}")
report.notes.append(f"transport={ctx.transport} device={ctx.device} profile={args.profile}")
for name in names:
mod, needs = SUITES[name]
if "transport" in needs and ctx.transport == "com" and not ctx.port:
report.add(name, "前置条件", "SKIP", "transport=com 需 --port已跳过该套件")
continue
print(f"\n===== 套件 {name} =====", flush=True)
try:
mod.run(report, ctx)
except SystemExit as exc:
report.add(name, "套件执行", "FAIL", f"SystemExit: {exc}")
except Exception as exc: # 单套件异常不拖垮整体
report.add(name, "套件执行", "FAIL", f"{type(exc).__name__}: {exc}")
report.print_summary()
ts = report.started.strftime("%Y%m%d_%H%M%S")
out = args.report or os.path.join(default_report_dir(), f"k1_hil_{ts}.md")
report.write_markdown(out)
return report.exit_code()
def main() -> None:
ap = argparse.ArgumentParser(prog="k1_harness", description="K1 吉他统一 HIL 自动化测试")
sub = ap.add_subparsers(dest="cmd", required=True)
sub.add_parser("selftest", help="无硬件自检(帧编解码 + 分析器 + 报告)")
runp = sub.add_parser("run", help="跑测试套件")
runp.add_argument("--suite", required=True,
help="逗号分隔: app,ble,keys,keys1617,packs,ui 或 all")
runp.add_argument("--transport", choices=("com", "ble"), default="ble",
help="app 套件通道(默认 ble")
runp.add_argument("--port", default="", help="COM 端口transport=com 时必填)")
runp.add_argument("--baud", type=int, default=115200)
runp.add_argument("--device", default="AT32F403AC", help="J-Link 器件名")
runp.add_argument("--profile", choices=("smoke", "nightly", "destructive"), default="nightly")
runp.add_argument("--allow-poweroff", action="store_true", help="允许 05 00 关机用例")
runp.add_argument("--dump-png", default="", help="ui 套件截屏输出路径(需 DEBUG_LCD_DUMP 固件)")
runp.add_argument("--report", default="", help="Markdown 报告输出路径")
args = ap.parse_args()
if args.cmd == "selftest":
raise SystemExit(_run_selftest())
raise SystemExit(_run_suites(args))