core/tests/test_learning_loop_demo.py
Shay 34295e55ce perf(test-infra): pytest-xdist + module-scoped demo fixtures
Full lane wall-time: 6:35 → 2:25 (2.7× speedup).  No behavioral
changes; same 1933 passed, 2 skipped.

Three wins, biggest first:

1. pytest-xdist as a project dependency.

   ``pyproject.toml`` gains ``pytest-xdist>=3.6``.  ``cmd_test``
   injects ``-n auto`` for ``--suite full`` when xdist is importable;
   curated suites stay single-process because worker-spawn overhead
   is net-negative on the smaller suites.  Operator can override
   via passing ``-n <N>`` or ``--dist`` explicitly.

   Verified: ``core test --suite full -q`` prints ``bringing up
   nodes...`` and parallelises across the runner's CPUs.

2. Module-scoped fixture for run_demo() in test_learning_loop_demo.py.

   The 7 demo tests each previously called ``run_demo(emit_json=True)``
   from scratch — and ``run_demo`` itself runs the cognition lane
   twice via the replay-equivalence gate.  ~15s/file → ~3s/file.

   Module scope (not session) is intentional: pytest-xdist
   distributes by test, so a session-scoped fixture would still be
   re-evaluated per worker that picks up a test from this file.
   Module scope keeps the cost paid once per worker per file, which
   is the actual lower bound.

3. Module-scoped fixture for the teaching-loop bench.

   ``test_teaching_loop_bench.py``'s 5 tests previously each ran
   ``run_teaching_loop_determinism(runs=2 or 3)`` — 12 pipeline
   invocations across the file.  One ``runs=3`` invocation shared
   across all 5 tests covers every assertion: ~25s → ~7s.

For local iteration, ``core test --suite cognition -q`` etc. remain
fast (no xdist overhead).  The full-lane speedup is most visible
under CI / pre-merge runs.
2026-05-18 16:12:27 -07:00

90 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Learning-loop demo — pins the load-bearing before/after claim.
If any assertion fails, the headline claim ("CORE learned a new chain
from a cold turn and the same prompt is now teaching-grounded with
provenance") no longer holds.
Performance: ``run_demo()`` exercises the full pipeline including the
replay-equivalence gate (which itself runs the cognition public split
twice). Each invocation costs ~2-3s. A module-scoped fixture caches
the report so every assertion in this file shares one demo run —
reduces this file's runtime from ~15s (7 × 2s) to ~2s.
Compatibility with pytest-xdist: pytest-xdist distributes by test, not
by module; module-scoped fixtures are re-evaluated per worker that
picks up a test from this file. Worst case one worker takes the
whole file's 2s; xdist still parallelises across the rest of the
suite.
"""
from __future__ import annotations
import pytest
from evals.learning_loop.run_demo import run_demo
@pytest.fixture(scope="module")
def demo_report() -> dict:
"""One ``run_demo()`` invocation shared across every test in this
module. Module-scoped so pytest-xdist's per-worker isolation
still applies (a worker that picks up any test in this file pays
the demo cost once)."""
return run_demo(emit_json=True)
def test_demo_closes_the_full_loop(demo_report: dict) -> None:
assert demo_report["learning_loop_closed"] is True
assert demo_report["active_corpus_byte_identical"] is True
assert len(demo_report["scenes"]) == 5
def test_before_is_ungrounded_disclosure(demo_report: dict) -> None:
assert demo_report["before"]["grounding_source"] == "none"
assert "insufficient grounding" in demo_report["before"]["surface"].lower()
def test_after_is_teaching_grounded_with_new_chain_atoms(demo_report: dict) -> None:
assert demo_report["after"]["grounding_source"] == "teaching"
surface = demo_report["after"]["surface"].lower()
# The accepted chain is (narrative, cause, reveals, meaning).
# ``thought`` was the original cold subject; cognition saturation
# v2 (commit ``a0edbb4``) added ``cause_thought_reveals_meaning``
# to the active corpus so the demo switched to ``narrative`` —
# same shape, still cold.
assert "narrative" in surface
assert "reveal" in surface # humanised connective
assert "meaning" in surface
assert "teaching-grounded" in surface
def test_s1_emits_one_discovery_candidate(demo_report: dict) -> None:
s1 = demo_report["scenes"][0]
assert s1["scene"] == "S1_cold_turn"
assert s1["detail"]["discovery_candidates_emitted"] >= 1
def test_s3_replay_gate_reports_no_regression(demo_report: dict) -> None:
s3 = demo_report["scenes"][2]
assert s3["scene"] == "S3_propose_replay_pass"
ev = s3["detail"]["replay_evidence"]
assert ev["replay_equivalent"] is True
assert ev["regressed_metrics"] == []
assert s3["detail"]["state"] == "pending"
def test_s4_active_corpus_byte_identical_after_accept(demo_report: dict) -> None:
s4 = demo_report["scenes"][3]
assert s4["scene"] == "S4_accept_against_transient"
assert s4["detail"]["active_corpus_byte_identical"] is True
assert s4["detail"]["transient_lines_after"] == s4["detail"]["transient_lines_before"] + 1
def test_same_prompt_drives_before_and_after(demo_report: dict) -> None:
"""The same input string drives both sides of the before/after pair.
Different surfaces emerge from the corpus state change alone, not
from any prompt variation or stochastic sampling."""
assert demo_report["prompt"] == "Why does narrative exist?"
# And the two surfaces are observably different — the loop changed
# the response, not merely the metadata.
assert demo_report["before"]["surface"] != demo_report["after"]["surface"]