From 9512966d81257ba14d7b54028a08186f5ab80692 Mon Sep 17 00:00:00 2001 From: Michael Pham <61564344+Mikefly123@users.noreply.github.com> Date: Sun, 30 Aug 2026 11:52:51 -0700 Subject: [PATCH] fix(test): guard YAMCS event subscription teardown against websocket race test_noop_round_trip failed intermittently in integration-uart with: AttributeError: 'NoneType' object has no attribute 'close_frame' websocket/_app.py:211 The round trip itself was healthy in every failing run (both failures completed in ~1s, well inside the 180s budget, i.e. the NoOpReceived event was observed and the test returned). The failure came entirely from `subscription.cancel()` in the `finally` block. websocket-client's WebSocketApp.close() has a check-then-use race on self.sock: it tests the attribute for truthiness, calls self.sock.close() - which wakes the reader thread, and that thread sets self.sock = None - then dereferences self.sock again to capture close_frame. The race is still present in websocket-client 1.9.1, so bumping the dependency does not fix it. Swallow and log teardown errors so they cannot replace the test's real result (a pass, or the AssertionError raised above) with a misleading traceback. Closes #514 Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01WBD3NFb2h8TxkuxrfSRyhS --- .../test/yamcs/test_yamcs_noop.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/PROVESFlightControllerReference/test/yamcs/test_yamcs_noop.py b/PROVESFlightControllerReference/test/yamcs/test_yamcs_noop.py index 68f35800..04df1c0c 100644 --- a/PROVESFlightControllerReference/test/yamcs/test_yamcs_noop.py +++ b/PROVESFlightControllerReference/test/yamcs/test_yamcs_noop.py @@ -11,6 +11,7 @@ the full TC + TM + events path is healthy. """ +import logging import queue import time @@ -71,4 +72,15 @@ def test_noop_round_trip(yamcs_client, yamcs_processor, yamcs_instance): f"{TOTAL_TIMEOUT_S}s after {attempts} CMD_NO_OP attempts" ) finally: - subscription.cancel() + # websocket-client's WebSocketApp.close() has a check-then-use race on + # self.sock: the reader thread can null it between the truthiness check + # and the close_frame read, raising AttributeError out of teardown. A + # teardown error would replace this test's real result (pass, or the + # AssertionError above) with a misleading traceback, so swallow it. + try: + subscription.cancel() + except Exception: # noqa: BLE001 - teardown must not mask the test result + logging.getLogger(__name__).warning( + "Ignoring error while cancelling YAMCS event subscription", + exc_info=True, + )