marionette / tests /conftest.py
RemiFabre
Expand test suite to ~170 tests across 3 tiers
7659b58
"""Shared fixtures for Marionette tests."""
import io
import json
import struct
import wave
from pathlib import Path
import pytest
from fastapi.testclient import TestClient
from marionette.main import Marionette, create_app
def make_wav_bytes(duration: float = 1.0, sample_rate: int = 44100) -> bytes:
"""Build a valid WAV file in memory using only stdlib.
Returns raw bytes suitable for uploading via TestClient.
"""
n_frames = int(duration * sample_rate)
buf = io.BytesIO()
with wave.open(buf, "wb") as wf:
wf.setnchannels(1)
wf.setsampwidth(2) # 16-bit
wf.setframerate(sample_rate)
# Write silence (all zeros)
wf.writeframes(struct.pack(f"<{n_frames}h", *([0] * n_frames)))
return buf.getvalue()
def pytest_collection_modifyitems(config, items):
"""Auto-skip @pytest.mark.hardware tests unless -m hardware is given."""
marker_expr = config.getoption("-m", default="")
if "hardware" in marker_expr:
return
skip_hw = pytest.mark.skip(reason="hardware tests require -m hardware")
for item in items:
if "hardware" in item.keywords:
item.add_marker(skip_hw)
@pytest.fixture()
def tmp_registry(tmp_path: Path) -> Path:
"""Return a path for a temporary dataset registry file."""
return tmp_path / "dataset_registry.json"
@pytest.fixture()
def tmp_dataset_root(tmp_path: Path) -> Path:
"""Return a temporary directory for datasets."""
root = tmp_path / "datasets"
root.mkdir()
return root
@pytest.fixture()
def marionette_app(tmp_registry: Path, tmp_dataset_root: Path) -> tuple[TestClient, Marionette]:
"""Create a Marionette app with TestClient, using isolated temp paths."""
app, m = create_app(registry_path=tmp_registry, dataset_root=tmp_dataset_root)
client = TestClient(app)
return client, m
@pytest.fixture()
def client(marionette_app: tuple[TestClient, Marionette]) -> TestClient:
"""Convenience fixture: just the TestClient."""
return marionette_app[0]
@pytest.fixture()
def marionette(marionette_app: tuple[TestClient, Marionette]) -> Marionette:
"""Convenience fixture: just the Marionette instance."""
return marionette_app[1]
@pytest.fixture()
def sample_move_json() -> dict:
"""A minimal valid move JSON structure matching RecordedMove expectations."""
timestamps = [i * 0.01 for i in range(500)] # 5 seconds at 100Hz
frames = []
for _ in timestamps:
frames.append({
"head": [
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
],
"antennas": [0.0, 0.0],
"body_yaw": 0.0,
"check_collision": False,
})
return {
"description": "test move",
"time": timestamps,
"set_target_data": frames,
}