Add Phase 0a black-box integration test suite
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.
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user