Files
C.E.L_Slide_test2/tests/test_phase_z2_cli_reuse_from.py
T
KyeongminandClaude Opus 4.8 b836e79ee1 wip: phase_z2 evidence 파이프라인 + matching 실험(phase2~26) + 프론트 trace 패널 진행분 스냅샷
- src: phase_z2 composition/mapper/pipeline/placement_planner/retry, ai_fallback(prompts/schema/validate), mdx_text_atoms 신규
- Front: PipelineTracePanel 신규, FramePanel/SlideCanvas/Home/designAgentApi 등 갱신 + 테스트 4종 추가
- templates/phase_z2: catalog(component_expansion_registry, node_slot_mapping 신규), frames, families, slide_base 갱신
- tests/matching: phase2~26 매칭 실험 스크립트·리포트·온톨로지 전체 (미커밋 진행분)
- tests: b4_v4 evidence, task5~28.5 시리즈, regression(imp95 baseline) 등 신규 테스트 대량 추가
- docs/reference: MDX 구조 인벤토리, MDX→Frame 구조 계약 문서
- scripts: mdx 계약/parity/coverage/viewport 체크, gitea comment, run sync 유틸
- .gitignore: tmp*.json, chromedriver, .orchestrator, *.pkl, Front_test* 등 임시/스냅샷 제외

미완성 작업의 보존용 스냅샷 커밋 (2026-07-02)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 17:03:42 +09:00

390 lines
13 KiB
Python

"""IMP-43 (#72) u1 + u5 — focused tests for the ``--reuse-from`` CLI surface.
u1 scope (per the Stage 2 Exit Report):
- argparse flag ``--reuse-from PREV_RUN_ID`` parses without error.
- Fail-closed precondition guard runs AFTER the ``user_overrides.json``
merge and BEFORE dispatch. With ``--reuse-from`` set, the guard
must:
* accept frame-only overrides (or no overrides at all);
* reject layout / zone-geometry / zone-section / image overrides
with ``sys.exit(2)`` whose stderr names every rejected axis.
u5 scope (added 2026-05-24):
- ``reuse_from`` is keyword-only on ``run_phase_z2_mvp1`` and defaults
to ``None`` so the absent-flag path preserves pre-u5 behaviour.
- The CLI dispatch forwards ``args.reuse_from`` verbatim — both
``None`` (flag absent) and ``"PREV_RUN_ID"`` (flag present) reach
the kwarg unchanged.
- The fake ``run_phase_z2_mvp1`` stub below mirrors the production
signature so the forwarding lock would fail loudly on any
forwarding regression.
The harness mirrors ``tests/test_phase_z2_cli_overrides.py`` — the
``if __name__ == "__main__"`` block of ``src.phase_z2_pipeline`` is
exec'd inside the module's namespace after monkeypatching
``run_phase_z2_mvp1`` with a recording stub. The persistence fallback
is silenced by redirecting ``src.user_overrides_io.DEFAULT_OVERRIDES_ROOT``
to a clean tmp directory so persisted state from prior runs cannot bleed
into the parser-only assertions here.
"""
from __future__ import annotations
import ast
import sys
from pathlib import Path
from typing import Any
import pytest
import src.phase_z2_pipeline as _pz2
import src.user_overrides_io as _io
# -- harness ---------------------------------------------------------------
def _exec_main_block(
captured: dict[str, Any], argv: list[str], monkeypatch
) -> None:
"""Run the ``__main__`` body of phase_z2_pipeline.py with a fake
``run_phase_z2_mvp1`` so its kwargs are observable. Captures the
presence of the call (``called=True``) so guard-driven early exits
can be distinguished from a successful parse + dispatch."""
def _fake_run(
mdx_path,
run_id,
*,
override_layout=None,
override_frames=None,
override_zone_geometries=None,
override_section_assignments=None,
override_image_overrides=None,
override_slide_css=None,
override_text_overrides=None,
override_structure_overrides=None,
reuse_from=None,
):
captured["called"] = True
captured["mdx_path"] = mdx_path
captured["run_id"] = run_id
captured["override_layout"] = override_layout
captured["override_frames"] = override_frames
captured["override_zone_geometries"] = override_zone_geometries
captured["override_section_assignments"] = override_section_assignments
captured["override_image_overrides"] = override_image_overrides
captured["override_slide_css"] = override_slide_css
captured["override_text_overrides"] = override_text_overrides
captured["override_structure_overrides"] = override_structure_overrides
captured["reuse_from"] = reuse_from
monkeypatch.setattr(_pz2, "run_phase_z2_mvp1", _fake_run)
monkeypatch.setattr(sys, "argv", argv)
src_path = Path(_pz2.__file__)
source = src_path.read_text(encoding="utf-8")
tree = ast.parse(source)
for node in tree.body:
if (
isinstance(node, ast.If)
and isinstance(node.test, ast.Compare)
and isinstance(node.test.left, ast.Name)
and node.test.left.id == "__name__"
):
block = ast.Module(body=node.body, type_ignores=[])
exec(compile(block, str(src_path), "exec"), _pz2.__dict__)
return
raise AssertionError("no `if __name__ == '__main__'` block found")
def _redirect_overrides_root(tmp_path: Path, monkeypatch) -> None:
"""Isolate the persistence fallback so file state never leaks in."""
monkeypatch.setattr(_io, "DEFAULT_OVERRIDES_ROOT", tmp_path)
# -- success paths --------------------------------------------------------
def test_reuse_from_alone_parses_and_dispatches(tmp_path, monkeypatch):
"""``--reuse-from`` with no other overrides must parse cleanly and
fall through to dispatch (frame-only / empty override is allowed).
u5 (2026-05-24): also asserts the CLI threads ``args.reuse_from``
verbatim into the ``reuse_from`` kwarg."""
_redirect_overrides_root(tmp_path, monkeypatch)
captured: dict[str, Any] = {}
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--reuse-from",
"03__DX_20260508025134",
],
monkeypatch,
)
assert captured.get("called") is True
# u5 — verbatim threading.
assert captured["reuse_from"] == "03__DX_20260508025134"
def test_reuse_from_with_frame_override_dispatches(tmp_path, monkeypatch):
"""Frame overrides ARE preserved across Step 0/1/2/5/6 reuse, so
``--reuse-from`` + ``--override-frame`` must reach dispatch.
u5: forwards both ``reuse_from`` and ``override_frames`` in the
same call."""
_redirect_overrides_root(tmp_path, monkeypatch)
captured: dict[str, Any] = {}
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--reuse-from",
"03__DX_20260508025134",
"--override-frame",
"03-1=frame_foo",
],
monkeypatch,
)
assert captured.get("called") is True
assert captured["override_frames"] == {"03-1": "frame_foo"}
# u5 — frame override + reuse_from reach the kwarg simultaneously.
assert captured["reuse_from"] == "03__DX_20260508025134"
# -- u5 — flag-absent default + signature surface ------------------------
def test_no_reuse_from_threads_none_kwarg(tmp_path, monkeypatch):
"""u5 — when ``--reuse-from`` is absent, the kwarg must reach
``run_phase_z2_mvp1`` as ``None`` (not omitted, not ``""``). This
locks the "default None preserves current behavior" requirement
from the Stage 2 plan §u5."""
_redirect_overrides_root(tmp_path, monkeypatch)
captured: dict[str, Any] = {}
_exec_main_block(
captured,
["src.phase_z2_pipeline", "03.mdx"],
monkeypatch,
)
assert captured.get("called") is True
assert captured["reuse_from"] is None
def test_run_phase_z2_mvp1_signature_includes_reuse_from():
"""Production signature lock — ``reuse_from`` must be a keyword-only
parameter with default ``None``. Mirror of the entry-tests
invariant; kept here so the CLI-surface test file fails loudly if
the production signature drifts away from the dispatch contract."""
import inspect
sig = inspect.signature(_pz2.run_phase_z2_mvp1)
assert "reuse_from" in sig.parameters, list(sig.parameters)
param = sig.parameters["reuse_from"]
assert param.kind is inspect.Parameter.KEYWORD_ONLY, param.kind
assert param.default is None, param.default
# -- fail-closed (single-axis rejection) ----------------------------------
def test_reuse_from_with_layout_override_exits(tmp_path, monkeypatch, capsys):
_redirect_overrides_root(tmp_path, monkeypatch)
captured: dict[str, Any] = {}
with pytest.raises(SystemExit) as excinfo:
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--reuse-from",
"03__DX_20260508025134",
"--override-layout",
"horizontal-2",
],
monkeypatch,
)
assert excinfo.value.code == 2
err = capsys.readouterr().err
assert "--reuse-from incompatible with override axes" in err
assert "layout" in err
assert captured.get("called") is not True
def test_reuse_from_with_zone_geometry_override_exits(
tmp_path, monkeypatch, capsys
):
_redirect_overrides_root(tmp_path, monkeypatch)
captured: dict[str, Any] = {}
with pytest.raises(SystemExit) as excinfo:
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--reuse-from",
"03__DX_20260508025134",
"--override-zone-geometry",
"top=0,0,1,0.3",
],
monkeypatch,
)
assert excinfo.value.code == 2
err = capsys.readouterr().err
assert "--reuse-from incompatible with override axes" in err
assert "zone_geometry" in err
assert captured.get("called") is not True
def test_reuse_from_with_zone_section_override_exits(
tmp_path, monkeypatch, capsys
):
_redirect_overrides_root(tmp_path, monkeypatch)
captured: dict[str, Any] = {}
with pytest.raises(SystemExit) as excinfo:
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--reuse-from",
"03__DX_20260508025134",
"--override-section-assignment",
"top=03-1",
],
monkeypatch,
)
assert excinfo.value.code == 2
err = capsys.readouterr().err
assert "--reuse-from incompatible with override axes" in err
assert "zone_section" in err
assert captured.get("called") is not True
def test_reuse_from_with_image_override_exits(tmp_path, monkeypatch, capsys):
_redirect_overrides_root(tmp_path, monkeypatch)
captured: dict[str, Any] = {}
with pytest.raises(SystemExit) as excinfo:
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--reuse-from",
"03__DX_20260508025134",
"--override-image",
"img-abc=10,15,30,25",
],
monkeypatch,
)
assert excinfo.value.code == 2
err = capsys.readouterr().err
assert "--reuse-from incompatible with override axes" in err
assert "image" in err
assert captured.get("called") is not True
# -- fail-closed (multi-axis aggregation) ---------------------------------
def test_reuse_from_with_multiple_rejected_axes_lists_all(
tmp_path, monkeypatch, capsys
):
"""Stderr must enumerate every rejected axis (not stop at first)."""
_redirect_overrides_root(tmp_path, monkeypatch)
captured: dict[str, Any] = {}
with pytest.raises(SystemExit) as excinfo:
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--reuse-from",
"03__DX_20260508025134",
"--override-layout",
"horizontal-2",
"--override-zone-geometry",
"top=0,0,1,0.3",
"--override-image",
"img-abc=10,15,30,25",
],
monkeypatch,
)
assert excinfo.value.code == 2
err = capsys.readouterr().err
assert "layout" in err
assert "zone_geometry" in err
assert "image" in err
assert captured.get("called") is not True
# -- guard inactive when --reuse-from absent ------------------------------
def test_no_reuse_from_layout_override_still_dispatches(
tmp_path, monkeypatch
):
"""Without ``--reuse-from``, the guard must be silent — existing
override behaviour is preserved end-to-end."""
_redirect_overrides_root(tmp_path, monkeypatch)
captured: dict[str, Any] = {}
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--override-layout",
"horizontal-2",
],
monkeypatch,
)
assert captured.get("called") is True
assert captured["override_layout"] == "horizontal-2"
# -- fail-closed honours persisted overrides ------------------------------
def test_reuse_from_with_persisted_layout_override_exits(
tmp_path, monkeypatch, capsys
):
"""The guard runs AFTER the user_overrides.json merge, so a layout
persisted on disk (not on the CLI) must still reject when
``--reuse-from`` is set. This locks the Stage 2 placement rule."""
_redirect_overrides_root(tmp_path, monkeypatch)
# Persist a layout override keyed by the MDX stem ``03``.
overrides_dir = tmp_path
overrides_dir.mkdir(parents=True, exist_ok=True)
(overrides_dir / "03.json").write_text(
'{"layout": "vertical-2"}', encoding="utf-8"
)
captured: dict[str, Any] = {}
with pytest.raises(SystemExit) as excinfo:
_exec_main_block(
captured,
[
"src.phase_z2_pipeline",
"03.mdx",
"--reuse-from",
"03__DX_20260508025134",
],
monkeypatch,
)
assert excinfo.value.code == 2
err = capsys.readouterr().err
assert "--reuse-from incompatible with override axes" in err
assert "layout" in err
assert captured.get("called") is not True