From 90dbb35a9e7b34fdd916d34694cad2c977a0869c Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Fri, 8 Aug 2025 09:45:20 +0100 Subject: [PATCH] Directly call DummyStage instance during movement test for speed --- tests/mock_things/mock_cancel.py | 19 ++++++++++++++++ tests/test_stage.py | 39 ++++++++++++++++++++++---------- 2 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 tests/mock_things/mock_cancel.py diff --git a/tests/mock_things/mock_cancel.py b/tests/mock_things/mock_cancel.py new file mode 100644 index 00000000..fd380894 --- /dev/null +++ b/tests/mock_things/mock_cancel.py @@ -0,0 +1,19 @@ +"""Create a fake CancelHook used instead of a dependency, it cannot be used to cancel! + +Certain actions require a CancelHook to be supplied if called directly. The only method +we use from the LabThings CancelHook is ``sleep()``. The CanceHook ``sleep()`` works +exactly like ``time.sleep()`` except with raise an ``InvocationCancelledError`` if the +server receives a cancellation request. + +This is just an object with a sleep method. +""" + +import time + + +class MockCancel: + """A class to use when directly calling an action with CancelHook dependency.""" + + def sleep(self, time_in_seconds: float): + """Sleep for the input number of seconds.""" + time.sleep(time_in_seconds) diff --git a/tests/test_stage.py b/tests/test_stage.py index be4e662f..54b5c107 100644 --- a/tests/test_stage.py +++ b/tests/test_stage.py @@ -18,6 +18,9 @@ from openflexure_microscope_server.things.stage import ( ) from openflexure_microscope_server.things.stage.dummy import DummyStage + +from .mock_things.mock_cancel import MockCancel + # Keep the size and number of moves fairly small or the tests can take forever point3d = st.tuples( st.integers(min_value=-100, max_value=100), @@ -191,23 +194,26 @@ def test_direction_inversion(stage_client, dummy_stage): assert excinfo.value.response.status_code == 422 -def _test_move_relative(stage_client, dummy_stage, axis_inverted, path): +def _test_move_relative(dummy_stage, axis_inverted, path): """Test moving relative, ensuring position and hardware position behave as expected. :param axis_inverted: Is used to set the inversion. :param path: The 3d path to move over, generated by hypothesis. """ + cancel = MockCancel() _set_axis_direction(dummy_stage, axis_inverted) # Explicitly do axes calculation here to check logic in main code. x_dir = -1 if axis_inverted["x"] else 1 y_dir = -1 if axis_inverted["y"] else 1 z_dir = -1 if axis_inverted["z"] else 1 - position = list(stage_client.position.values()) + position = list(dummy_stage.position.values()) for movement in path: - stage_client.move_relative(x=movement[0], y=movement[1], z=movement[2]) + dummy_stage.move_relative( + cancel=cancel, x=movement[0], y=movement[1], z=movement[2] + ) position = [pos + move for pos, move in zip(position, movement)] - stage_pos = stage_client.get_xyz_position() + stage_pos = dummy_stage.get_xyz_position() hw_pos = dummy_stage._hardware_position assert position[0] == stage_pos[0] == hw_pos["x"] * x_dir assert position[1] == stage_pos[1] == hw_pos["y"] * y_dir @@ -226,6 +232,11 @@ def test_move_relative(stage_client, dummy_stage, path): 3 paths are tried for each case of axis inversion. This checks both that the reported position changes as is input in path, and that the hardware position is inverted when appropriate. + + NOTE: it is essential that `stage_client` is imported even if any time it is used + dummy client could be used. This is because the fixture that creates stage client + handles adding the dummy_stage to a server. It needs to have been added to a + server for it not to throw errors about not being connected to a server. """ # Note that the fixture is not reset. This is fine, because the stage_should work # no matter the starting position. @@ -239,30 +250,31 @@ def test_move_relative(stage_client, dummy_stage, path): print(path) for axis_inverted in inversion_combinations: _test_move_relative( - stage_client=stage_client, dummy_stage=dummy_stage, axis_inverted=axis_inverted, path=path, ) -def _test_move_absolute(stage_client, dummy_stage, axis_inverted, path): +def _test_move_absolute(dummy_stage, axis_inverted, path): """Test moving relative, ensuring position and hardware position behave as expected. :param axis_inverted: Is used to set the inversion. :param path: The 3d path to move over, generated by hypothesis. """ + cancel = MockCancel() _set_axis_direction(dummy_stage, axis_inverted) # Explicitly do axes calculation here to check logic in main code. x_dir = -1 if axis_inverted["x"] else 1 y_dir = -1 if axis_inverted["y"] else 1 z_dir = -1 if axis_inverted["z"] else 1 - - position = list(stage_client.position.values()) + position = list(dummy_stage.position.values()) for move_to in path: - stage_client.move_absolute(x=move_to[0], y=move_to[1], z=move_to[2]) + dummy_stage.move_absolute( + cancel=cancel, x=move_to[0], y=move_to[1], z=move_to[2] + ) position = move_to - stage_pos = stage_client.get_xyz_position() + stage_pos = dummy_stage.get_xyz_position() hw_pos = dummy_stage._hardware_position assert position[0] == stage_pos[0] == hw_pos["x"] * x_dir assert position[1] == stage_pos[1] == hw_pos["y"] * y_dir @@ -281,6 +293,11 @@ def test_move_absolute(stage_client, dummy_stage, path): 3 paths are tried for each case of axis inversion. This checks both that the reported position changes as is input in path, and that the hardware position is inverted when appropriate. + + NOTE: it is essential that `stage_client` is imported even if any time it is used + dummy client could be used. This is because the fixture that creates stage client + handles adding the dummy_stage to a server. It needs to have been added to a + server for it not to throw errors about not being connected to a server. """ # Note that the fixture is not reset. This is fine, because the stage_should work # no matter the starting position. @@ -291,10 +308,8 @@ def test_move_absolute(stage_client, dummy_stage, path): dict(zip(axis_names, inverted)) for inverted in itertools.product([True, False], repeat=len(axis_names)) ] - print(path) for axis_inverted in inversion_combinations: _test_move_absolute( - stage_client=stage_client, dummy_stage=dummy_stage, axis_inverted=axis_inverted, path=path,