141 lines
5.6 KiB
Python
141 lines
5.6 KiB
Python
# -*- 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))
|