mirror of
https://github.com/hathach/tinyusb.git
synced 2026-08-18 02:53:35 +00:00
test/hil: add board-pool health check, split hil_test into focused modules (#3794) Add test/hil/hil_pool_check.py: per-board rig health scan — probe presence, light-example flash (dfu_runtime; device_info + serial check for host-only boards), uid re-enumeration, safe recovery (probe authorized-toggle, board reset), verified board_test re-park, USB topology report, and a markdown summary table. Missing firmware is built on the spot (tools/build.py, idf.py for espressif, one get_deps retry); row statuses: ok, flash-failed, failed, locked. Board locks are always respected, never bypassed. Refactor hil_test.py into hil_lock.py (flock protocol, controller permits, hold/release/status CLI; replaces board_lock.py) and hil_flash.py (flashers, find_firmware, run_cmd). Update WCH probe uids and the board roster in tinyusb.json; add the hil-pool-check skill.
143 lines
5.2 KiB
Bash
143 lines
5.2 KiB
Bash
#!/usr/bin/env bash
|
|
# Run HIL test remotely on ci.lan
|
|
# Usage: test/hil/hil_ci.sh [-b BOARD] [-t TEST] [extra hil_test.py args...]
|
|
# Example:
|
|
# test/hil/hil_ci.sh -b stm32f723disco
|
|
# test/hil/hil_ci.sh -b stm32f723disco -t host/cdc_msc_hid -r 1
|
|
#
|
|
# Env overrides: REMOTE, REMOTE_DIR, CONFIG (path to HIL config json),
|
|
# ROOT_DIR (tinyusb checkout to test; defaults to the script's own checkout).
|
|
|
|
set -euo pipefail
|
|
|
|
REMOTE=${REMOTE:-ci.lan}
|
|
REMOTE_DIR=${REMOTE_DIR:-/tmp/tinyusb-hil}
|
|
ROOT_DIR=${ROOT_DIR:-$(cd "$(dirname "$0")/../.." && pwd)}
|
|
CONFIG=${CONFIG:-$ROOT_DIR/test/hil/tinyusb.json}
|
|
|
|
[[ -f "$ROOT_DIR/test/hil/hil_test.py" && -d "$ROOT_DIR/examples" ]] || {
|
|
echo "error: $ROOT_DIR does not look like a tinyusb checkout" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Parse -b BOARD from arguments to know which build to copy
|
|
BOARD=""
|
|
ARGS=()
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
-b)
|
|
[[ $# -ge 2 ]] || { echo "error: -b requires a BOARD argument" >&2; exit 1; }
|
|
BOARD="$2"
|
|
ARGS+=("$1" "$2")
|
|
shift 2
|
|
;;
|
|
*)
|
|
ARGS+=("$1")
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Setup remote directory. Use `bash -s` + heredoc so REMOTE_DIR (user-overridable)
|
|
# is passed as a positional parameter and never reinterpreted by the remote shell.
|
|
echo "==> Setting up remote $REMOTE:$REMOTE_DIR"
|
|
ssh "$REMOTE" bash -s -- "$REMOTE_DIR" <<'REMOTE'
|
|
set -e
|
|
rm -rf -- "$1"
|
|
# .claude path: usbtest.py's HUNG recovery resolves usb_recover.sh relative to the
|
|
# staged repo root — without it, recovery ENOENTs and the wedge is left in place
|
|
mkdir -p -- "$1/test/hil" "$1/examples" "$1/.claude/skills/usb-kernel-recover/scripts"
|
|
REMOTE
|
|
|
|
# Copy HIL test script and config
|
|
echo "==> Copying test scripts"
|
|
scp -q "$ROOT_DIR/test/hil/hil_test.py" \
|
|
"$ROOT_DIR/test/hil/hil_flash.py" \
|
|
"$ROOT_DIR/test/hil/hil_lock.py" \
|
|
"$ROOT_DIR/test/hil/usbtest.py" \
|
|
"$ROOT_DIR/test/hil/pymtp.py" \
|
|
"$CONFIG" \
|
|
"$REMOTE:$REMOTE_DIR/test/hil/"
|
|
scp -q "$ROOT_DIR/.claude/skills/usb-kernel-recover/scripts/usb_recover.sh" \
|
|
"$REMOTE:$REMOTE_DIR/.claude/skills/usb-kernel-recover/scripts/"
|
|
|
|
# Copy only firmware binaries (elf/bin/hex) plus esptool metadata
|
|
# (config.env + flash_args needed by the esptool flasher), preserving structure
|
|
copy_board_binaries() {
|
|
local src="$1"
|
|
rsync -a --prune-empty-dirs \
|
|
--include='*/' --include='*.elf' --include='*.bin' --include='*.hex' \
|
|
--include='config.env' --include='flash_args' \
|
|
--exclude='*' \
|
|
"$src" "$REMOTE:$REMOTE_DIR/examples/"
|
|
}
|
|
|
|
if [ -n "$BOARD" ]; then
|
|
# Copy the board's build dir plus its variant dirs. Variant names come from
|
|
# $CONFIG (they are not required to be prefixed with the board name); the
|
|
# cmake-build-<BOARD>-* glob is kept as a fallback for ad-hoc local builds.
|
|
# Collect only dirs that actually exist, deduplicated.
|
|
declare -A SEEN_DIRS=()
|
|
BUILD_DIRS=()
|
|
add_build_dir() {
|
|
[[ -d "$1" && -z "${SEEN_DIRS[$1]:-}" ]] || return 0
|
|
SEEN_DIRS[$1]=1
|
|
BUILD_DIRS+=("$1")
|
|
}
|
|
shopt -s nullglob
|
|
for d in "$ROOT_DIR"/examples/cmake-build-"$BOARD" "$ROOT_DIR"/examples/cmake-build-"$BOARD"-*; do
|
|
add_build_dir "$d"
|
|
done
|
|
shopt -u nullglob
|
|
while IFS= read -r v; do
|
|
add_build_dir "$ROOT_DIR/examples/cmake-build-$v"
|
|
done < <(python3 -c '
|
|
import json, sys
|
|
cfg = json.load(open(sys.argv[1]))
|
|
for b in cfg.get("boards", []):
|
|
if b["name"] == sys.argv[2]:
|
|
for v in b.get("variant") or []:
|
|
print(v["name"])
|
|
' "$CONFIG" "$BOARD")
|
|
if [ ${#BUILD_DIRS[@]} -eq 0 ]; then
|
|
echo "Error: no build directory found for $BOARD under $ROOT_DIR/examples/"
|
|
echo "Build first with: cd examples && cmake --preset $BOARD && cmake --build --preset $BOARD"
|
|
exit 1
|
|
fi
|
|
echo "==> Copying binaries for $BOARD (${#BUILD_DIRS[@]} build dir(s))"
|
|
for d in "${BUILD_DIRS[@]}"; do
|
|
copy_board_binaries "$d"
|
|
done
|
|
else
|
|
echo "==> Copying all built binaries"
|
|
# Use `%/` parameter expansion to strip the trailing slash from the glob —
|
|
# rsync needs the bare dir name so the per-board cmake-build-<BOARD>/ subdir
|
|
# is preserved on the remote (hil_test.py looks up binaries by that path).
|
|
for dir in "$ROOT_DIR"/examples/cmake-build-*/; do
|
|
[ -d "$dir" ] && copy_board_binaries "${dir%/}"
|
|
done
|
|
fi
|
|
|
|
# Run test. Use `bash -s` so REMOTE_DIR + ARGS reach the remote shell as positional
|
|
# parameters; quoting and metacharacters in args are preserved.
|
|
CONFIG_BASENAME="$(basename "$CONFIG")"
|
|
echo "==> Running HIL test on $REMOTE"
|
|
rc=0
|
|
ssh "$REMOTE" bash -s -- "$REMOTE_DIR" "${ARGS[@]}" "test/hil/$CONFIG_BASENAME" <<'REMOTE' || rc=$?
|
|
cd -- "$1"
|
|
shift
|
|
# Flasher CLIs live in the user bin dirs on ci.lan (esptool/idf in ~/.local/bin,
|
|
# STM32CubeProgrammer's STM32_Programmer_CLI in ~/bin); the non-interactive shell
|
|
# subprocess used for flashing doesn't source profile/rc, so add them explicitly.
|
|
export PATH="$HOME/.local/bin:$HOME/bin:$PATH"
|
|
python3 -u test/hil/hil_test.py -B examples "$@"
|
|
REMOTE
|
|
|
|
# Copy the generated report back to the local checkout (best-effort; the run's
|
|
# exit code is preserved regardless of whether a report was produced).
|
|
scp -q "$REMOTE:$REMOTE_DIR/hil_report.md" "$ROOT_DIR/hil_report.md" \
|
|
&& echo "==> Report copied to $ROOT_DIR/hil_report.md" \
|
|
|| echo "==> warning: no hil_report.md copied back" >&2
|
|
|
|
exit $rc
|