summaryrefslogtreecommitdiff
path: root/test/hil/hil_test.py
diff options
context:
space:
mode:
authorhathach <[email protected]>2026-07-13 18:01:07 +0700
committerhathach <[email protected]>2026-07-13 18:01:07 +0700
commitff69550b3d8d55b5c9d48a3dfe4e87f7690e9455 (patch)
tree93567f5abd365f572282a6c2c63ce39c2f60fd05 /test/hil/hil_test.py
parent0557655afbb9e608c26ac0c6cbf95c6e69138c77 (diff)
Fix max-effort review findings in lock protocol, workflows, and docs
Confirmed by a 10-finder / 28-verifier adversarial review pass: board_lock.py — the flock is now the sole authority: drop cmd_hold's pid-liveness pre-gate (a live hil_test.py pool worker's stale record no longer blocks a genuinely free board); cmd_release probes the flock and only signals a verified holder, refuses to kill hil_test.py holders (CI mid-test), handles PermissionError; the holder daemon truncates its lock records on SIGTERM and keeps the success pipe clear of fds 0-2 (closed-stdio hold used to leave an orphan holder while reporting failure); --config default resolves beside the script. hil_test.py — truncate the lock record on per-board release (pool workers outlive their flocks); warn instead of silently failing open when the lock dir is unusable; error out on -b names absent from the config (was a silent zero-test exit 0, readable as a green HIL run); drop an emptied board row in accumulate_report (variant boards left a blank ghost row). workflows — remove the stray positional arg that made the validate size stage exit 2 on every run; wrap JSON.parse(args) in all six scripts; factor pr-babysit's drifted reply recipe into postReplyRecipe and dedup refutation replies across cycles; validate args.pr and maxCycles; driver-review rejects an empty dimensions list; hil-validate drops a dead guard clause and retries diagnostics with -v -r 1. agents/docs — port-dev scopes git clang-format to its own files (concurrent workers reformatted each other in shared checkouts); hil-operator/hil skill wording matches actual fail-fast output; the implementation plan is now a DO-NOT-EXECUTE historical record (banner + checked boxes) so plan-executing agents cannot revert shipped files. Verified: lock storm 1-winner-in-10, stale-record hold, closed-stdio hold, dead-pid cleanup, CI-holder refusal, ghost-row 4-scenario merge, unknown-board exit 1, py_compile + check.sh on all six workflows, pre-commit clean. Co-Authored-By: Claude Fable 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Upj4hta5TNoAbidqeC1zZ6
Diffstat (limited to 'test/hil/hil_test.py')
-rwxr-xr-xtest/hil/hil_test.py29
1 files changed, 26 insertions, 3 deletions
diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py
index 2e5085079..4b87154b6 100755
--- a/test/hil/hil_test.py
+++ b/test/hil/hil_test.py
@@ -75,8 +75,12 @@ def acquire_board_lock(board_name):
fd = os.open(os.path.join(BOARD_LOCK_DIR, f'{board_name}.lock'),
os.O_RDWR | os.O_CREAT, 0o666)
fh = os.fdopen(fd, 'r+')
- except OSError:
- return None # odd lock dir (perms, path collision): proceed unlocked
+ except OSError as e:
+ # odd lock dir (perms, path collision): proceed unlocked, but say so —
+ # a silent fail-open is indistinguishable from the intentional bypass
+ print(f'warning: board lock unavailable for {board_name} ({e}); proceeding unlocked',
+ flush=True)
+ return None
try:
fcntl.flock(fh, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
@@ -1767,6 +1771,14 @@ def test_board(board: Board) -> tuple[str, int, list[str], list]:
return name, err_count, sorted(set(failed_tests)), rows
finally:
if _lock_fh:
+ try:
+ # clear our pid record before dropping the flock: this worker
+ # process lives on (pool reuse), so a stale record would make
+ # board_lock.py's pid-liveness checks report a freed board as
+ # still locked for the rest of the run
+ _lock_fh.truncate(0)
+ except OSError:
+ pass
_lock_fh.close()
@@ -1837,7 +1849,13 @@ def accumulate_report(mret: list, report_dir: Path, fresh: bool) -> str:
if rows and not any('board-locked' in cells for _, cells in rows):
# board ran for real this time: clear a stale lock-failure cell
# (its row is keyed by board name; test rows may be variant names)
- acc.get(name, {}).pop('board-locked', None)
+ stale = acc.get(name)
+ if stale is not None:
+ stale.pop('board-locked', None)
+ if not stale:
+ # variant-keyed boards never repopulate the board-name row —
+ # drop it or it renders as a blank ghost row
+ del acc[name]
for row_label, cells in rows:
acc.setdefault(row_label, {}).update(cells)
@@ -1900,6 +1918,11 @@ def main() -> None:
if len(boards) == 0:
config_boards = [e for e in config['boards'] if e['name'] not in skip_boards]
else:
+ unknown = [b for b in boards if b not in {e['name'] for e in config['boards']}]
+ if unknown:
+ # exiting 0 with 'No tests were run.' would read as a green HIL run
+ print(f'ERROR: board(s) not in {config_file.name}: {", ".join(unknown)}')
+ sys.exit(1)
config_boards = [e for e in config['boards'] if e['name'] in boards]
build_err = 0