255 lines
10 KiB
Python
255 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""gen_connection_pool_truth.py — freeze the connection_pool parity oracle
|
|
(dichotomy wave #2479 tranche).
|
|
|
|
Generator law: RUNE_CPOOL_CORE=python pinned (the Rust core must never see
|
|
the freeze), inputs deep-copied, every public kernel branch exercised
|
|
(new / reuse / stale-removal / capacity-eviction / commit / status /
|
|
close_all) against a REAL sqlite-backed pool in a temp dir. Output:
|
|
src/rust/connection_pool/frozen_cpool_truth.json — replayed VERBATIM by the
|
|
crate's cfg(test) battery.
|
|
"""
|
|
import copy
|
|
import json
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "src"))
|
|
|
|
os.environ["RUNE_CPOOL_CORE"] = "python"
|
|
|
|
from rune import connection_pool as cp_mod # noqa: E402
|
|
from rune.connection_pool import ConnectionPool # noqa: E402
|
|
|
|
MAX = 2
|
|
cases: list = []
|
|
|
|
|
|
def pool_state(p: ConnectionPool) -> dict:
|
|
"""The kernel-shaped pool state, as the oracle holds it right now."""
|
|
st = p.get_status()
|
|
return {"max_connections": st["max_connections"],
|
|
"databases": copy.deepcopy(st["databases"])}
|
|
|
|
|
|
def record_begin_get(p: ConnectionPool, db_path: str, valid: bool) -> None:
|
|
"""Run one oracle get_connection op and freeze it: a cpool_begin_get
|
|
case (and, when the oracle created a connection, the matching
|
|
cpool_commit case)."""
|
|
before = pool_state(p)
|
|
pooled_before = dict(p.connections)
|
|
conn = p.get_connection(db_path)
|
|
after_full = pool_state(p)
|
|
reuse = db_path in pooled_before and conn is pooled_before[db_path]
|
|
if reuse:
|
|
after_begin = copy.deepcopy(before)
|
|
else:
|
|
after_begin = {"max_connections": after_full["max_connections"],
|
|
"databases": [d for d in after_full["databases"]
|
|
if d != db_path]}
|
|
evicted = (set(before["databases"]) - set(after_full["databases"])
|
|
- {db_path})
|
|
cases.append({
|
|
"fn": "cpool_begin_get",
|
|
"args": {"pool": copy.deepcopy(before), "db_path": db_path,
|
|
"valid": valid},
|
|
"expected": {"pool": after_begin, "reuse": reuse,
|
|
"evict": sorted(evicted)[0] if evicted else None},
|
|
})
|
|
if not reuse:
|
|
cases.append({
|
|
"fn": "cpool_commit",
|
|
"args": {"pool": copy.deepcopy(after_begin), "db_path": db_path},
|
|
"expected": {"pool": copy.deepcopy(after_full)},
|
|
})
|
|
|
|
|
|
def main() -> None:
|
|
with tempfile.TemporaryDirectory() as td:
|
|
p = ConnectionPool(max_connections=MAX)
|
|
cases.append({
|
|
"fn": "cpool_new",
|
|
"args": {"max_connections": MAX},
|
|
"expected": copy.deepcopy(pool_state(p)),
|
|
})
|
|
|
|
dbs = [os.path.join(td, f"{n}.db") for n in "ABCD"]
|
|
|
|
record_begin_get(p, dbs[0], True) # create A
|
|
record_begin_get(p, dbs[0], True) # reuse A
|
|
record_begin_get(p, dbs[1], True) # create B
|
|
record_begin_get(p, dbs[2], True) # create C — evicts A
|
|
# force B stale: close the underlying connection, then re-get
|
|
p.connections[dbs[1]].close()
|
|
record_begin_get(p, dbs[1], False) # stale B — no evict
|
|
record_begin_get(p, dbs[3], True) # create D — evicts C
|
|
|
|
before_status = pool_state(p)
|
|
cases.append({
|
|
"fn": "cpool_status",
|
|
"args": {"pool": copy.deepcopy(before_status)},
|
|
"expected": copy.deepcopy(p.get_status()),
|
|
})
|
|
|
|
before_close = pool_state(p)
|
|
p.close_all()
|
|
cases.append({
|
|
"fn": "cpool_close_all",
|
|
"args": {"pool": copy.deepcopy(before_close)},
|
|
"expected": copy.deepcopy(pool_state(p)),
|
|
})
|
|
|
|
record_begin_get(p, dbs[0], True) # fresh pool: create A
|
|
|
|
# ── orchestration residue: get_pool / _kernel_state /
|
|
# _get_connection_core (literal-100%) ─────────────────────
|
|
# A recording stand-in for the kernel protocol (new_pool /
|
|
# begin_get / commit / close_all / status), semantics identical
|
|
# to the facade's own python-path dict bookkeeping — the
|
|
# ORCHESTRATION under test is the oracle's _get_connection_core
|
|
# body itself; the state math is already parity-frozen above.
|
|
class RecordingCore:
|
|
def __init__(self, max_c):
|
|
self.max_c = max_c
|
|
self.calls = []
|
|
|
|
def new_pool(self, max_connections):
|
|
self.calls.append(f"new_pool:{max_connections}")
|
|
return {"max_connections": max_connections, "databases": []}
|
|
|
|
def begin_get(self, pool, db_path, valid):
|
|
dbs = pool["databases"]
|
|
if db_path in dbs:
|
|
if valid:
|
|
self.calls.append(f"begin_get:{os.path.basename(db_path)}:{valid}")
|
|
return {"pool": copy.deepcopy(pool), "reuse": True, "evict": None}
|
|
dbs = [d for d in dbs if d != db_path]
|
|
evict = None
|
|
if len(dbs) >= pool["max_connections"] and dbs:
|
|
evict = dbs.pop(0)
|
|
self.calls.append(f"begin_get:{os.path.basename(db_path)}:{valid}")
|
|
return {"pool": {"max_connections": pool["max_connections"],
|
|
"databases": dbs},
|
|
"reuse": False, "evict": evict}
|
|
|
|
def commit(self, pool, db_path):
|
|
self.calls.append(f"commit:{os.path.basename(db_path)}")
|
|
return {"max_connections": pool["max_connections"],
|
|
"databases": pool["databases"] + [db_path]}
|
|
|
|
def close_all(self, pool):
|
|
self.calls.append("close_all")
|
|
return {"max_connections": pool["max_connections"], "databases": []}
|
|
|
|
def status(self, pool):
|
|
self.calls.append("status")
|
|
return {"connections": len(pool["databases"]),
|
|
"max_connections": pool["max_connections"],
|
|
"databases": list(pool["databases"])}
|
|
|
|
# 1. _kernel_state lazy law (a fresh pool has NO kernel state; the
|
|
# first core op initializes it at the instance's max)
|
|
lazy_p = ConnectionPool(max_connections=2)
|
|
core = RecordingCore(2)
|
|
before = lazy_p._state
|
|
lazy_p._kernel_state(core)
|
|
cases.append({
|
|
"fn": "cpool_kernel_state_lazy",
|
|
"args": {},
|
|
"expected": {"before": before,
|
|
"after": lazy_p._state},
|
|
})
|
|
|
|
# 2. _get_connection_core orchestration over a real sqlite-backed
|
|
# pool: the full op ladder with the core-call trace + status
|
|
# snapshots frozen per op
|
|
orch_p = ConnectionPool(max_connections=2)
|
|
orch_core = RecordingCore(2)
|
|
orch_dbs = [os.path.join(td, f"{n}.db") for n in "abcd"]
|
|
orch = []
|
|
|
|
def orch_get(db_path):
|
|
handles_before = dict(orch_p.connections)
|
|
conn = orch_p._get_connection_core(orch_core, db_path)
|
|
reused = db_path in handles_before and conn is handles_before[db_path]
|
|
st = orch_core.status(orch_p._state)
|
|
st["databases"] = [d.replace(td, "TMPDIR") for d in st["databases"]]
|
|
orch.append({"db": os.path.basename(db_path),
|
|
"pooled": db_path in handles_before,
|
|
"reuse": reused,
|
|
"status": st})
|
|
return conn
|
|
|
|
orch_get(orch_dbs[0]) # create a
|
|
orch_get(orch_dbs[0]) # reuse a
|
|
orch_get(orch_dbs[1]) # create b
|
|
orch_get(orch_dbs[2]) # create c — evicts a
|
|
orch_p.connections[orch_dbs[1]].close() # force b stale
|
|
orch_get(orch_dbs[1]) # stale b — rejoin at END
|
|
orch_get(orch_dbs[3]) # create d — evicts c
|
|
cases.append({
|
|
"fn": "cpool_get_connection_core",
|
|
"args": {},
|
|
"expected": {"ops": orch, "core_calls": orch_core.calls},
|
|
})
|
|
|
|
# 3. the connect-failure branch: the exception propagates and the
|
|
# pool stays exactly as begin_get left it — NO commit
|
|
fail_p = ConnectionPool(max_connections=2)
|
|
fail_core = RecordingCore(2)
|
|
fail_p._get_connection_core(fail_core, orch_dbs[0])
|
|
real_sqlite = cp_mod.sqlite3
|
|
import types as _types
|
|
|
|
class _ConnectFail(Exception):
|
|
pass
|
|
|
|
cp_mod.sqlite3 = _types.SimpleNamespace(
|
|
connect=lambda *_a, **_k: (_ for _ in ()).throw(_ConnectFail("disk I/O error")),
|
|
Row=real_sqlite.Row)
|
|
try:
|
|
crashed = None
|
|
try:
|
|
fail_p._get_connection_core(fail_core, orch_dbs[1])
|
|
except Exception as e: # noqa: BLE001 — the crash class IS the truth
|
|
crashed = type(e).__name__
|
|
st = fail_core.status(fail_p._state)
|
|
st["databases"] = [d.replace(td, "TMPDIR") for d in st["databases"]]
|
|
cases.append({
|
|
"fn": "cpool_get_connection_core_connect_fail",
|
|
"args": {},
|
|
"expected": {"crash": crashed, "status": st,
|
|
"core_calls": fail_core.calls},
|
|
})
|
|
finally:
|
|
cp_mod.sqlite3 = real_sqlite
|
|
|
|
# 4. get_pool singleton law (fresh module state, one process)
|
|
import rune.connection_pool as cp_singleton
|
|
cp_singleton._pool = None
|
|
p1 = cp_singleton.get_pool()
|
|
p2 = cp_singleton.get_pool()
|
|
cases.append({
|
|
"fn": "cpool_get_pool_singleton",
|
|
"args": {},
|
|
"expected": {"same": p1 is p2, "max_connections": p1.max_connections},
|
|
})
|
|
|
|
doc = {
|
|
"module": "connection_pool",
|
|
"pin": "RUNE_CPOOL_CORE=python",
|
|
"frozen_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
|
"cases": cases,
|
|
}
|
|
out = ROOT / "src" / "rust" / "connection_pool" / "frozen_cpool_truth.json"
|
|
out.write_text(json.dumps(doc, indent=1) + "\n")
|
|
print(f"frozen {len(cases)} cases -> {out}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|