marionette / tests /conftest.py
RemiFabre
Expand test suite to ~170 tests across 3 tiers
7659b58
Raw
History Blame
2.92 kB
"""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,
}