Zum Inhalt

tests/test_integration.py

"""End-to-end pipeline with a fake client and no real host sampling.

Exercises iter_cells → stream_once → record building → merge → report, proving
the whole chain produces report.md + raw.csv with the right row counts.
"""

from touchstone import report
from touchstone.config import Config, ModelSpec
from touchstone.runner import StreamEvent, run_benchmark


class FakeClient:
    engine = "fake"
    engine_version = "1.2.3"

    def stream(self, *, messages, model, max_tokens, temperature, seed):
        yield StreamEvent(delta_text="Hallo ")
        yield StreamEvent(delta_text="Welt")
        yield StreamEvent(prompt_tokens=123, completion_tokens=7)


class NoopSampler:
    def start(self):
        pass

    def stop(self):
        pass


def _cfg(tmp_path):
    return Config.model_validate(
        {
            "endpoint": {"base_url": "http://localhost:1234/v1"},
            "machine": "M1-test",
            "runs_per_cell": 3,
            "context_buckets": [4096],
            "scenarios": ["bodydouble", "rag_synth"],
            "models": [{"id": "qwen3-8b", "quant": "Q5"}],
            "power_check": False,
            "output_dir": str(tmp_path),
        }
    )


def test_run_benchmark_full_pipeline(tmp_path):
    cfg = _cfg(tmp_path)
    run_dir = tmp_path / "run1"
    records = run_benchmark(cfg, FakeClient(), run_dir=run_dir, sampler=NoopSampler())

    # cells: bodydouble (ctx 0) + rag_synth (ctx 4096) = 2 cells
    # per cell: 3 runs; plus 1 cold-start overall = 2*3 + 1 = 7 records
    assert len(records) == 7
    assert sum(1 for r in records if r.is_cold_start) == 1
    assert sum(1 for r in records if r.warmup) == 2  # one warmup per cell
    assert all(r.engine == "lm-studio" for r in records)
    assert all(r.engine_version == "1.2.3" for r in records)
    assert all(r.actual_prompt_tokens == 123 for r in records)

    # Write + read back the report artifacts.
    md_path, raw_path = report.write_report(records, run_dir, date_str="2026-06-15")
    assert md_path.exists() and raw_path.exists()
    md = md_path.read_text(encoding="utf-8")
    assert "Cold-Start" in md
    assert "M1-test" in md
    # engine resolved from the :1234 port, version taken from the client
    assert "lm-studio 1.2.3" in md

    reloaded = report.load_raw_csv(raw_path)
    assert len(reloaded) == 7


def test_run_benchmark_aggregates_exclude_warmup_and_cold(tmp_path, monkeypatch):
    # Make the test hermetic w.r.t. the host's power state: on battery the runner
    # would flag every run and aggregate_cells would exclude them (n_valid → 0).
    monkeypatch.setattr("touchstone.sampler.read_power_source", lambda: "ac")
    cfg = _cfg(tmp_path)
    records = run_benchmark(cfg, FakeClient(), run_dir=tmp_path / "run2", sampler=NoopSampler())
    cells = report.aggregate_cells(records)
    # 2 cells, each with 2 valid runs (3 runs − 1 warmup), cold excluded from cells
    assert len(cells) == 2
    assert all(c.n_valid == 2 for c in cells)


def test_latency_runner_forwards_model_extra_body(tmp_path):
    """`ModelSpec.extra_body` (z. B. `reasoning_effort`) muss AUCH im Latenz-Runner ankommen.
    Er hat es still verworfen — gefunden am 2026-09-20 beim Splash-Smoke: Das Denken blieb an,
    verbrauchte das Token-Budget, und `ttft_s` (= erster sichtbarer Inhalt) blieb `nan`,
    während der Eval-Pfad denselben Knopf korrekt durchreicht."""
    seen: list[dict | None] = []

    class SpyClient(FakeClient):
        def stream(self, *, messages, model, max_tokens, temperature, seed, extra_body=None):
            seen.append(extra_body)
            yield StreamEvent(delta_text="Hallo")
            yield StreamEvent(prompt_tokens=10, completion_tokens=2)

    cfg = _cfg(tmp_path).model_copy(
        update={
            "models": [
                ModelSpec.model_validate(
                    {"id": "qwen3-8b", "quant": "Q5", "extra_body": {"reasoning_effort": "none"}}
                )
            ]
        }
    )
    run_benchmark(cfg, SpyClient(), run_dir=tmp_path / "eb", sampler=NoopSampler())
    assert seen, "kein Request abgesetzt"
    assert all(e == {"reasoning_effort": "none"} for e in seen), seen


def test_latency_runner_omits_extra_body_when_unset(tmp_path):
    """Gegenprobe: ohne Knopf darf nichts mitgeschickt werden (sonst überschriebe ein leeres
    Dict Server-Defaults)."""
    seen: list[dict | None] = []

    class SpyClient(FakeClient):
        def stream(self, *, messages, model, max_tokens, temperature, seed, extra_body=None):
            seen.append(extra_body)
            yield StreamEvent(delta_text="Hallo")
            yield StreamEvent(prompt_tokens=10, completion_tokens=2)

    run_benchmark(_cfg(tmp_path), SpyClient(), run_dir=tmp_path / "nb", sampler=NoopSampler())
    assert seen and all(e is None for e in seen), seen