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