"""Shared fixtures for the unit test suite. These tests exercise the self-play framework end-to-end with tiny, fast, seed-pinned tournaments so the suite stays deterministic and runs in seconds. """ from __future__ import annotations import json from pathlib import Path import pytest from fable_selfplay.tournament import TournamentConfig, run_tournament ROOT = Path(__file__).resolve().parents[2] @pytest.fixture(scope="session") def kernel_v01() -> Path: path = ROOT / "kernel" / "kernel-v0.1.yaml" assert path.exists(), f"missing kernel file: {path}" return path @pytest.fixture(scope="session") def kernel_v02() -> Path: path = ROOT / "kernel" / "kernel-v0.2.yaml" assert path.exists(), f"missing kernel file: {path}" return path @pytest.fixture(scope="session") def exploits_dir() -> Path: path = ROOT / "exploits" assert path.exists(), f"missing exploits directory: {path}" return path @pytest.fixture(scope="session") def exploit_records(exploits_dir: Path) -> list[dict]: records = [] for path in sorted(exploits_dir.glob("EXP-*.json")): with path.open("r", encoding="utf-8") as fh: records.append(json.load(fh)) assert records, "no exploit records found" return records def make_config(kernel_path: Path, roster: dict[str, int], *, seed: int = 7, episodes: int = 3, max_turns: int = 40, name: str = "unit") -> TournamentConfig: return TournamentConfig( name=name, kernel_path=str(kernel_path), episodes=episodes, seed=seed, roster=roster, max_turns=max_turns, ) @pytest.fixture(scope="session") def small_honest_result(kernel_v02: Path): """A tiny honest-only tournament on v0.2, shared across tests.""" config = make_config(kernel_v02, {"honest": 5}, seed=11, episodes=3, max_turns=40, name="unit-honest") return run_tournament(config) @pytest.fixture(scope="session") def small_adversarial_result(kernel_v01: Path): """A tiny adversarial tournament on v0.1, shared across tests.""" config = make_config(kernel_v01, {"honest": 5, "drainer": 2}, seed=13, episodes=3, max_turns=60, name="unit-adv") return run_tournament(config) def episode_dicts(result) -> list[dict]: """Normalize a tournament result's episodes into plain dicts.""" episodes = getattr(result, "episodes", None) if episodes is None: episodes = result.to_dict().get("episodes", []) out = [] for episode in episodes: out.append(episode.to_dict() if hasattr(episode, "to_dict") else dict(episode)) return out