K1Guitar/tools/k1_harness/cli.py

141 lines
5.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- 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))