Ensure that each integration test tests more than just a lack of errors

This commit is contained in:
Julian Stirling 2025-12-22 18:57:34 +00:00
parent 7e9637e496
commit 6ec60523cd

View file

@ -12,51 +12,47 @@ it has been moved as it artificaially inflated coverage.
""" """
import json import json
import os
import numpy as np import numpy as np
import piexif import piexif
import pytest import pytest
from httpx import HTTPStatusError
from PIL import Image from PIL import Image
from ..shared_utils.lt_test_utils import LabThingsTestEnv from ..shared_utils.lt_test_utils import LabThingsTestEnv
THIS_DIR = os.path.dirname(os.path.abspath(__file__))
REPO_ROOT = os.path.dirname(os.path.dirname(THIS_DIR))
SIM_CONFIG = os.path.join(REPO_ROOT, "ofm_config_simulation.json")
@pytest.fixture @pytest.fixture
def test_env(): def test_env():
"""Yield a server with a very basic configuration.""" """Yield a server with a very basic configuration."""
thing_conf = { with open(SIM_CONFIG, "r", encoding="utf-8") as f_obj:
"camera": { config_dict = json.load(f_obj)
"class": "openflexure_microscope_server.things.camera.simulation:SimulatedCamera", with LabThingsTestEnv(things=config_dict["things"]) as env:
"kwargs": {
"shape": (240, 320, 3),
"canvas_shape": (1000, 1500, 3),
"frame_interval": 0.01,
},
},
"stage": {
"class": "openflexure_microscope_server.things.stage.dummy:DummyStage",
"kwargs": {"step_time": 0.000001},
},
"autofocus": "openflexure_microscope_server.things.autofocus:AutofocusThing",
"camera_stage_mapping": "openflexure_microscope_server.things.camera_stage_mapping:CameraStageMapper",
}
with LabThingsTestEnv(things=thing_conf) as env:
yield env yield env
def test_autofocus(test_env): def test_autofocus(test_env):
"""Test Fast Autofocus can run doesn't raise an exception.""" """Test Fast Autofocus can run doesn't raise an exception."""
# Adjust the time for stage is 100 microseconds rather than 1 microsecond. stage = test_env.get_thing_client("stage")
test_env.get_thing_by_name("stage").step_time = 0.0001
autofocus = test_env.get_thing_client("autofocus") autofocus = test_env.get_thing_client("autofocus")
_ = autofocus.fast_autofocus() assert stage.position["z"] == 0
# Autofocus 5 times and check each ends within 500 steps
for i in range(5):
autofocus.fast_autofocus()
assert abs(stage.position["z"]) < 500, f"Autofocus failed on iteration {i}"
def test_grab_jpeg(test_env): def test_grab_jpeg(test_env):
"""Check that grab_jpeg returns a blob that can be opened.""" """Check that grab_jpeg returns a blob that can be opened."""
camera = test_env.get_thing_client("camera") camera = test_env.get_thing_client("camera")
blob = camera.grab_jpeg() blob = camera.grab_jpeg()
_image = Image.open(blob.open()) image = Image.open(blob.open())
assert image.size == (820, 616)
def test_capture_jpeg_metadata(test_env): def test_capture_jpeg_metadata(test_env):
@ -68,33 +64,68 @@ def test_capture_jpeg_metadata(test_env):
encoded_metadata = exif_dict["Exif"][piexif.ExifIFD.UserComment] encoded_metadata = exif_dict["Exif"][piexif.ExifIFD.UserComment]
metadata = json.loads(encoded_metadata) metadata = json.loads(encoded_metadata)
assert "position" in metadata["stage"] assert "position" in metadata["stage"]
assert metadata["stage"]["position"] == {"x": 0, "y": 0, "z": 0}
assert image.size == (820, 616)
def test_stage(test_env): def test_stage(test_env):
"""Test moving th stage forwards and backwards.""" """Test moving the stage forwards and backwards."""
stage = test_env.get_thing_client("stage") stage = test_env.get_thing_client("stage")
start = stage.position start = stage.position
move = {"x": 1, "y": 2, "z": 3} move = {"x": 1, "y": 2, "z": 3}
move_back = {"x": -1, "y": -2, "z": -3}
stage.move_relative(**move) stage.move_relative(**move)
pos = stage.position pos = stage.position
for s, m, p in zip(start.values(), move.values(), pos.values(), strict=True):
assert s + m == p assert start["x"] + move["x"] == pos["x"]
stage.move_relative(**{k: -v for k, v in move.items()}) assert start["y"] + move["y"] == pos["y"]
assert start["z"] + move["z"] == pos["z"]
# Move back
stage.move_relative(**move_back)
pos = stage.position pos = stage.position
for s, p in zip(start.values(), pos.values(), strict=True):
assert s == p # Check nack at start.
assert start["x"] == pos["x"]
assert start["y"] == pos["y"]
assert start["z"] == pos["z"]
def test_capture_array(test_env): def test_capture_array(test_env):
"""Capture array from simulation and check the size is as expected.""" """Capture array from simulation and check the size is as expected."""
camera = test_env.get_thing_client("camera") camera = test_env.get_thing_client("camera")
array = np.asarray(camera.capture_array()) array = np.asarray(camera.capture_array())
assert array.shape == (240, 320, 3) assert array.shape == (616, 820, 3)
def test_camera_stage_mapping_calibration(test_env): def test_camera_stage_mapping_calibration(test_env):
"""Check that camera stage mapping can run without an exception.""" """Check that camera stage mapping runs and returns the expected result."""
camera = test_env.get_thing_client("camera") camera = test_env.get_thing_client("camera")
# Remove camera settling time for speed.
camera.settling_time = 0 camera.settling_time = 0
camera_stage_mapping = test_env.get_thing_client("camera_stage_mapping") csm = test_env.get_thing_client("camera_stage_mapping")
camera_stage_mapping.calibrate_xy() stage = test_env.get_thing_client("stage")
# Check it starts uncalibrated
assert csm.calibration_required
assert csm.image_to_stage_displacement_matrix is None
assert csm.last_calibration is None
# And therefore that actions error
with pytest.raises(HTTPStatusError):
csm.move_in_image_coordinates(x=10)
# Calibrate
csm.calibrate_xy()
assert not csm.calibration_required
assert csm.image_to_stage_displacement_matrix is not None
assert isinstance(csm.last_calibration, dict)
# Check it returned home
assert stage.position == {"x": 0, "y": 0, "z": 0}
csm.move_in_image_coordinates(x=10, y=0)
assert stage.position == {"x": -5, "y": 0, "z": 0}
csm_matrix = csm.image_to_stage_displacement_matrix
expected_matrix = [[0, 0.5], [-0.5, 0]]
assert isinstance(csm_matrix, list)
# Check CSM is as expected to an absolute tolerance of 1e-3
assert np.allclose(csm_matrix, expected_matrix, atol=1e-3)