Files
cs249r_book/labs/tests/conftest.py
Vijay Janapa Reddi 25d200638a feat(labs): add Level 4 protocol invariant tests and integrate into CI
New test_protocol.py validates 6 protocol invariants from PROTOCOL.md:
- Invariant 1: constants sourced from mlsysim registries (not hardcoded)
- Invariant 4: multi-part tabbed structure (4-5 parts + synthesis)
- Invariant 5: multiple deployment contexts (2-3 hardware tiers)
- Zone structure (4 zones: opening, widgets, tabs, ledger)
- Ledger integration (ledger.save with correct chapter number)
- Pedagogical flow (predictions per part, mo.stop gates, stakeholder msgs)

Known gaps surface as xfail, not hard failures — provides a quality
dashboard without blocking CI while labs are brought up to protocol.
2026-03-22 08:48:27 -04:00

73 lines
2.2 KiB
Python

"""
Labs Test Configuration
=======================
Shared fixtures for testing Marimo lab notebooks.
Four test levels:
Level 1 (Static): AST parse, structure checks, import validation
Level 2 (Engine): Run cells headlessly via marimo.App.run(), check computations
Level 3 (Widget): Widget structure, prediction-reveal pattern
Level 4 (Protocol): Protocol invariant compliance (pedagogical quality gates)
Usage:
python3 -m pytest labs/tests/ -v # All levels
python3 -m pytest labs/tests/ -v -k "static" # Level 1 only (fast, CI)
python3 -m pytest labs/tests/ -v -k "engine" # Level 2 (medium, CI)
python3 -m pytest labs/tests/ -v -k "widget" # Level 3 (fast, CI)
python3 -m pytest labs/tests/ -v -k "protocol" # Level 4 (fast, CI)
"""
import ast
import glob
import importlib.util
import sys
from pathlib import Path
import pytest
# ---------------------------------------------------------------------------
# Discovery: collect all lab notebook paths
# ---------------------------------------------------------------------------
REPO_ROOT = Path(__file__).resolve().parents[2]
LABS_ROOT = REPO_ROOT / "labs"
VOL1_LABS = sorted(glob.glob(str(LABS_ROOT / "vol1" / "lab_*.py")))
VOL2_LABS = sorted(glob.glob(str(LABS_ROOT / "vol2" / "lab_*.py")))
ALL_LABS = VOL1_LABS + VOL2_LABS
def lab_id(path: str) -> str:
"""Extract a short ID like 'vol1/lab_01' from a full path."""
p = Path(path)
return f"{p.parent.name}/{p.stem}"
# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------
@pytest.fixture(params=ALL_LABS, ids=[lab_id(p) for p in ALL_LABS])
def lab_path(request):
"""Parametrized fixture yielding each lab file path."""
return request.param
@pytest.fixture(params=VOL1_LABS, ids=[lab_id(p) for p in VOL1_LABS])
def vol1_lab_path(request):
return request.param
@pytest.fixture(params=VOL2_LABS, ids=[lab_id(p) for p in VOL2_LABS])
def vol2_lab_path(request):
return request.param
@pytest.fixture(scope="session")
def mlsysim():
"""Import mlsysim once for the session."""
sys.path.insert(0, str(REPO_ROOT))
import mlsysim
return mlsysim