mirror of
https://github.com/hathach/tinyusb.git
synced 2026-08-18 02:53:35 +00:00
test/hil: fold openocd_wch into openocd, verify per board, resolve firmware by flasher extension (#3804)
test/hil: one openocd flasher, per-board verify and firmware extension The four WCH boards move to `openocd`, leaving one flasher for all. `verify` is now a per-board opt-out, not dropped fleet-wide: WCH cannot read flash back over the WCH-Link sdi transport; the other seven openocd boards can, and say so explicitly. FLASHER_SUFFIX decides each flasher's extension once — find_firmware returns the full path and the flashers pass it through, so a build with only the wrong artifact is skipped rather than failed mid-flash. --skip-flash bypasses the filter. rescue_openocd() power-on-resets a wedged RP2040/RP2350 via its Rescue DP from the flash retry; the probe has no reset line. Drops unused openocd_adi, stflash, wlink_rs and uniflash, parks the unstable ra6m5_ek, and tests that every roster flasher name dispatches.
This commit is contained in:
@ -26,7 +26,7 @@ build_dir = 'cmake-build'
|
||||
CMD_TIMEOUT = int(os.getenv('HIL_CMD_TIMEOUT', '180'))
|
||||
|
||||
# flasher names (dispatch key, board['flasher']['name'].lower()) whose reset_* is a no-op
|
||||
RESET_NOOP = {'esptool', 'lm4flash', 'stflash', 'uniflash'}
|
||||
RESET_NOOP = {'esptool', 'lm4flash'}
|
||||
|
||||
# extra parents find_firmware ALSO searches after build_dir. Empty by default so
|
||||
# hil_test's -B stays authoritative (a board missing there must report "Skip (no
|
||||
@ -46,7 +46,6 @@ def cmd_stdout_text(out: Any) -> str:
|
||||
# -------------------------------------------------------------
|
||||
# Path
|
||||
# -------------------------------------------------------------
|
||||
OPENCOD_ADI_PATH = Path.home() / 'app' / 'openocd_adi'
|
||||
TINYUSB_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
# get usb serial by id
|
||||
@ -129,7 +128,7 @@ def run_cmd(cmd: str, cwd: str | None = None, timeout: int = CMD_TIMEOUT) -> sub
|
||||
|
||||
def flash_jlink(board: Board, firmware: str) -> subprocess.CompletedProcess:
|
||||
flasher = board['flasher']
|
||||
script = ['halt', 'r', f'loadfile {firmware}.elf', 'r', 'go', 'exit']
|
||||
script = ['halt', 'r', f'loadfile {firmware}', 'r', 'go', 'exit']
|
||||
f_jlink = Path(f'{board["name"]}_{Path(firmware).name}.jlink')
|
||||
with f_jlink.open('w') as f:
|
||||
f.writelines(f'{s}\n' for s in script)
|
||||
@ -151,88 +150,84 @@ def reset_jlink(board: Board) -> subprocess.CompletedProcess:
|
||||
|
||||
def flash_stlink(board, firmware):
|
||||
flasher = board['flasher']
|
||||
return run_cmd(f'STM32_Programmer_CLI --connect port=swd sn={flasher["uid"]} --write {firmware}.elf --go')
|
||||
return run_cmd(f'STM32_Programmer_CLI --connect port=swd sn={flasher["uid"]} --write {firmware} --go')
|
||||
|
||||
|
||||
def reset_stlink(board):
|
||||
flasher = board['flasher']
|
||||
return run_cmd(f'STM32_Programmer_CLI --connect port=swd sn={flasher["uid"]} --rst --go')
|
||||
|
||||
def flash_stflash(board, firmware):
|
||||
flasher = board['flasher']
|
||||
ret = run_cmd(f'st-flash --serial {flasher["uid"]} write {firmware}.bin 0x8000000')
|
||||
return ret
|
||||
|
||||
|
||||
def reset_stflash(board):
|
||||
flasher = board['flasher']
|
||||
return subprocess.CompletedProcess(args=['dummy'], returncode=0)
|
||||
|
||||
def _openocd_cmd_base(flasher):
|
||||
return (f'openocd -c "tcl_port disabled" -c "gdb_port disabled" -c "telnet_port disabled" '
|
||||
f'-c "adapter serial {flasher["uid"]}" {flasher["args"]}')
|
||||
|
||||
|
||||
# `verify` is on by default and opted out per board with "verify": false in the roster.
|
||||
# WCH targets must opt out: flash read-back over the WCH-Link sdi transport returns a
|
||||
# repeated word instead of memory contents, so verification always reports a mismatch and
|
||||
# fails the flash (measured on ch32v103r and ch32v307v, 2026-07-30). Do NOT drop verify
|
||||
# fleet-wide to accommodate them — every other openocd board can read back, and without it
|
||||
# a partial or corrupt write exits 0 and the test phase runs bad firmware.
|
||||
def flash_openocd(board, firmware):
|
||||
flasher = board['flasher']
|
||||
ret = run_cmd(f'openocd -c "tcl_port disabled" -c "gdb_port disabled" -c "adapter serial {flasher["uid"]}" '
|
||||
f'{flasher["args"]} -c "init; halt; program {firmware}.elf verify; reset; exit"')
|
||||
verify = ' verify' if flasher.get('verify', True) else ''
|
||||
ret = run_cmd(f'{_openocd_cmd_base(flasher)} -c "program {firmware}{verify} reset exit"')
|
||||
return ret
|
||||
|
||||
|
||||
def reset_openocd(board):
|
||||
flasher = board['flasher']
|
||||
ret = run_cmd(f'openocd -c "tcl_port disabled" -c "gdb_port disabled" -c "adapter serial {flasher["uid"]}" '
|
||||
f'{flasher["args"]} -c "init; reset run; exit"')
|
||||
ret = run_cmd(f'{_openocd_cmd_base(flasher)} -c "init; reset run; exit"')
|
||||
return ret
|
||||
|
||||
|
||||
def flash_openocd_wch(board, firmware):
|
||||
# OpenOCD's messages for "the target's debug port did not answer". The probe is fine when
|
||||
# these appear (the log still shows "CMSIS-DAP: Interface ready"); the chip's debug clock
|
||||
# is gone, which no reset the probe can drive would fix -- the CMSIS-DAP Debug Probe has no
|
||||
# nRESET line at all. Which message you get depends on the DAP topology, NOT on the board:
|
||||
# rp2040.cfg creates three multidrop DAPs (cores 0/1 and the Rescue DP at instance 0xf) so
|
||||
# it fails in swd_multidrop_select, while rp2350.cfg creates a single plain ADIv6 DAP that
|
||||
# fails earlier in swd_connect. A dead RP2040 can also produce the second one if the very
|
||||
# first DP read never gets through, so both are accepted for both chips -- it is the target
|
||||
# cfg in the roster args, below, that picks how to rescue.
|
||||
DAP_WEDGED = ('Failed to connect multidrop', 'Error connecting DP: cannot read IDR')
|
||||
|
||||
# How each RP target reaches its Rescue DP, keyed by the target cfg named in flasher args.
|
||||
# (cfg substitution, extra args): rp2040.cfg drives the Rescue DP itself behind a RESCUE
|
||||
# flag and calls init/shutdown on its own; rp2350 has a separate cfg that pokes the rescue
|
||||
# bit via an AP register but never shuts down, so it would sit in the server loop until
|
||||
# CMD_TIMEOUT without an explicit one.
|
||||
RESCUE_CFG = {
|
||||
'target/rp2040.cfg': ('target/rp2040.cfg', '-c "set RESCUE 1" ', ''),
|
||||
'target/rp2350.cfg': ('target/rp2350-rescue.cfg', '', ' -c "shutdown"'),
|
||||
}
|
||||
|
||||
|
||||
def rescue_openocd(board, flash_out: str = '') -> bool:
|
||||
"""Power-on-reset a wedged RP2040/RP2350 through its Rescue DP, the one debug port not
|
||||
gated by the system clock (RP2040 datasheet 2.3.4.2): setting CDBGPWRUPREQ hard-resets
|
||||
the chip, and the bootrom halts it in a safe state ready to be flashed. This is the
|
||||
only way back for a target whose cores have stopped answering -- otherwise the board
|
||||
needs a physical replug, since the probe carries no reset line.
|
||||
|
||||
No-op (returns False) unless this is an openocd RP board AND the flash output shows the
|
||||
wedge, so a flash that failed for any other reason still just retries. Returns True
|
||||
when a rescue was attempted; the caller should retry the flash afterwards."""
|
||||
flasher = board['flasher']
|
||||
ret = run_cmd(f'openocd -c "tcl_port disabled" -c "gdb_port disabled" -c "telnet_port disabled" '
|
||||
f'-c "adapter serial {flasher["uid"]}" {flasher.get("args", "")} -c "program {firmware}.elf reset exit"')
|
||||
return ret
|
||||
|
||||
|
||||
def reset_openocd_wch(board):
|
||||
flasher = board['flasher']
|
||||
ret = run_cmd(f'openocd -c "tcl_port disabled" -c "gdb_port disabled" -c "telnet_port disabled" '
|
||||
f'-c "adapter serial {flasher["uid"]}" {flasher.get("args", "")} -c "init; reset run; exit"')
|
||||
return ret
|
||||
|
||||
|
||||
def flash_openocd_adi(board: Board, firmware: str) -> subprocess.CompletedProcess:
|
||||
flasher = board['flasher']
|
||||
openocd = OPENCOD_ADI_PATH / 'src' / 'openocd'
|
||||
tcl_dir = OPENCOD_ADI_PATH / 'tcl'
|
||||
ret = run_cmd(f'{openocd} -c "adapter serial {flasher["uid"]}" -s {tcl_dir} '
|
||||
f'{flasher["args"]} -c "program {firmware}.elf reset exit"')
|
||||
return ret
|
||||
|
||||
|
||||
def reset_openocd_adi(board: Board) -> subprocess.CompletedProcess:
|
||||
flasher = board['flasher']
|
||||
openocd = OPENCOD_ADI_PATH / 'src' / 'openocd'
|
||||
tcl_dir = OPENCOD_ADI_PATH / 'tcl'
|
||||
ret = run_cmd(f'{openocd} -c "adapter serial {flasher["uid"]}" -s {tcl_dir} '
|
||||
f'{flasher["args"]} -c "program reset exit"')
|
||||
return ret
|
||||
|
||||
|
||||
def flash_wlink_rs(board, firmware):
|
||||
flasher = board['flasher']
|
||||
# wlink use index for probe selection and lacking usb serial support
|
||||
ret = run_cmd(f'wlink flash {firmware}.elf')
|
||||
return ret
|
||||
|
||||
|
||||
def reset_wlink_rs(board):
|
||||
flasher = board['flasher']
|
||||
# wlink use index for probe selection and lacking usb serial support
|
||||
ret = run_cmd(f'wlink reset')
|
||||
return ret
|
||||
if flasher['name'].lower() != 'openocd' or not any(m in flash_out for m in DAP_WEDGED):
|
||||
return False
|
||||
for cfg, (rescue_cfg, pre, post) in RESCUE_CFG.items():
|
||||
if cfg in flasher['args']:
|
||||
args = flasher['args'].replace(cfg, rescue_cfg)
|
||||
return run_cmd(f'{_openocd_cmd_base({**flasher, "args": pre + args})}{post}').returncode == 0
|
||||
return False
|
||||
|
||||
|
||||
def flash_esptool(board: Board, firmware: str) -> subprocess.CompletedProcess:
|
||||
flasher = board['flasher']
|
||||
port = get_serial_dev(flasher["uid"], None, None, 0)
|
||||
fw_dir = Path(f'{firmware}.bin').parent
|
||||
fw_dir = Path(firmware).parent
|
||||
with (fw_dir / 'config.env').open() as f:
|
||||
idf_target = json.load(f)['IDF_TARGET']
|
||||
with (fw_dir / 'flash_args').open() as f:
|
||||
@ -248,21 +243,10 @@ def reset_esptool(board):
|
||||
return subprocess.CompletedProcess(args=['dummy'], returncode=0)
|
||||
|
||||
|
||||
def flash_uniflash(board, firmware):
|
||||
flasher = board['flasher']
|
||||
ret = run_cmd(f'dslite.sh {flasher["args"]} -f {firmware}.hex')
|
||||
return ret
|
||||
|
||||
|
||||
def reset_uniflash(board):
|
||||
flasher = board['flasher']
|
||||
return subprocess.CompletedProcess(args=['dummy'], returncode=0)
|
||||
|
||||
|
||||
def flash_lm4flash(board, firmware):
|
||||
# TI Tiva-C / Stellaris ICDI: lightweight lm4flash, resets and runs after write
|
||||
flasher = board['flasher']
|
||||
ret = run_cmd(f'lm4flash -s {flasher["uid"]} {flasher["args"]} {firmware}.bin')
|
||||
ret = run_cmd(f'lm4flash -s {flasher["uid"]} {flasher["args"]} {firmware}')
|
||||
return ret
|
||||
|
||||
|
||||
@ -272,22 +256,42 @@ def reset_lm4flash(board):
|
||||
return subprocess.CompletedProcess(args=['dummy'], returncode=0)
|
||||
|
||||
|
||||
def find_firmware(variant: str, example: str, roots: list | None = None):
|
||||
"""Locate a built example's firmware base path (no extension) under
|
||||
<build_dir>/cmake-build-<variant>/<example>/, then under EXTRA_BUILD_DIRS
|
||||
(empty unless the caller opts in — see its comment). `roots` overrides that
|
||||
search list entirely for one call (e.g. to find a build just produced by
|
||||
tools/build.py in its fixed cmake-build/ layout without widening the global
|
||||
policy). Accepts the single-config layout (firmware directly in the example
|
||||
dir) or Ninja Multi-Config (a per-config subdir like RelWithDebInfo/).
|
||||
Returns the base Path, or None if not built."""
|
||||
# The one place a flasher's firmware extension is decided: find_firmware resolves the
|
||||
# path with it and the flash_* functions pass that path through untouched. A flasher
|
||||
# added here without an entry falls back to .elf-or-.bin and can be handed the wrong
|
||||
# file — test_hil_select's TestRosterFlashersDispatch fails if a roster names one.
|
||||
FLASHER_SUFFIX = {
|
||||
'esptool': '.bin',
|
||||
'jlink': '.elf',
|
||||
'lm4flash': '.bin',
|
||||
'openocd': '.elf',
|
||||
'stlink': '.elf',
|
||||
}
|
||||
|
||||
|
||||
def find_firmware(variant: str, example: str, roots: list | None = None, flasher: str | None = None):
|
||||
"""Locate a built example's firmware under <build_dir>/cmake-build-<variant>/<example>/,
|
||||
then under EXTRA_BUILD_DIRS (empty unless the caller opts in — see its comment).
|
||||
`roots` overrides that search list entirely for one call (e.g. to find a build just
|
||||
produced by tools/build.py in its fixed cmake-build/ layout without widening the
|
||||
global policy). `flasher` is the roster flasher name: it selects which extension
|
||||
counts (see FLASHER_SUFFIX), so a build that produced only the other one is reported
|
||||
missing — a clean "Skip (no binary)" — instead of being handed to the flasher, which
|
||||
would fail opaquely on the absent file and burn every retry plus the board lock.
|
||||
Accepts the single-config layout (firmware directly in the example dir) or Ninja
|
||||
Multi-Config (a per-config subdir like RelWithDebInfo/).
|
||||
Returns the full Path INCLUDING extension, or None if not built."""
|
||||
base = Path(example).name
|
||||
suffixes = [FLASHER_SUFFIX.get(flasher.lower())] if flasher else []
|
||||
if not suffixes or suffixes == [None]:
|
||||
suffixes = ['.elf', '.bin']
|
||||
for bd in dict.fromkeys(roots if roots is not None else [build_dir, *EXTRA_BUILD_DIRS]):
|
||||
fw_dir = TINYUSB_ROOT / bd / f'cmake-build-{variant}' / example
|
||||
if not fw_dir.is_dir():
|
||||
continue
|
||||
for cand in [fw_dir / base, fw_dir / 'RelWithDebInfo' / base,
|
||||
*(p.with_suffix('') for p in sorted(fw_dir.glob(f'*/{base}.elf')))]:
|
||||
if cand.with_suffix('.elf').exists() or cand.with_suffix('.bin').exists():
|
||||
return cand
|
||||
*(p.with_suffix('') for s in suffixes for p in sorted(fw_dir.glob(f'*/{base}{s}')))]:
|
||||
for s in suffixes:
|
||||
if cand.with_suffix(s).exists():
|
||||
return cand.with_suffix(s)
|
||||
return None
|
||||
|
||||
@ -201,7 +201,7 @@ def resolve_variant(board: dict, example: str, note: list | None = None) -> str:
|
||||
name = board['name']
|
||||
for v in board.get('variant') or [{'name': name}]:
|
||||
vn = v['name']
|
||||
if hil_flash.find_firmware(vn, example):
|
||||
if hil_flash.find_firmware(vn, example, flasher=board['flasher']['name']):
|
||||
if vn != name and note is not None and f'variant: {vn}' not in note:
|
||||
note.append(f'variant: {vn}')
|
||||
return vn
|
||||
@ -211,8 +211,8 @@ def resolve_variant(board: dict, example: str, note: list | None = None) -> str:
|
||||
def pick_example(board: dict, note: list, build_missing: bool = True):
|
||||
"""(example, kind, variant, fw) with built firmware for this board; kind is
|
||||
'device' (uid check) or 'host' (serial-output check); variant is the resolved
|
||||
build-dir variant that has it (see resolve_variant); fw is the firmware base
|
||||
path to flash. When nothing is built and build_missing is set (the default —
|
||||
build-dir variant that has it (see resolve_variant); fw is the firmware path to
|
||||
flash, extension included. When nothing is built and build_missing is set (the default —
|
||||
never skip a board for lack of a build), the preferred candidate is built on
|
||||
the spot via ensure_fw."""
|
||||
tests = board.get('tests', {})
|
||||
@ -229,7 +229,7 @@ def pick_example(board: dict, note: list, build_missing: bool = True):
|
||||
if ex in skip:
|
||||
continue
|
||||
variant = resolve_variant(board, ex, note)
|
||||
fw = hil_flash.find_firmware(variant, ex)
|
||||
fw = hil_flash.find_firmware(variant, ex, flasher=board['flasher']['name'])
|
||||
if fw:
|
||||
return ex, kind, variant, fw
|
||||
if not build_missing:
|
||||
@ -472,7 +472,7 @@ def ensure_fw(board: dict, variant: str, example: str, note: list):
|
||||
(variant, example) per run, success or failure — memoized in _builds, so a
|
||||
repeat call (park, under the held flock) resolves instantly even when an
|
||||
exclusive -B hides the fresh cmake-build/ artifact from the global search."""
|
||||
fw = hil_flash.find_firmware(variant, example)
|
||||
fw = hil_flash.find_firmware(variant, example, flasher=board['flasher']['name'])
|
||||
if fw:
|
||||
return fw
|
||||
key, base = (variant, example), Path(example).name
|
||||
@ -523,7 +523,8 @@ def ensure_fw(board: dict, variant: str, example: str, note: list):
|
||||
# look there too even when an explicit -B narrowed the global search — this is
|
||||
# OUR fresh build, not a stale-candidate fallback
|
||||
fw = hil_flash.find_firmware(variant, example,
|
||||
roots=[hil_flash.build_dir, 'cmake-build'])
|
||||
roots=[hil_flash.build_dir, 'cmake-build'],
|
||||
flasher=board['flasher']['name'])
|
||||
_builds[key] = (fw, 'ok' if fw else 'no-fw')
|
||||
note.append(f'built {base}' if fw else f'build produced no firmware: {base}')
|
||||
return fw
|
||||
@ -533,7 +534,7 @@ def ensure_board_test(board: dict, variant: str, note: list):
|
||||
"""board_test firmware for parking, building it if absent (via ensure_fw).
|
||||
Espressif included — tools/build.py builds board_test for that family too;
|
||||
the build just needs the ESP-IDF env (127 → noted, park is then skipped)."""
|
||||
fw = hil_flash.find_firmware(variant, 'device/board_test')
|
||||
fw = hil_flash.find_firmware(variant, 'device/board_test', flasher=board['flasher']['name'])
|
||||
if fw:
|
||||
return fw
|
||||
variants = board.get('variant') or [{'name': board['name']}]
|
||||
@ -706,7 +707,8 @@ def check_board(board: dict, args, allow_recovery: bool, seen: dict) -> dict:
|
||||
# even under --no-park; --no-build gates EVERY build, board_test included
|
||||
need_bt = (not args.no_build
|
||||
and (not args.no_park or kind == 'host')
|
||||
and hil_flash.find_firmware(bt_variant, 'device/board_test') is None)
|
||||
and hil_flash.find_firmware(bt_variant, 'device/board_test',
|
||||
flasher=board['flasher']['name']) is None)
|
||||
if need_example or need_bt:
|
||||
# builds are long and run BEFORE locking (park must never hold the flock
|
||||
# through a build); peek the lock first so minutes of building are not
|
||||
|
||||
@ -1370,13 +1370,17 @@ def test_example(board: Board, variant: str, example: str) -> tuple[int, str, st
|
||||
|
||||
test_name = f'{variant:40} {example:30} ...'
|
||||
|
||||
fw_name = hil_flash.find_firmware(variant, example)
|
||||
# --skip-flash runs whatever is already on the board, so any build counts as present:
|
||||
# only the flashing path needs the artifact this board's flasher actually consumes.
|
||||
# Filtering there too would skip the test as "no binary" over an extension it never uses.
|
||||
fw_name = hil_flash.find_firmware(variant, example,
|
||||
flasher=None if skip_flash else board['flasher']['name'])
|
||||
if fw_name is None:
|
||||
log_line(f'{test_name} Skip (no binary)')
|
||||
return 0, 'skip', None
|
||||
|
||||
if verbose:
|
||||
log_line(f'Flashing {fw_name}.elf')
|
||||
log_line(f'Firmware {fw_name}')
|
||||
|
||||
# flash firmware (unless --skip-flash), then run the test. Both may fail randomly,
|
||||
# retry a few times.
|
||||
@ -1396,7 +1400,13 @@ def test_example(board: Board, variant: str, example: str) -> tuple[int, str, st
|
||||
if PROFILE:
|
||||
log_line(f'[prof] {variant} {example} flash attempt {i + 1}: '
|
||||
f'{time.monotonic() - t_flash:.1f}s rc={ret.returncode}')
|
||||
flash_ok = (ret.returncode == 0)
|
||||
flash_ok = (ret.returncode == 0)
|
||||
# A wedged RP2040/RP2350 DAP answers nothing and the probe has no reset
|
||||
# line, so the retry would fail identically; POR it via the Rescue DP
|
||||
# first. No-op for every other board and every other flash failure.
|
||||
if not flash_ok and i + 1 < max_retry and \
|
||||
hil_flash.rescue_openocd(board, hil_flash.cmd_stdout_text(ret.stdout)):
|
||||
log_line(f'{variant} {example}: DAP wedged, rescued via Rescue DP')
|
||||
if flash_ok:
|
||||
try:
|
||||
tret = globals()[f'test_{example.replace("/", "_")}'](board)
|
||||
|
||||
@ -9,6 +9,7 @@ import sys
|
||||
import unittest
|
||||
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
import hil_flash
|
||||
import hil_select
|
||||
from hil_examples import device_tests, dual_tests, host_test
|
||||
|
||||
@ -26,6 +27,19 @@ def real_rosters():
|
||||
return rosters
|
||||
|
||||
|
||||
def roster_flashers():
|
||||
"""(roster path, board) for every board in the live rosters, `boards-skip`
|
||||
included: a parked board's flasher name must still dispatch, so that unparking it
|
||||
is not what discovers the name went stale."""
|
||||
for name in ('tinyusb.json', 'hfp.json'):
|
||||
path = os.path.join(REPO, 'test/hil', name)
|
||||
with open(path) as f:
|
||||
cfg = json.load(f)
|
||||
for key in ('boards', 'boards-skip'):
|
||||
for b in cfg.get(key, []):
|
||||
yield f'test/hil/{name}', b
|
||||
|
||||
|
||||
def on_roster(tc, *names):
|
||||
"""The subset of `names` currently in the live rig rosters, skipping the test
|
||||
when none are. Parking/unparking a board is routine rig maintenance and must not
|
||||
@ -538,5 +552,30 @@ class TestPortWithoutFamilyIsFull(unittest.TestCase):
|
||||
self.assertTrue(any('no board family' in r for r in s['reasons']), s['reasons'])
|
||||
|
||||
|
||||
class TestRosterFlashersDispatch(unittest.TestCase):
|
||||
"""hil_test and hil_pool_check resolve a board's flasher with a bare
|
||||
getattr(hil_flash, f'flash_{name}'), and hil_test does it inside a redirect_stdout —
|
||||
so a renamed or typo'd roster name raises an AttributeError whose output is swallowed,
|
||||
with nothing pointing at the roster as the thing to edit. Renaming a flash_*/reset_*
|
||||
pair without updating every roster must fail here instead."""
|
||||
|
||||
def test_flash_and_reset_exist_for_every_roster_flasher(self):
|
||||
for path, board in roster_flashers():
|
||||
name = board['flasher']['name'].lower()
|
||||
for fn in (f'flash_{name}', f'reset_{name}'):
|
||||
self.assertTrue(callable(getattr(hil_flash, fn, None)),
|
||||
f'{path}: {board["name"]} uses flasher "{name}" '
|
||||
f'but hil_flash.{fn} does not exist')
|
||||
|
||||
def test_firmware_suffix_known_for_every_roster_flasher(self):
|
||||
"""find_firmware falls back to accepting .elf-or-.bin when a flasher is missing
|
||||
from FLASHER_SUFFIX, silently restoring the mismatch that map exists to catch."""
|
||||
for path, board in roster_flashers():
|
||||
name = board['flasher']['name'].lower()
|
||||
self.assertIn(name, hil_flash.FLASHER_SUFFIX,
|
||||
f'{path}: {board["name"]} uses flasher "{name}" '
|
||||
f'with no hil_flash.FLASHER_SUFFIX entry')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=1)
|
||||
|
||||
@ -149,7 +149,8 @@
|
||||
"flasher": {
|
||||
"name": "openocd",
|
||||
"uid": "E6614C311B597D32",
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/max32665.cfg"
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/max32665.cfg",
|
||||
"verify": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -239,7 +240,8 @@
|
||||
"flasher": {
|
||||
"name": "openocd",
|
||||
"uid": "E6614103E72C1D2F",
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \"adapter speed 5000\""
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \"adapter speed 5000\"",
|
||||
"verify": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -268,7 +270,8 @@
|
||||
"flasher": {
|
||||
"name": "openocd",
|
||||
"uid": "E6633861A3819D38",
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \"adapter speed 5000\""
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/rp2040.cfg -c \"adapter speed 5000\"",
|
||||
"verify": true
|
||||
},
|
||||
"comment": "Test native host"
|
||||
},
|
||||
@ -293,7 +296,8 @@
|
||||
"flasher": {
|
||||
"name": "openocd",
|
||||
"uid": "E6633861A3978538",
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/rp2350.cfg -c \"adapter speed 5000\""
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/rp2350.cfg -c \"adapter speed 5000\"",
|
||||
"verify": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -322,7 +326,8 @@
|
||||
"flasher": {
|
||||
"name": "openocd",
|
||||
"uid": "E663AC91D3359B38",
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/rp2350.cfg -c \"adapter speed 5000\""
|
||||
"args": "-f interface/cmsis-dap.cfg -f target/rp2350.cfg -c \"adapter speed 5000\"",
|
||||
"verify": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -404,7 +409,8 @@
|
||||
"flasher": {
|
||||
"name": "openocd",
|
||||
"uid": "004C00343137510F39383538",
|
||||
"args": "-f interface/stlink.cfg -f target/stm32h7x.cfg"
|
||||
"args": "-f interface/stlink.cfg -f target/stm32h7x.cfg",
|
||||
"verify": true
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -418,7 +424,8 @@
|
||||
"flasher": {
|
||||
"name": "openocd",
|
||||
"uid": "066FFF495087534867063844",
|
||||
"args": "-f interface/stlink.cfg -f target/stm32g0x.cfg"
|
||||
"args": "-f interface/stlink.cfg -f target/stm32g0x.cfg",
|
||||
"verify": true
|
||||
},
|
||||
"comment": "32-bit scheme, 2KB USB SRAM"
|
||||
},
|
||||
@ -463,9 +470,10 @@
|
||||
"dual": false
|
||||
},
|
||||
"flasher": {
|
||||
"name": "openocd_wch",
|
||||
"name": "openocd",
|
||||
"uid": "A76D8F062C2A",
|
||||
"args": "-f target/wch-riscv.cfg"
|
||||
"args": "-f target/wch-riscv.cfg",
|
||||
"verify": false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -478,9 +486,10 @@
|
||||
"dual": false
|
||||
},
|
||||
"flasher": {
|
||||
"name": "openocd_wch",
|
||||
"name": "openocd",
|
||||
"uid": "BC4954081051",
|
||||
"args": "-f target/wch-riscv.cfg"
|
||||
"args": "-f target/wch-riscv.cfg",
|
||||
"verify": false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -497,9 +506,10 @@
|
||||
"dual": false
|
||||
},
|
||||
"flasher": {
|
||||
"name": "openocd_wch",
|
||||
"name": "openocd",
|
||||
"uid": "BC5DA47360D0",
|
||||
"args": "-f target/wch-riscv.cfg"
|
||||
"args": "-f target/wch-riscv.cfg",
|
||||
"verify": false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -512,9 +522,10 @@
|
||||
"dual": false
|
||||
},
|
||||
"flasher": {
|
||||
"name": "openocd_wch",
|
||||
"name": "openocd",
|
||||
"uid": "57468F06DC03",
|
||||
"args": "-f target/wch-riscv.cfg"
|
||||
"args": "-f target/wch-riscv.cfg",
|
||||
"verify": false
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -561,22 +572,6 @@
|
||||
"uid": "1051856258",
|
||||
"args": "-device NRF54LM20A_M33"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ra6m5_ek",
|
||||
"uid": "8419032D32363657364EF4622D294B4E",
|
||||
"tests": {
|
||||
"device": true,
|
||||
"host": false,
|
||||
"dual": false,
|
||||
"skip": ["device/cdc_msc_throughput", "device/msc_dual_lun"],
|
||||
"comment": "MSC writes wedge the uPD720201 host (URBs queued, zero wire activity, bus-15 ctrl xfers time out until the device's URBs are killed); reproduced identically with master firmware - device side armed+BUF and exonerated. MSC reads and usbtest bulk (15.8 MB/s) are fine"
|
||||
},
|
||||
"flasher": {
|
||||
"name": "jlink",
|
||||
"uid": "000831915224",
|
||||
"args": "-device R7FA6M5BH"
|
||||
}
|
||||
}
|
||||
],
|
||||
"boards-skip": [
|
||||
@ -612,6 +607,23 @@
|
||||
"uid": "000778170924",
|
||||
"args": "-device stm32f769ni"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ra6m5_ek",
|
||||
"uid": "8419032D32363657364EF4622D294B4E",
|
||||
"comment": "Unstable in CI: intermittent usbtest failures plus cdc_dual_ports/hid_boot_interface/midi_test/mtp/printer_to_cdc flapping. Parked until diagnosed",
|
||||
"tests": {
|
||||
"device": true,
|
||||
"host": false,
|
||||
"dual": false,
|
||||
"skip": ["device/cdc_msc_throughput", "device/msc_dual_lun"],
|
||||
"comment": "MSC writes wedge the uPD720201 host (URBs queued, zero wire activity, bus-15 ctrl xfers time out until the device's URBs are killed); reproduced identically with master firmware - device side armed+BUF and exonerated. MSC reads and usbtest bulk (15.8 MB/s) are fine"
|
||||
},
|
||||
"flasher": {
|
||||
"name": "jlink",
|
||||
"uid": "000831915224",
|
||||
"args": "-device R7FA6M5BH"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user