mirror of
https://github.com/harvard-edge/cs249r_book.git
synced 2026-05-07 18:18:42 -05:00
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.
73 lines
2.2 KiB
Python
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
|