Further improve picamera tests to reuse fixtures and context managers.

This commit is contained in:
Julian Stirling 2025-10-28 21:35:20 +00:00
parent 995a464ba2
commit 2010e8eeb2
4 changed files with 51 additions and 67 deletions

View file

@ -2,35 +2,40 @@
from typing import Optional from typing import Optional
import tempfile import tempfile
from contextlib import contextmanager from contextlib import contextmanager
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from labthings_fastapi.server import ThingServer import labthings_fastapi as lt
from labthings_fastapi.client import ThingClient
from openflexure_microscope_server.things.camera.picamera import StreamingPiCamera2 from openflexure_microscope_server.things.camera.picamera import StreamingPiCamera2
@contextmanager @contextmanager
def camera_test_client(settings_folder: Optional[str] = None): def camera_test_client(
cam: Optional[StreamingPiCamera2] = None, settings_folder: Optional[str] = None
):
"""Yield a camera ThingClient on a camera server. """Yield a camera ThingClient on a camera server.
This is a context manager not a pytest fixture as it needs to be created This is a context manager not a pytest fixture as it needs to be created
multiple times in some tests. multiple times in some tests.
:param cam: The camera Thing to be used. If not supplied a new one will be created.
:param settings_folder: The settings folder for the camera, if none is supplies new
temporary directory will be used as the settings folder.
""" """
if cam is None:
cam = StreamingPiCamera2()
# Create a temp dir, if the setting folder is set it isn't really needed # Create a temp dir, if the setting folder is set it isn't really needed
# but doesn't add much overhead. # but doesn't add much overhead.
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
if settings_folder is None: if settings_folder is None:
settings_folder = tmpdir settings_folder = tmpdir
cam = StreamingPiCamera2() server = lt.ThingServer(settings_folder=settings_folder)
server = ThingServer(settings_folder=settings_folder)
server.add_thing(cam, "/camera/") server.add_thing(cam, "/camera/")
with TestClient(server.app) as test_client: with TestClient(server.app) as test_client:
client = ThingClient.from_url("/camera/", client=test_client) client = lt.ThingClient.from_url("/camera/", client=test_client)
yield client yield client
del server del server
del cam del cam

View file

@ -0,0 +1,33 @@
"""Shared fixtures for picamera tests."""
import pytest
import labthings_fastapi as lt
from openflexure_microscope_server.things.camera.picamera import StreamingPiCamera2
from .cam_test_utils import camera_test_client
@pytest.fixture
def picamera_thing() -> StreamingPiCamera2:
"""Return a StreamingPiCamera2 Thing.
This is the Thing that the fixture picamera_client uses. It can be used to probe
the Thing directly to check actions had the expected response.
"""
return StreamingPiCamera2()
@pytest.fixture
def picamera_client(picamera_thing) -> lt.ThingClient:
"""Initialise a test picamera_client for the StreamingPiCamera2 Thing.
This fixture:
* Sets up a ThingServer,
* Registers a StreamingPiCamera2 instance at the "/camera/" endpoint
* Provides a ThingClient for interacting with it during tests.
"""
with camera_test_client(cam=picamera_thing) as picamera_client:
yield picamera_client

View file

@ -1,52 +1,29 @@
"""Test data collection from the Raspberry Picamera.""" """Test data collection from the Raspberry Picamera."""
from fastapi.testclient import TestClient
from PIL import Image from PIL import Image
import numpy as np import numpy as np
import pytest
import labthings_fastapi as lt
from openflexure_microscope_server.things.camera.picamera import StreamingPiCamera2
@pytest.fixture(scope="module") def test_jpeg_and_array(picamera_client):
def client() -> lt.ThingClient:
"""Initialise a test client for the StreamingPiCamera2 Thing.
This fixture:
* Sets up a ThingServer,
* Registers a StreamingPiCamera2 instance at the "/camera/" endpoint
* Provides a ThingClient for interacting with it during tests.
"""
server = lt.ThingServer()
server.add_thing(StreamingPiCamera2(), "/camera/")
with TestClient(server.app) as test_client:
client = lt.ThingClient.from_url("/camera/", client=test_client)
yield client
def test_jpeg_and_array(client):
"""Check that a jpeg grabbed from the stream is the same size as other captures. """Check that a jpeg grabbed from the stream is the same size as other captures.
Compare it to an array capture and a jpeg capture. Compare it to an array capture and a jpeg capture.
""" """
# Grab a jpeg from the stream # Grab a jpeg from the stream
blob = client.grab_jpeg() blob = picamera_client.grab_jpeg()
mjpeg_frame = Image.open(blob.open()) mjpeg_frame = Image.open(blob.open())
# Verify throws an error if there are issues with the image # Verify throws an error if there are issues with the image
mjpeg_frame.verify() mjpeg_frame.verify()
assert mjpeg_frame.format == "JPEG" assert mjpeg_frame.format == "JPEG"
# Capture a jpeg # Capture a jpeg
blob = client.capture_jpeg(stream_name="main") blob = picamera_client.capture_jpeg(stream_name="main")
jpeg_capture = Image.open(blob.open()) jpeg_capture = Image.open(blob.open())
jpeg_capture.verify() jpeg_capture.verify()
assert jpeg_capture.format == "JPEG" assert jpeg_capture.format == "JPEG"
# Capture an array # Capture an array
arrlist = client.capture_array(stream_name="main") arrlist = picamera_client.capture_array(stream_name="main")
array_main = np.array(arrlist) array_main = np.array(arrlist)
# Verify image sizes are the same # Verify image sizes are the same

View file

@ -1,47 +1,16 @@
"""Test data collection from the Raspberry Picamera.""" """Test data collection from the Raspberry Picamera."""
import tempfile
from copy import deepcopy from copy import deepcopy
from fastapi.testclient import TestClient
import pytest import pytest
import labthings_fastapi as lt import labthings_fastapi as lt
from openflexure_microscope_server.things.camera.picamera import ( from openflexure_microscope_server.things.camera.picamera import (
StreamingPiCamera2,
MissingCalibrationError, MissingCalibrationError,
) )
@pytest.fixture
def picamera_thing() -> StreamingPiCamera2:
"""Return a StreamingPiCamera2 Thing.
This is the Thing that the fixture client uses. It can be used to probe the
Thing directly to check actions had the expected response.
"""
return StreamingPiCamera2()
@pytest.fixture
def client(picamera_thing) -> lt.ThingClient:
"""Initialise a test client for the StreamingPiCamera2 Thing.
This fixture:
* Sets up a ThingServer,
* Registers a StreamingPiCamera2 instance at the "/camera/" endpoint
* Provides a ThingClient for interacting with it during tests.
"""
temp_folder = tempfile.TemporaryDirectory()
server = lt.ThingServer(settings_folder=temp_folder.name)
server.add_thing(picamera_thing, "/camera/")
with TestClient(server.app) as test_client:
client = lt.ThingClient.from_url("/camera/", client=test_client)
yield client
def test_get_tuning_algo(picamera_thing): def test_get_tuning_algo(picamera_thing):
"""Test that get_tuning algorithm retrieves tuning algorithms.""" """Test that get_tuning algorithm retrieves tuning algorithms."""
# Missing algorithm raises an error. # Missing algorithm raises an error.
@ -73,7 +42,7 @@ def test_get_tuning_algo(picamera_thing):
assert picamera_thing.get_tuning_algo("rpi.geq", raise_if_missing=False) is None assert picamera_thing.get_tuning_algo("rpi.geq", raise_if_missing=False) is None
def test_calibration(picamera_thing, client): def test_calibration(picamera_thing, picamera_client):
"""Check that full auto calibrate completes and set the expected values.""" """Check that full auto calibrate completes and set the expected values."""
# Check the calibration_required property used by the calibration wizard # Check the calibration_required property used by the calibration wizard
assert picamera_thing.calibration_required assert picamera_thing.calibration_required
@ -88,7 +57,7 @@ def test_calibration(picamera_thing, client):
assert not picamera_thing.background_detector_status.ready assert not picamera_thing.background_detector_status.ready
# Run full auto calibrate # Run full auto calibrate
client.full_auto_calibrate() picamera_client.full_auto_calibrate()
# After calibration it should report that calibration is no longer required # After calibration it should report that calibration is no longer required
assert not picamera_thing.calibration_required assert not picamera_thing.calibration_required