**Final report — phase 07 final verification pass** - **Verified:** all 5 tasks present in `complete/`; app boots (`/healthz` 200, `/` 200, static + atlas + `/api/levels` served); `docs/CLIENT.md` module map matches the on-disk `app/static/` tree; no code defects found. - **Tests/lint/coverage:** `bash .agents/validate.sh` → ruff clean, pyright 0 errors, `undercooked-levels validate` pass, **full pytest 1838 passed, 97.21% coverage (floor 90%)**, slow sim stage 2 passed, repo hygiene clean. `uv run pytest tests/e2e -q` → **53 passed** (all 8 suites added this phase). - **Criteria:** (1) two tabs create→join→ready→start→serve one dish, keyboard only — green via `test_us01/02/03/04` ✔; (2) e2e green ✔; (3) canvas non-blank, pixels change after a move — `test_us04_render_smoke` ✔; (4) HUD score within 200 ms of the `served` event — asserted, green ✔; (5) zero console errors + zero off-origin requests — `test_atlas_loads` / `test_us01_landing_surface` ✔; (6) `docs/CLIENT.md` matches layout ✔; (7) 100% suite + 97.21% ✔; (8) docs pass below ✔; (9) no regression in completed phases (all prior suites green) ✔. - **README.md fixed:** status still said "next pending phase is 07" → now 07 complete, 08 pending; the phase-07 client text buried in the phase-05 bullet → its own "canvas client, art, HUD (phase 07)" bullet; stale "What does not exist yet" (claimed movement/controls missing) → rewritten to phases 08–14; "once the room flow ships" → "live in the browser today"; Layout `app/static/` "renderer arrives with the rest of phase 07" → full shipped-client map; Layout `docs/` "CLIENT lands with phase 07" → `CLIENT.md` listed; added `gen_art_index.py` Layout entry. - **AGENTS.md fixed:** "the eight screens" (7 listed) → "the seven screens" (+ `dom.js` is the shared builder, not a screen); e2e naming line → notes the non-story guard `test_atlas_loads.py`. - **Decisions:** no code changes were needed; only the two doc fixes above. Docs re-verified by `test_docs_commands`/`test_docs_index` (44 passed) plus the final full gate. - **Next pending phase:** `08_client_prediction_and_touch`.
105 lines
3.8 KiB
Python
105 lines
3.8 KiB
Python
"""Phase-01 task-01 harness smoke tests: package metadata and pytest wiring."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
import undercooked
|
|
|
|
pytestmark = pytest.mark.unit
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[2]
|
|
HARNESS_MARKERS = ("unit", "integration", "e2e", "slow")
|
|
TEST_SUBDIRS = ("unit", "integration", "sim", "e2e")
|
|
|
|
#: The technical e2e suites that a phase file names but that belong to no
|
|
#: single user story (e.g. the phase-07 art/atlas guard). Every entry is a
|
|
#: deliberate, named exception to the story-naming contract in
|
|
#: `tests/e2e/README.md`; a new technical suite lands here first.
|
|
E2E_TECHNICAL_SUITES = frozenset({"test_atlas_loads.py"})
|
|
|
|
|
|
def test_package_metadata() -> None:
|
|
assert undercooked.__version__ == "0.1.0"
|
|
assert undercooked.__all__ == []
|
|
|
|
|
|
def test_harness_markers_registered(pytestconfig: pytest.Config) -> None:
|
|
registered = {line.split(":", 1)[0].strip() for line in pytestconfig.getini("markers")}
|
|
assert set(HARNESS_MARKERS) <= registered
|
|
|
|
|
|
def test_strict_markers_enabled(pytestconfig: pytest.Config) -> None:
|
|
assert "--strict-markers" in pytestconfig.getini("addopts")
|
|
|
|
|
|
@pytest.mark.parametrize("name", TEST_SUBDIRS)
|
|
def test_test_tree_directories_exist_with_readme(name: str) -> None:
|
|
directory = REPO_ROOT / "tests" / name
|
|
assert directory.is_dir()
|
|
readme = directory / "README.md"
|
|
assert readme.is_file()
|
|
assert readme.read_text(encoding="utf-8").strip()
|
|
|
|
|
|
def test_tests_dir_is_not_a_package() -> None:
|
|
assert not (REPO_ROOT / "tests" / "__init__.py").exists()
|
|
|
|
|
|
def test_fixture_levels_dir_exists(isolated_levels_dir: None) -> None:
|
|
levels_dir = Path(os.environ["UNDERCOOKED_LEVELS_DIR"])
|
|
assert levels_dir == REPO_ROOT / "tests" / "fixtures" / "levels"
|
|
assert levels_dir.is_dir()
|
|
# Hermeticity is about *levels*: a default test must never see the shipped
|
|
# campaign. The committed `recipes.yaml` is not one (the loader never treats
|
|
# it as a level) — startup has required a recipe book since phase 02 task 04,
|
|
# and this minimal one is what lets fixture-isolated apps boot at all.
|
|
levels = [p.name for p in levels_dir.glob("*.yaml") if p.name != "recipes.yaml"]
|
|
assert not levels
|
|
|
|
|
|
def test_e2e_tests_follow_the_story_naming_convention() -> None:
|
|
"""`pytest -m e2e --collect-only` selects story suites (`test_usNN_*.py`) and
|
|
the allowlisted technical suites only.
|
|
|
|
The durable invariant is the naming contract in `tests/e2e/README.md`: a
|
|
story suite that is not named for its story is one that nobody can find,
|
|
and a suite that cannot be collected is one that never runs. Technical
|
|
suites (guards for a phase's machinery, named by the phase files) are
|
|
the documented exception, tracked in `E2E_TECHNICAL_SUITES`.
|
|
"""
|
|
result = subprocess.run( # fixed argv, repo's own interpreter
|
|
[
|
|
sys.executable,
|
|
"-m",
|
|
"pytest",
|
|
"-m",
|
|
"e2e",
|
|
"--collect-only",
|
|
"-q",
|
|
"-o",
|
|
"addopts=", # drop the outer coverage flags: no nested .coverage writes
|
|
"-p",
|
|
"no:cacheprovider",
|
|
],
|
|
cwd=REPO_ROOT,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
timeout=120,
|
|
)
|
|
assert result.returncode == pytest.ExitCode.OK, result.stdout + result.stderr
|
|
assert "error" not in result.stderr.lower(), result.stderr
|
|
files = sorted({line.split("::")[0] for line in result.stdout.splitlines() if "::" in line})
|
|
assert files, f"the e2e marker selected nothing:\n{result.stdout}"
|
|
for path in files:
|
|
assert path.startswith("tests/e2e/"), path
|
|
name = Path(path).name
|
|
assert re.fullmatch(r"test_us\d{2}_.+\.py", name) or name in E2E_TECHNICAL_SUITES, path
|