Spaces:
Running
Running
| """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) | |
| def tmp_registry(tmp_path: Path) -> Path: | |
| """Return a path for a temporary dataset registry file.""" | |
| return tmp_path / "dataset_registry.json" | |
| def tmp_dataset_root(tmp_path: Path) -> Path: | |
| """Return a temporary directory for datasets.""" | |
| root = tmp_path / "datasets" | |
| root.mkdir() | |
| return root | |
| 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 | |
| def client(marionette_app: tuple[TestClient, Marionette]) -> TestClient: | |
| """Convenience fixture: just the TestClient.""" | |
| return marionette_app[0] | |
| def marionette(marionette_app: tuple[TestClient, Marionette]) -> Marionette: | |
| """Convenience fixture: just the Marionette instance.""" | |
| return marionette_app[1] | |
| 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, | |
| } | |