diff options
Diffstat (limited to 'test')
| -rwxr-xr-x | test/hil/board_lock.py | 32 | ||||
| -rwxr-xr-x | test/hil/hil_test.py | 10 |
2 files changed, 30 insertions, 12 deletions
diff --git a/test/hil/board_lock.py b/test/hil/board_lock.py index feee2b849..42df80b35 100755 --- a/test/hil/board_lock.py +++ b/test/hil/board_lock.py @@ -49,17 +49,22 @@ def read_info(board: str): def is_locked(board: str) -> bool: - """True if some live process currently holds the flock.""" - path = lock_path(board) - if not os.path.exists(path): + """True if the recorded holder process is still alive. + + Deliberately never touches the flock: even a momentary probe lock would + make a concurrent acquirer's LOCK_NB attempt fail spuriously. The flock + taken by acquirers themselves stays the only authority.""" + info = read_info(board) + pid = info.get('pid') if isinstance(info, dict) else None + if not isinstance(pid, int) or pid <= 0: return False - with open(path) as f: - try: - fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB) - fcntl.flock(f, fcntl.LOCK_UN) - return False - except OSError: - return True + try: + os.kill(pid, 0) + except ProcessLookupError: + return False + except PermissionError: + return True # alive but owned by another user (e.g. the CI runner) + return True def cmd_hold(boards, reason): @@ -91,6 +96,13 @@ def cmd_hold(boards, reason): os._exit(0) # holder (grandchild): acquire all flocks, signal the parent, sleep until killed os.close(r_fd) + # Detach stdio: a `hold` whose output is captured must see EOF when the + # front-end exits — the immortal holder must not keep that pipe open. + devnull = os.open(os.devnull, os.O_RDWR) + for std_fd in (0, 1, 2): + os.dup2(devnull, std_fd) + if devnull > 2: + os.close(devnull) try: handles = [] for b in boards: diff --git a/test/hil/hil_test.py b/test/hil/hil_test.py index 979c784ab..2e5085079 100755 --- a/test/hil/hil_test.py +++ b/test/hil/hil_test.py @@ -1704,7 +1704,9 @@ def test_board(board: Board) -> tuple[str, int, list[str], list]: _lock_fh = acquire_board_lock(name) except RuntimeError as e: log_line(f'{name:25} {STATUS_FAILED}: {e}') - return name, 1, [], [] + # visible report row so the ❌ matches the exit code; failed-tests stays + # empty so a re-run repeats the whole board (no bogus -bt test filter) + return name, 1, [], [(name, {'board-locked': 'fail'})] try: # default to all tests test_list = [] @@ -1831,7 +1833,11 @@ def accumulate_report(mret: list, report_dir: Path, fresh: bool) -> str: pass # corrupt/old sidecar: start fresh # merge this run: current cells override prior for boards/tests that ran - for _, _, _, rows in mret: + for name, _, _, rows in mret: + 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) for row_label, cells in rows: acc.setdefault(row_label, {}).update(cells) |
