81 tests across 11 files plus a 5-module harness (load, pg, streams, containers, payloads), covering all eleven Phase 0 goals and all 13 named chaos-flag pathologies. Talks to the system only over HTTP, Postgres, Redis, and the Docker CLI -- no imports of application code. RED-BASELINE.md records the suite failing red against the empty repo before any implementation existed; README.md documents the request-contract fields invented to fill gaps in the spec, measurement methodology, and known-flaky tests. This suite is a frozen contract from this point forward per CLAUDE.md.
75 lines
2.5 KiB
Python
75 lines
2.5 KiB
Python
"""pg_stat_statements deltas and EXPLAIN helpers, per phase-0a-test-suite-spec.md §7."""
|
|
from contextlib import contextmanager
|
|
|
|
import psycopg
|
|
|
|
|
|
class _Counter:
|
|
"""Populated with `.calls` (and `.total_time_ms`) once the `with` block exits."""
|
|
|
|
def __init__(self):
|
|
self.calls = None
|
|
self.total_time_ms = None
|
|
|
|
def __repr__(self):
|
|
return f"Counter(calls={self.calls}, total_time_ms={self.total_time_ms})"
|
|
|
|
def __str__(self):
|
|
return repr(self)
|
|
|
|
|
|
@contextmanager
|
|
def statement_count(dsn):
|
|
"""Reset pg_stat_statements, run the caller's block, yield the call-count delta.
|
|
|
|
pg_stat_statements is reset globally, so this is only meaningful when
|
|
nothing else is hitting the database concurrently during the `with` block
|
|
-- true for the single-request pathology assertions this is built for.
|
|
"""
|
|
counter = _Counter()
|
|
with psycopg.connect(dsn, autocommit=True) as conn:
|
|
with conn.cursor() as cur:
|
|
cur.execute("SELECT pg_stat_statements_reset()")
|
|
try:
|
|
yield counter
|
|
finally:
|
|
with conn.cursor() as cur:
|
|
cur.execute(
|
|
"SELECT COALESCE(SUM(calls), 0), COALESCE(SUM(total_exec_time), 0) "
|
|
"FROM pg_stat_statements"
|
|
)
|
|
calls, total_time_ms = cur.fetchone()
|
|
counter.calls = calls
|
|
counter.total_time_ms = total_time_ms
|
|
|
|
|
|
def explain(dsn, sql, params=None):
|
|
"""Run EXPLAIN (ANALYZE, FORMAT JSON) and return the plan dict."""
|
|
with psycopg.connect(dsn, autocommit=True) as conn:
|
|
with conn.cursor() as cur:
|
|
cur.execute(f"EXPLAIN (ANALYZE, FORMAT JSON) {sql}", params or ())
|
|
(plan,) = cur.fetchone()
|
|
return plan[0]
|
|
|
|
|
|
def plan_node_types(plan: dict) -> set:
|
|
"""Flatten an EXPLAIN JSON plan (or its top-level {"Plan": ...} wrapper)
|
|
into the set of "Node Type" values it contains, at any depth."""
|
|
types: set = set()
|
|
root = plan.get("Plan", plan) if isinstance(plan, dict) else plan
|
|
|
|
def walk(node):
|
|
if isinstance(node, dict):
|
|
if "Node Type" in node:
|
|
types.add(node["Node Type"])
|
|
for key, value in node.items():
|
|
if key == "Plans" and isinstance(value, list):
|
|
for child in value:
|
|
walk(child)
|
|
elif isinstance(node, list):
|
|
for item in node:
|
|
walk(item)
|
|
|
|
walk(root)
|
|
return types
|