Port the eleven audit-named missing fns name-preserving 1:1 from src/rune/cache.py (recordHit/_record_hit, recordMiss, flushStats, getCache, putCache, invalidateScope, invalidateAll, nowTs, parseTs, cacheStats, handleHook) with the in-memory hit/miss accumulation (threshold 50 / interval 30s), INVALIDATION_MAP, full schema (both indexes) and an oracle-byte-parity CLI (usage gates, --json indent=2, rune-aware :20 padding and [:200] truncation, compact hook line, PLUGIN manifest). Clock/monotonic/DB behind Env seams. Frozen truth: 78 cases via the shared freeze_truth.py harness (envelope, pinned clock, sandboxed RUNE_MEMORY_DIR), byte-identical regen + --verify green. Go battery replays verbatim + flush-branch seam pins + 16-case CLI live parity (100%). Python oracle battery 12 passed BOTH pins. Audit: cache 6/17 -> 17/17 FULL (GO).
93 lines
3.2 KiB
Python
93 lines
3.2 KiB
Python
#!/usr/bin/env python3
|
|
"""check_cache_parity.py — live CLI parity: the Python oracle
|
|
(python -m rune.cache) vs the Go port (built binary) over the same argv
|
|
matrix, each in its own throwaway memory dir. Byte-for-byte stdout with
|
|
ISO timestamps and age values normalized (both engines render real
|
|
clocks). Covers every ported fn through the CLI surface: put_cache,
|
|
get_cache, invalidate, invalidate_scope, invalidate_all, cache_stats,
|
|
handle_hook, main (usage gates, --json/plain rendering, ensure_ascii).
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
GO_BIN = Path(tempfile.mkdtemp(prefix="cache-parity-")) / "cache"
|
|
|
|
TS_RE = re.compile(r"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?\+00:00")
|
|
AGE_RE = re.compile(r'"age_seconds": [0-9.e+-]+')
|
|
AGE_S_RE = re.compile(r"\(age: \d+s\)")
|
|
|
|
|
|
def normalize(s: str) -> str:
|
|
s = TS_RE.sub("<TS>", s)
|
|
s = AGE_RE.sub('"age_seconds": <AGE>', s)
|
|
return AGE_S_RE.sub("(age: <AGE>s", s)
|
|
|
|
|
|
MATRIX = [
|
|
["put", "alpha", "Alpha value line one\nAlpha line two"],
|
|
["put", "beta", "Beta retrieval value", "--scope", "retrieval", "--source-hash", "src-1", "--json"],
|
|
["put", "uni-ü", "Wörter with ümlaut", "--json"],
|
|
["get", "alpha", "--json"],
|
|
["get", "alpha"],
|
|
["get", "uni-ü", "--json"],
|
|
["get", "missing-key"],
|
|
["get"],
|
|
["put", "only-key"],
|
|
["invalidate", "alpha", "--reason", "stale", "--json"],
|
|
["invalidate-scope", "retrieval", "--json"],
|
|
["invalidate-all", "--json"],
|
|
["stats"],
|
|
["stats", "--json"],
|
|
["hook", "--event", "memory.created", "--data", '{"x": 1}'],
|
|
["hook"],
|
|
]
|
|
|
|
|
|
def py_run(argv: list[str], mem: str) -> str:
|
|
env = dict(os.environ, RUNE_MEMORY_DIR=mem, PYTHONPATH=str(REPO / "src"))
|
|
r = subprocess.run([sys.executable, "-m", "rune.cache", *argv],
|
|
capture_output=True, text=True, cwd=REPO, env=env)
|
|
if r.returncode != 0:
|
|
sys.exit(f"oracle CLI {argv} failed: {r.stderr}")
|
|
return r.stdout
|
|
|
|
|
|
def go_run(argv: list[str], mem: str) -> str:
|
|
env = dict(os.environ, RUNE_MEMORY_DIR=mem)
|
|
r = subprocess.run([str(GO_BIN), *argv], capture_output=True, text=True, cwd=REPO, env=env)
|
|
if r.returncode != 0:
|
|
sys.exit(f"go CLI {argv} failed: {r.stderr}")
|
|
return r.stdout
|
|
|
|
|
|
def main() -> int:
|
|
build = subprocess.run(["go", "build", "-o", str(GO_BIN), "."],
|
|
cwd=REPO / "src/go/cache", capture_output=True, text=True)
|
|
if build.returncode != 0:
|
|
sys.exit(f"go build failed: {build.stderr}")
|
|
|
|
py_mem = tempfile.mkdtemp(prefix="cache-parity-py-")
|
|
go_mem = tempfile.mkdtemp(prefix="cache-parity-go-")
|
|
passed = 0
|
|
for argv in MATRIX:
|
|
py_out = normalize(py_run(argv, py_mem))
|
|
go_out = normalize(go_run(argv, go_mem))
|
|
if py_out != go_out:
|
|
print(f"FAIL {' '.join(argv)}\n py: {py_out!r}\n go: {go_out!r}")
|
|
return 1
|
|
passed += 1
|
|
print(f"PASS {' '.join(json.dumps(a, ensure_ascii=False) for a in argv)}")
|
|
print(f"LIVE PARITY 100% ({passed}/{len(MATRIX)} cases)")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|