bureaucat / tests /test_gallery.py
ravinsingh15's picture
Bureaucat — Build Small Hackathon submission (Qwen3-VL-8B, ZeroGPU, gr.Server)
6b5e47d
Raw
History Blame Contribute Delete
8.56 kB
"""
tests/test_gallery.py — Zero-GPU gallery loader tests (Plan 02-04, UI-01).
Verifies:
(a) load_example returns a 7-element tuple matching render_result's output arity
and does NOT raise.
(b) load_example is NOT wrapped by @spaces.GPU and never calls the model —
run_inference_multi is patched to raise; load_example must still succeed.
(c) data/gallery/*-result.json fixtures round-trip into StructuredResult correctly
(severity is int or None, all fields present).
BUREAUCAT_NO_MODEL=1 is set BEFORE import to prevent model weight loading.
"""
import json
import os
import sys
import types
import unittest
from pathlib import Path
from unittest.mock import patch
# ---- escape hatch: must be set BEFORE importing app ----
os.environ["BUREAUCAT_NO_MODEL"] = "1"
# Ensure project root is on sys.path when run from tests/ or project root
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if _REPO_ROOT not in sys.path:
sys.path.insert(0, _REPO_ROOT)
import app # noqa: E402 (after path setup)
from app import EXAMPLE_LETTERS, StructuredResult, load_example # noqa: E402
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
_GALLERY_DIR = Path(_REPO_ROOT) / "data" / "gallery"
_STRUCTURED_RESULT_FIELDS = frozenset(
StructuredResult.__dataclass_fields__.keys()
)
_RENDER_RESULT_ARITY = 7 # (panic_html, mascot_html, quip_md, tldr, why, actions, deadlines_html)
def _make_select_event(index: int):
"""Return a SimpleNamespace that looks like gr.SelectData(index=N)."""
return types.SimpleNamespace(index=index)
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
class TestExampleLettersList(unittest.TestCase):
"""EXAMPLE_LETTERS has >= 4 entries with required keys."""
def test_length(self):
self.assertGreaterEqual(len(EXAMPLE_LETTERS), 4, "need >= 4 gallery entries")
def test_keys(self):
for entry in EXAMPLE_LETTERS:
for key in ("slug", "label", "image", "cached"):
self.assertIn(key, entry, f"missing key '{key}' in entry {entry}")
def test_image_paths_under_public(self):
for entry in EXAMPLE_LETTERS:
self.assertIn(
"data/letters/public/",
entry["image"],
f"image path should be under data/letters/public/: {entry['image']}",
)
def test_cached_paths_under_gallery(self):
for entry in EXAMPLE_LETTERS:
self.assertIn(
"data/gallery/",
entry["cached"],
f"cached path should be under data/gallery/: {entry['cached']}",
)
class TestLoadExampleArity(unittest.TestCase):
"""load_example returns correct arity without raising."""
def setUp(self):
"""Skip if gallery JSONs don't exist yet (generated by Task 1)."""
json_path = Path(_REPO_ROOT) / EXAMPLE_LETTERS[0]["cached"]
if not json_path.exists():
self.skipTest(f"Gallery JSON not yet generated: {json_path}")
def test_returns_tuple_of_correct_arity(self):
"""load_example(index=0) returns a 7-element tuple."""
evt = _make_select_event(0)
result_tuple = load_example(evt)
self.assertIsInstance(result_tuple, tuple)
self.assertEqual(
len(result_tuple),
_RENDER_RESULT_ARITY,
f"expected {_RENDER_RESULT_ARITY}-element tuple, got {len(result_tuple)}",
)
def test_all_indices(self):
"""load_example works for every valid index in EXAMPLE_LETTERS."""
for i, entry in enumerate(EXAMPLE_LETTERS):
json_path = Path(_REPO_ROOT) / entry["cached"]
if not json_path.exists():
continue
with self.subTest(index=i, slug=entry["slug"]):
evt = _make_select_event(i)
tpl = load_example(evt)
self.assertEqual(len(tpl), _RENDER_RESULT_ARITY)
class TestLoadExampleNoModelCall(unittest.TestCase):
"""load_example never calls run_inference_multi — zero GPU guarantee."""
def setUp(self):
json_path = Path(_REPO_ROOT) / EXAMPLE_LETTERS[0]["cached"]
if not json_path.exists():
self.skipTest(f"Gallery JSON not yet generated: {json_path}")
def test_no_model_call_when_inference_patched_to_raise(self):
"""
Even if run_inference_multi would raise, load_example still succeeds.
This proves load_example reads only disk (never calls the model).
"""
def _boom(*args, **kwargs):
raise RuntimeError("load_example must NOT call run_inference_multi")
with patch.object(app, "run_inference_multi", side_effect=_boom):
evt = _make_select_event(0)
# Must NOT raise RuntimeError
result_tuple = load_example(evt)
self.assertEqual(len(result_tuple), _RENDER_RESULT_ARITY)
def test_load_example_not_spaces_gpu_wrapped(self):
"""
load_example must not be decorated with @spaces.GPU.
Presence of __wrapped__ or _spaces_fn_wrapped attributes indicates GPU decoration.
"""
self.assertFalse(
hasattr(load_example, "__wrapped__"),
"load_example should NOT be wrapped with @spaces.GPU (__wrapped__ found)",
)
self.assertFalse(
hasattr(load_example, "_spaces_fn_wrapped"),
"load_example should NOT be wrapped with @spaces.GPU (_spaces_fn_wrapped found)",
)
class TestGalleryJsonFixtures(unittest.TestCase):
"""data/gallery/*-result.json files round-trip into StructuredResult."""
def _iter_gallery_jsons(self):
"""Yield (stem, dict) for each existing gallery JSON."""
for entry in EXAMPLE_LETTERS:
json_path = Path(_REPO_ROOT) / entry["cached"]
if json_path.exists():
yield entry["slug"], json.loads(json_path.read_text(encoding="utf-8"))
def test_all_fields_present(self):
found_any = False
for slug, data in self._iter_gallery_jsons():
found_any = True
with self.subTest(slug=slug):
missing = _STRUCTURED_RESULT_FIELDS - set(data.keys())
self.assertEqual(
missing,
set(),
f"{slug}: missing StructuredResult fields: {missing}",
)
if not found_any:
self.skipTest("No gallery JSONs exist yet — run Task 1 first")
def test_severity_type(self):
found_any = False
for slug, data in self._iter_gallery_jsons():
found_any = True
with self.subTest(slug=slug):
sev = data.get("severity")
self.assertTrue(
sev is None or isinstance(sev, int),
f"{slug}: severity must be int or None, got {type(sev).__name__}",
)
if not found_any:
self.skipTest("No gallery JSONs exist yet — run Task 1 first")
def test_no_quip_prefix(self):
"""quip field must NOT contain 'Bureaucat says:' prefix (prevents double-prefix)."""
found_any = False
for slug, data in self._iter_gallery_jsons():
found_any = True
with self.subTest(slug=slug):
quip = data.get("quip", "")
self.assertNotIn(
"Bureaucat says:",
quip,
f"{slug}: quip contains 'Bureaucat says:' prefix — would double-prefix in UI",
)
if not found_any:
self.skipTest("No gallery JSONs exist yet — run Task 1 first")
def test_structured_result_roundtrip(self):
"""StructuredResult(**data) does not raise for any gallery JSON."""
found_any = False
for slug, data in self._iter_gallery_jsons():
found_any = True
with self.subTest(slug=slug):
# Should not raise
result = StructuredResult(**data)
self.assertIsInstance(result, StructuredResult)
self.assertIsNotNone(result.raw, f"{slug}: raw field is None")
if not found_any:
self.skipTest("No gallery JSONs exist yet — run Task 1 first")
if __name__ == "__main__":
unittest.main()