Skip to content

Commit bf1080e

Browse files
committed
VisualizerCommandTests: await the dispatched event instead of a fixed sleep
These tests fired a resource action, waited a fixed `time.sleep(0.1)`, then asserted `send_command` was called. The sleep blocks the event loop and, under CI load, can elapse before the event is dispatched, so the test flakes. `set_well_volumes` also fans out into one `set_state` per well, so the last captured call must be the one carrying the well the assert reads. Poll the most recent call for the expected event (and payload key) via `await asyncio.sleep`, bounded by a generous timeout.
1 parent 10b8761 commit bf1080e

1 file changed

Lines changed: 22 additions & 3 deletions

File tree

pylabrobot/visualizer/visualizer_tests.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import asyncio
12
import json
23
import time
34
import unittest
45
import unittest.mock
56
import urllib.request
7+
from typing import Optional
68

79
import pytest
810
import websockets
@@ -212,11 +214,28 @@ async def asyncSetUp(self):
212214

213215
await self.vis.setup()
214216

217+
async def _wait_for_event(self, event: str, data_key: Optional[str] = None, timeout: float = 5.0):
218+
"""Wait until the most recent send_command call is ``event`` (optionally carrying
219+
``data_key`` in its data), yielding to the loop.
220+
221+
Replaces fixed ``time.sleep()`` waits: those block the event loop and race under CI
222+
load. Checking the last call - not just any call - matters when one action fans out
223+
into many events (e.g. set_well_volumes emits a set_state per well); the assert reads
224+
the last call, so we wait until that call carries the expected payload.
225+
"""
226+
deadline = time.monotonic() + timeout
227+
while time.monotonic() < deadline:
228+
last = self.vis.send_command.call_args
229+
if last is not None and last.kwargs.get("event") == event:
230+
if data_key is None or data_key in last.kwargs.get("data", {}):
231+
return
232+
await asyncio.sleep(0.01)
233+
215234
async def test_assign_child_resource(self):
216235
"""Test that the assign_child_resource method sends the correct event."""
217236
child = Resource(size_x=100, size_y=100, size_z=100, name="child")
218237
self.r.assign_child_resource(child, location=Coordinate(0, 0, 0))
219-
time.sleep(0.1) # wait for the event to be sent
238+
await self._wait_for_event("resource_assigned")
220239
self.vis.send_command.assert_called_once_with( # type: ignore[attr-defined]
221240
event="resource_assigned",
222241
data={
@@ -233,7 +252,7 @@ async def test_resource_unassigned(self):
233252
child = Resource(size_x=100, size_y=100, size_z=100, name="child")
234253
self.r.assign_child_resource(child, location=Coordinate(0, 0, 0))
235254
self.r.unassign_child_resource(child)
236-
time.sleep(0.1)
255+
await self._wait_for_event("resource_unassigned")
237256

238257
self.vis.send_command.assert_called_with( # type: ignore[attr-defined]
239258
event="resource_unassigned",
@@ -246,7 +265,7 @@ async def test_state_updated(self):
246265
plate = cor_96_wellplate_360uL_Fb(name="plate_01")
247266
self.r.assign_child_resource(plate, location=Coordinate(0, 0, 0))
248267
plate.set_well_volumes([500] * 96)
249-
time.sleep(0.1)
268+
await self._wait_for_event("set_state", data_key="plate_01_well_H12")
250269
self.vis.send_command.assert_called() # type: ignore[attr-defined]
251270
call_args = self.vis.send_command.call_args[1] # type: ignore[attr-defined]
252271
self.assertEqual(call_args["event"], "set_state")

0 commit comments

Comments
 (0)