"""The stage cache, from the side that needs a database. The pipeline half — what a hit restores and what a key is made of — is in `tests/flow/test_runs.py`, which runs without one. """ import json from sqlmodel import Session from fluksio.core.db import engine as db_engine from fluksio.flow.artifacts import ArtifactStore from fluksio.flow.pipeline import NodeOutcome from fluksio.flow.runs import OUTPUT_CAP, RunCache, _cacheable from fluksio.models import RunNode def test_a_run_cache_finds_what_an_earlier_run_recorded(tmp_path): """The run history is the cache; there is no second store to keep.""" store = ArtifactStore(tmp_path / "artifacts") reference = store.put([b"payload"], name="data.bin") plain, with_artifact, collected = "k-plain", "k-artifact", "k-collected" with Session(db_engine) as session: session.add( RunNode( run_id="cache-1", node="study.a", status="ok", cache_key=plain, outputs=json.dumps({"study.loss": 1.5}), ) ) session.add( RunNode( run_id="cache-2", node="study.b", status="ok", cache_key=with_artifact, outputs=json.dumps({"study.data": reference}), ) ) session.add( RunNode( run_id="cache-3", node="study.c", status="ok", cache_key=collected, outputs=json.dumps( {"study.data": {**reference, "digest": "sha256:" + "1" * 64}} ), ) ) session.commit() cache = RunCache(store) assert cache.lookup(plain) == (True, {"study.loss": 1.5}) assert cache.lookup(with_artifact) == (True, {"study.data": reference}) # Its bytes have gone from the store, so the reference names nothing a # restored run could open. That is a miss, not a broken run. assert cache.lookup(collected) == (False, None) assert cache.lookup("never-seen") == (False, None) assert cache.lookup("") == (False, None) def test_what_may_be_stored_as_a_cache_entry(): """A row carries a key and its outputs together, or neither.""" ok = NodeOutcome( node="study.a", ok=True, cache_key="k", output_values={"study.loss": 1.0} ) assert _cacheable(ok) == '{"study.loss":1.0}' # A node that published nothing is still an answer worth reusing. assert _cacheable(ok.model_copy(update={"output_values": None})) == "null" # Not cacheable: it failed, it has no key, or it returned too much. assert _cacheable(ok.model_copy(update={"ok": False})) is None assert _cacheable(ok.model_copy(update={"cache_key": ""})) is None big = {"study.data": "x" * (OUTPUT_CAP + 1)} assert _cacheable(ok.model_copy(update={"output_values": big})) is None