Merge branch 'flash-led-sim' into 'v3'
Illumination Thing See merge request openflexure/openflexure-microscope-server!468
This commit is contained in:
commit
704fc5569f
8 changed files with 219 additions and 24 deletions
127
tests/unit_tests/test_illumination.py
Normal file
127
tests/unit_tests/test_illumination.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
"""Tests for the Illumination thing."""
|
||||
|
||||
from unittest.mock import call, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from labthings_fastapi.testing import create_thing_without_server
|
||||
|
||||
from openflexure_microscope_server.things.illumination import (
|
||||
Illumination,
|
||||
SangaIllumination,
|
||||
SimulatorIllumination,
|
||||
)
|
||||
|
||||
# Test the BaseClass before testing specific implementation
|
||||
|
||||
|
||||
def test_base_illumination_set_led_not_implemented():
|
||||
"""Check that calling set_led on the base Illumination raises NotImplementedError."""
|
||||
ill = create_thing_without_server(Illumination)
|
||||
with pytest.raises(
|
||||
NotImplementedError,
|
||||
match="Turning the LED on and off should be implemented by child classes.",
|
||||
):
|
||||
ill.set_led(led_on=True)
|
||||
|
||||
|
||||
def test_flash_calls_set_led_and_raises():
|
||||
"""Check that calling flash from Baseclass calls flash with right arguments."""
|
||||
ill = create_thing_without_server(Illumination)
|
||||
|
||||
number_of_flashes = 3 # smaller number for test speed
|
||||
dt = 0 # avoid sleeping
|
||||
|
||||
with pytest.raises(NotImplementedError):
|
||||
ill.flash(number_of_flashes=number_of_flashes, dt=dt)
|
||||
|
||||
# Patch set_led to just record calls without raising
|
||||
with patch.object(ill, "set_led") as mock_set_led:
|
||||
# Call flash normally
|
||||
ill.flash(number_of_flashes=number_of_flashes, dt=dt)
|
||||
|
||||
# Each flash calls set_led twice: False, True
|
||||
expected_sequence = [call(False), call(True)] * number_of_flashes
|
||||
assert mock_set_led.call_args_list == expected_sequence
|
||||
|
||||
# Also check total number of calls
|
||||
assert mock_set_led.call_count == number_of_flashes * 2
|
||||
|
||||
|
||||
def test_negative_flash_calls():
|
||||
"""Check that calling flash with negative number doesn't call set_led."""
|
||||
ill = create_thing_without_server(Illumination)
|
||||
|
||||
number_of_flashes = -33 # smaller number for test speed
|
||||
dt = 0 # avoid sleeping
|
||||
|
||||
# Patch set_led to just record calls without raising
|
||||
with patch.object(ill, "set_led") as mock_set_led:
|
||||
# Call flash normally
|
||||
ill.flash(number_of_flashes=number_of_flashes, dt=dt)
|
||||
|
||||
# Also check total number of calls
|
||||
assert mock_set_led.call_count == 0
|
||||
|
||||
|
||||
def test_negative_flash_dt():
|
||||
"""Check that calling flash with negative dt raises ValueError."""
|
||||
ill = create_thing_without_server(Illumination)
|
||||
|
||||
number_of_flashes = 3 # smaller number for test speed
|
||||
dt = -5 # set a negative delay
|
||||
|
||||
# Patch set_led to just record calls without raising
|
||||
with (
|
||||
patch.object(ill, "set_led"),
|
||||
pytest.raises(ValueError, match="sleep length must be non-negative"),
|
||||
):
|
||||
ill.flash(number_of_flashes=number_of_flashes, dt=dt)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sanga_mock_stage():
|
||||
"""Return a SangaIllumination with a mocked stage."""
|
||||
return create_thing_without_server(SangaIllumination, mock_all_slots=True)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sim_mock_cam():
|
||||
"""Return a SimulatorIllumination with a mocked camera."""
|
||||
return create_thing_without_server(SimulatorIllumination, mock_all_slots=True)
|
||||
|
||||
|
||||
def test_sanga_flash_calls_led(sanga_mock_stage):
|
||||
"""Test that flashing calls set_led with correct parameters."""
|
||||
number_of_flashes = 3
|
||||
dt = 0.01 # tiny sleep for test speed
|
||||
|
||||
sanga_mock_stage.flash(number_of_flashes=number_of_flashes, dt=dt)
|
||||
|
||||
# Each flash calls set_led False then True
|
||||
assert sanga_mock_stage._stage.set_led.call_count == 2 * number_of_flashes
|
||||
|
||||
# Check that False and True are alternating when sent to _stage.set_led
|
||||
calls = sanga_mock_stage._stage.set_led.call_args_list
|
||||
expected_sequence = [False, True] * number_of_flashes
|
||||
for led_call, expected in zip(calls, expected_sequence, strict=True):
|
||||
args, kwargs = led_call
|
||||
assert args[0] == expected
|
||||
|
||||
|
||||
def test_sim_flash_calls_led(sim_mock_cam):
|
||||
"""Test that simulator flash calls set_led correctly."""
|
||||
number_of_flashes = 2
|
||||
dt = 0.01
|
||||
|
||||
sim_mock_cam.flash(number_of_flashes=number_of_flashes, dt=dt)
|
||||
|
||||
# Each flash calls set_led False then True
|
||||
assert sim_mock_cam._cam.set_led.call_count == 2 * number_of_flashes
|
||||
|
||||
# Check that False and True are alternating when sent to _cam.set_led
|
||||
calls = sim_mock_cam._cam.set_led.call_args_list
|
||||
expected_sequence = [False, True] * number_of_flashes
|
||||
for led_call, expected in zip(calls, expected_sequence, strict=True):
|
||||
args, kwargs = led_call
|
||||
assert args[0] == expected
|
||||
|
|
@ -284,9 +284,6 @@ def test_thing_description_equivalence(dummy_stage, mocker):
|
|||
mocker.patch.dict("sys.modules", {"sangaboard": mock_sangaboard})
|
||||
from openflexure_microscope_server.things.stage.sangaboard import SangaboardThing
|
||||
|
||||
# Flash LED isn't a standard stage action, most stages do not control illumination.
|
||||
extra_sanga_actions = ["flash_led"]
|
||||
|
||||
base_td = create_thing_without_server(BaseStage).thing_description()
|
||||
base_actions = set(base_td.actions.keys())
|
||||
base_properties = set(base_td.properties.keys())
|
||||
|
|
@ -298,9 +295,6 @@ def test_thing_description_equivalence(dummy_stage, mocker):
|
|||
sanga_td = create_thing_without_server(SangaboardThing).thing_description()
|
||||
# Remove known extra actions
|
||||
sanga_actions = list(sanga_td.actions.keys())
|
||||
for action in extra_sanga_actions:
|
||||
index = sanga_actions.index(action)
|
||||
sanga_actions.pop(index)
|
||||
sanga_actions = set(sanga_actions)
|
||||
sanga_properties = set(sanga_td.properties.keys())
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue