From ed245ecf22d3f8d677a034cfe489bad6ebef05c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20C=C3=B4t=C3=A9?= Date: Tue, 7 Jul 2026 12:39:10 -0400 Subject: [PATCH] Fix flaky remote test on slow runners: quit mainloop when client finishes testRemote's _run_with_client ran the Tk mainloop for a fixed 1.5s window, then quit regardless of whether the client thread had finished. The only test whose client uses RemoteAppProxy (test_proxy_validates_against_signatures) makes three marshaled round-trips (connect -> remote_signatures -> add) instead of one, and on the slow ubuntu/py3.11 CI runner that consistently exceeded the window: the mainloop quit before add's task was serviced on the main thread, so the server-side future.result(timeout=30) timed out and the client got None. Quit as soon as the client thread finishes (polled on the main thread) instead of after a fixed delay; the queue keeps draining until the client is done, so a slow runner gets as long as it needs. The fixed time becomes a 5s safety net so a genuinely hung call cannot wedge the suite. Also makes the suite faster, since quick clients no longer wait out the full window. Co-Authored-By: Claude Opus 4.8 (1M context) --- mytk/tests/testRemote.py | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/mytk/tests/testRemote.py b/mytk/tests/testRemote.py index 99e0e88..995a8c8 100644 --- a/mytk/tests/testRemote.py +++ b/mytk/tests/testRemote.py @@ -66,11 +66,26 @@ def setUp(self): def tearDown(self): self.app.quit() - def _run_with_client(self, client_call, timeout=1500): + def _run_with_client(self, client_call, timeout=5000): # Client runs off the main thread; the Tk mainloop drains the queue. + # Quit as soon as the client thread finishes (polled on the main + # thread) rather than after a fixed delay, so a slow runner gets as + # long as it needs — the queue keeps draining until the client is done. + # `timeout` is only a safety net so a genuinely hung call cannot wedge + # the suite. thread = threading.Thread(target=client_call) - self.app.after(int(timeout / 4), thread.start) - self.app.after(timeout, self.app.quit) + + def check(remaining): + if not thread.is_alive() or remaining <= 0: + self.app.quit() + else: + self.app.after(25, lambda: check(remaining - 25)) + + def start(): + thread.start() + check(timeout) + + self.app.after(50, start) self.app.mainloop() thread.join(timeout=3) @@ -247,10 +262,23 @@ def setUp(self): def tearDown(self): self.app.quit() - def _run_with_client(self, client_call, timeout=1500): + def _run_with_client(self, client_call, timeout=5000): + # Quit as soon as the client thread finishes (polled on the main + # thread) instead of after a fixed delay; `timeout` is only a safety + # net. See TestRemoteServer._run_with_client for the rationale. thread = threading.Thread(target=client_call) - self.app.after(int(timeout / 4), thread.start) - self.app.after(timeout, self.app.quit) + + def check(remaining): + if not thread.is_alive() or remaining <= 0: + self.app.quit() + else: + self.app.after(25, lambda: check(remaining - 25)) + + def start(): + thread.start() + check(timeout) + + self.app.after(50, start) self.app.mainloop() thread.join(timeout=3)