Merge branch 'faster-stage-tests' into 'v3'
Speed up `tests/test_stage.py` significantly See merge request openflexure/openflexure-microscope-server!353
This commit is contained in:
commit
c9cb0c65e1
2 changed files with 46 additions and 12 deletions
19
tests/mock_things/mock_cancel.py
Normal file
19
tests/mock_things/mock_cancel.py
Normal file
|
|
@ -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) -> None:
|
||||||
|
"""Sleep for the input number of seconds."""
|
||||||
|
time.sleep(time_in_seconds)
|
||||||
|
|
@ -18,6 +18,9 @@ from openflexure_microscope_server.things.stage import (
|
||||||
)
|
)
|
||||||
from openflexure_microscope_server.things.stage.dummy import DummyStage
|
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
|
# Keep the size and number of moves fairly small or the tests can take forever
|
||||||
point3d = st.tuples(
|
point3d = st.tuples(
|
||||||
st.integers(min_value=-100, max_value=100),
|
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
|
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.
|
"""Test moving relative, ensuring position and hardware position behave as expected.
|
||||||
|
|
||||||
:param axis_inverted: Is used to set the inversion.
|
:param axis_inverted: Is used to set the inversion.
|
||||||
:param path: The 3d path to move over, generated by hypothesis.
|
:param path: The 3d path to move over, generated by hypothesis.
|
||||||
"""
|
"""
|
||||||
|
cancel = MockCancel()
|
||||||
_set_axis_direction(dummy_stage, axis_inverted)
|
_set_axis_direction(dummy_stage, axis_inverted)
|
||||||
# Explicitly do axes calculation here to check logic in main code.
|
# Explicitly do axes calculation here to check logic in main code.
|
||||||
x_dir = -1 if axis_inverted["x"] else 1
|
x_dir = -1 if axis_inverted["x"] else 1
|
||||||
y_dir = -1 if axis_inverted["y"] else 1
|
y_dir = -1 if axis_inverted["y"] else 1
|
||||||
z_dir = -1 if axis_inverted["z"] 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:
|
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)]
|
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
|
hw_pos = dummy_stage._hardware_position
|
||||||
assert position[0] == stage_pos[0] == hw_pos["x"] * x_dir
|
assert position[0] == stage_pos[0] == hw_pos["x"] * x_dir
|
||||||
assert position[1] == stage_pos[1] == hw_pos["y"] * y_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
|
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
|
reported position changes as is input in path, and that the hardware position is
|
||||||
inverted when appropriate.
|
inverted when appropriate.
|
||||||
|
|
||||||
|
NOTE: it is essential that `stage_client` is imported even if any time it is used
|
||||||
|
dummy stage 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
|
# Note that the fixture is not reset. This is fine, because the stage_should work
|
||||||
# no matter the starting position.
|
# no matter the starting position.
|
||||||
|
|
@ -239,30 +250,31 @@ def test_move_relative(stage_client, dummy_stage, path):
|
||||||
print(path)
|
print(path)
|
||||||
for axis_inverted in inversion_combinations:
|
for axis_inverted in inversion_combinations:
|
||||||
_test_move_relative(
|
_test_move_relative(
|
||||||
stage_client=stage_client,
|
|
||||||
dummy_stage=dummy_stage,
|
dummy_stage=dummy_stage,
|
||||||
axis_inverted=axis_inverted,
|
axis_inverted=axis_inverted,
|
||||||
path=path,
|
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.
|
"""Test moving relative, ensuring position and hardware position behave as expected.
|
||||||
|
|
||||||
:param axis_inverted: Is used to set the inversion.
|
:param axis_inverted: Is used to set the inversion.
|
||||||
:param path: The 3d path to move over, generated by hypothesis.
|
:param path: The 3d path to move over, generated by hypothesis.
|
||||||
"""
|
"""
|
||||||
|
cancel = MockCancel()
|
||||||
_set_axis_direction(dummy_stage, axis_inverted)
|
_set_axis_direction(dummy_stage, axis_inverted)
|
||||||
# Explicitly do axes calculation here to check logic in main code.
|
# Explicitly do axes calculation here to check logic in main code.
|
||||||
x_dir = -1 if axis_inverted["x"] else 1
|
x_dir = -1 if axis_inverted["x"] else 1
|
||||||
y_dir = -1 if axis_inverted["y"] else 1
|
y_dir = -1 if axis_inverted["y"] else 1
|
||||||
z_dir = -1 if axis_inverted["z"] else 1
|
z_dir = -1 if axis_inverted["z"] else 1
|
||||||
|
position = list(dummy_stage.position.values())
|
||||||
position = list(stage_client.position.values())
|
|
||||||
for move_to in path:
|
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
|
position = move_to
|
||||||
stage_pos = stage_client.get_xyz_position()
|
stage_pos = dummy_stage.get_xyz_position()
|
||||||
hw_pos = dummy_stage._hardware_position
|
hw_pos = dummy_stage._hardware_position
|
||||||
assert position[0] == stage_pos[0] == hw_pos["x"] * x_dir
|
assert position[0] == stage_pos[0] == hw_pos["x"] * x_dir
|
||||||
assert position[1] == stage_pos[1] == hw_pos["y"] * y_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
|
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
|
reported position changes as is input in path, and that the hardware position is
|
||||||
inverted when appropriate.
|
inverted when appropriate.
|
||||||
|
|
||||||
|
NOTE: it is essential that `stage_client` is imported even if any time it is used
|
||||||
|
dummy stage 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
|
# Note that the fixture is not reset. This is fine, because the stage_should work
|
||||||
# no matter the starting position.
|
# no matter the starting position.
|
||||||
|
|
@ -291,10 +308,8 @@ def test_move_absolute(stage_client, dummy_stage, path):
|
||||||
dict(zip(axis_names, inverted))
|
dict(zip(axis_names, inverted))
|
||||||
for inverted in itertools.product([True, False], repeat=len(axis_names))
|
for inverted in itertools.product([True, False], repeat=len(axis_names))
|
||||||
]
|
]
|
||||||
print(path)
|
|
||||||
for axis_inverted in inversion_combinations:
|
for axis_inverted in inversion_combinations:
|
||||||
_test_move_absolute(
|
_test_move_absolute(
|
||||||
stage_client=stage_client,
|
|
||||||
dummy_stage=dummy_stage,
|
dummy_stage=dummy_stage,
|
||||||
axis_inverted=axis_inverted,
|
axis_inverted=axis_inverted,
|
||||||
path=path,
|
path=path,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue