diff --git a/test/hil/hfp.json b/test/hil/hfp.json index 735d5a402..17fbb7605 100644 --- a/test/hil/hfp.json +++ b/test/hil/hfp.json @@ -36,7 +36,11 @@ "flasher": { "name": "jlink", "uid": "728973776", - "args": "-device LPC43S67_M4" + "args": "-device LPC43S67_M4", + "pre_flash": { + "device/usbtest": "device/board_test", + "device/cdc_msc_throughput": "device/board_test" + } } } ] diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index 0efc6826f..72af6c697 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -321,6 +321,7 @@ class FlasherCfg(TypedDict): name: str uid: str args: str + pre_flash: NotRequired[dict[str, str]] # target example -> USB-off separator example class AttachedDevCfg(TypedDict, total=False): @@ -1830,6 +1831,19 @@ def find_firmware(variant: str, example: str): return None +def usb_uid_paths(uid: str) -> set[str]: + """Return sysfs device paths currently exposing the requested USB serial.""" + paths = set() + for f in glob.glob('/sys/bus/usb/devices/*/serial'): + try: + with open(f) as serial_file: + if serial_file.read().strip().lower() == uid.lower(): + paths.add(os.path.dirname(f)) + except OSError: + pass + return paths + + def test_example(board: Board, variant: str, example: str) -> tuple[int, str, str | None]: """ Test example firmware @@ -1852,7 +1866,16 @@ def test_example(board: Board, variant: str, example: str) -> tuple[int, str, st log_line(f'{test_name} Skip (no binary)') return 0, 'skip', None + pre_flash_example = None if skip_flash else board['flasher'].get('pre_flash', {}).get(example) + pre_flash_name = find_firmware(variant, pre_flash_example) if pre_flash_example else None + if pre_flash_example and pre_flash_name is None: + log_line(f'{test_name} {STATUS_FAILED}: ' + f'pre-flash firmware {pre_flash_example} not found') + return 1, 'fail', None + if verbose: + if pre_flash_name is not None: + log_line(f'Pre-flashing {pre_flash_name}.elf') log_line(f'Flashing {fw_name}.elf') # flash firmware (unless --skip-flash), then run the test. Both may fail randomly, @@ -1867,13 +1890,36 @@ def test_example(board: Board, variant: str, example: str) -> tuple[int, str, st attempt_out = io.StringIO() with redirect_stdout(attempt_out): if not skip_flash: + flash_ok = True + flash_error = '' with flash_permit(board['uid']): - t_flash = time.monotonic() - ret = globals()[f'flash_{board["flasher"]["name"].lower()}'](board, str(fw_name)) - 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) + if pre_flash_name is not None: + previous_usb_paths = usb_uid_paths(board['uid']) + ret = globals()[f'flash_{board["flasher"]["name"].lower()}']( + board, str(pre_flash_name)) + flash_ok = (ret.returncode == 0) + if not flash_ok: + flash_error = f'Pre-flash {pre_flash_example} failed' + elif previous_usb_paths: + disconnected = wait_until( + lambda: all(not os.path.exists(p) for p in previous_usb_paths), step=0.1) + if not disconnected: + flash_ok = False + flash_error = (f'Pre-flash {pre_flash_example} did not disconnect ' + f'USB device {board["uid"]}') + else: + time.sleep(0.1) + + if flash_ok: + t_flash = time.monotonic() + ret = globals()[f'flash_{board["flasher"]["name"].lower()}']( + board, str(fw_name)) + 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) + if not flash_ok: + flash_error = 'Flash failed' if flash_ok: try: tret = globals()[f'test_{example.replace("/", "_")}'](board) @@ -1911,10 +1957,10 @@ def test_example(board: Board, variant: str, example: str) -> tuple[int, str, st log_line(msg) time.sleep(0.5) else: - last_err = 'Flash failed' + last_err = flash_error last_detail = compact_output(attempt_out.getvalue()) if i < max_retry - 1: - msg = f'{test_name} retry {i+2}/{max_retry}: flash failed' + msg = f'{test_name} retry {i+2}/{max_retry}: {flash_error}' if last_detail: msg += f' {last_detail}' log_line(msg)