-
-
Notifications
You must be signed in to change notification settings - Fork 7
feature: seed and validate testing harness #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit
Hold shift + click to select a range
5c97555
Add seed and validate design spec
randoneering b0844fb
feat(seed): rewrite 01_seed_static_checks with full structural seeds
randoneering 637de4f
fix(seed): drop pgfirstaid_seed_role before create for clean re-runs
randoneering 71b7f5c
fix(seed): address code review feedback on 01_seed_static_checks
randoneering f132157
initial commit for testing harness
randoneering 350bcf5
feat(seed): scaffold seed_and_validate.py with CLI and connection hel…
randoneering 168a0a6
feat(seed): add threshold patching for test-sized thresholds
randoneering c8efbb8
feat(seed): add database lifecycle functions (create, drop, install)
randoneering da17007
feat(seed): add seed file runners and replication slot guard
randoneering 996ce49
feat(seed): add live session thread functions and wait helpers
randoneering 9b30f1a
feat(seed): add validation logic, expected check sets, and reporting
randoneering 290cc5e
feat(seed): wire main() orchestrator with full seed-validate-cleanup …
randoneering a2f923e
fix(seed): handle pg_stat_statements installed-but-not-loadable edge …
randoneering c887825
chore: uv.lock
randoneering ecc3708
feat(seed): add --managed flag to test view_pgFirstAid_managed.sql
randoneering 3163988
resolve false flags and skipped tests
randoneering 56856c8
added seed/validation testing into workflow
randoneering 0bd730a
resolving comments/reviews from greptile/qodo
randoneering bf1b3bb
runner is now nixos, adjusting workflows
randoneering 7c2cc0d
uv added to runner
randoneering 4db33c5
further troubleshooting
randoneering ff697da
uv at shell
randoneering 396758d
changing env for nix runner
randoneering File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
188 changes: 188 additions & 0 deletions
188
docs/superpowers/specs/2026-04-15-seed-and-validate-design.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| # pgFirstAid Seed & Validation Script — Design Spec | ||
|
|
||
| **Date:** 2026-04-15 | ||
| **Branch:** feature/load_testing | ||
| **Status:** Approved | ||
|
|
||
| --- | ||
|
|
||
| ## Goal | ||
|
|
||
| A Python script that creates a throwaway PostgreSQL database, seeds it with data that triggers every health check in `pgFirstAid.sql`, runs the function, and reports which checks fired vs. which were missing. Exit code 0 = all expected checks fired; exit code 1 = gaps found. | ||
|
|
||
| --- | ||
|
|
||
| ## Entry Point | ||
|
|
||
| **`testing/seed_and_validate.py`** | ||
|
|
||
| Single script. No third-party dependencies beyond `psycopg` (psycopg3). Invoked as: | ||
|
|
||
| ```bash | ||
| python testing/seed_and_validate.py [--host localhost] [--port 5432] [--user postgres] | ||
| ``` | ||
|
|
||
| Connection parameters default to standard env vars (`PGHOST`, `PGPORT`, `PGUSER`, `PGPASSWORD`) with CLI overrides. | ||
|
|
||
| --- | ||
|
|
||
| ## Database Lifecycle | ||
|
|
||
| 1. Connect to `postgres` (maintenance database) as superuser | ||
| 2. Drop `pgfirstaid_test` if it exists | ||
| 3. Create `pgfirstaid_test` | ||
| 4. Run all seeding against `pgfirstaid_test` | ||
| 5. Drop `pgfirstaid_test` on exit (success or failure) via a `finally` block | ||
|
|
||
| --- | ||
|
|
||
| ## Threshold Patching | ||
|
|
||
| `pgFirstAid.sql` contains thresholds that are impractical in a test environment. The script reads the file, applies regex substitutions in memory, and installs the patched function into the test DB. The original file is never modified. | ||
|
|
||
| | Check | Original threshold | Test threshold | | ||
| |---|---|---| | ||
| | Unused Large Index | `> 104857600` (100MB) | `> 8192` (8KB) | | ||
| | Tables larger than 100GB | `> 107374182400` | `> 1048576` (1MB) | | ||
| | Tables larger than 50GB | `between 53687091200 and 107374182400` | `between 524288 and 1048576` | | ||
|
|
||
| --- | ||
|
|
||
| ## SQL Seed Files | ||
|
|
||
| Located in `testing/healthcheck_seed/`. Each file is idempotent and targets the `pgfirstaid_seed` schema. | ||
|
|
||
| ### `01_seed_static_checks.sql` | ||
|
|
||
| Seeds all structural checks that fire from schema state alone: | ||
|
|
||
| | Check triggered | Seeded object | | ||
| |---|---| | ||
| | Missing Primary Key (CRITICAL) | Table with no PK | | ||
| | Unused Large Index (CRITICAL) | Index on a table that is never scanned, sized above 8KB threshold | | ||
| | Duplicate Index (HIGH) | Two identical indexes on the same table and columns | | ||
| | Table with more than 200 columns (HIGH) | Table with 201 columns | | ||
| | Missing Statistics (HIGH) | Table with >1000 inserts, never analyzed | | ||
| | Outdated Statistics (MEDIUM) | Table with dead tuples exceeding autovacuum threshold | | ||
| | Table with more than 50 columns (MEDIUM) | Table with 60 columns | | ||
| | Low Index Efficiency (MEDIUM) | Non-selective index scanned >100 times; each scan reads many tuples (seeded via a loop of 110 queries using a predicate that matches most rows) | | ||
| | Excessive Sequential Scans (MEDIUM) | Table with >1000 seq scans produced by a seeding loop of sequential scans against a large table | | ||
| | Missing FK Index (LOW) | Table with FK constraint and no supporting index | | ||
| | Table With Single Or No Columns (LOW) | Table with 1 column | | ||
| | Table With No Activity Since Stats Reset (LOW) | Table created but never read or written | | ||
| | Role Never Logged In (LOW) | Role with LOGIN created but never connected | | ||
| | Empty Table (LOW) | Table with 0 rows | | ||
| | Index With Very Low Usage (LOW) | Index with 1–99 scans and size > 1MB — seeded via a loop that scans the index a small number of times | | ||
|
|
||
| > **Note:** Low Index Efficiency requires `idx_scan > 100` and `idx_tup_read / idx_scan > 1000`. The seed loop runs 110 queries using a predicate that hits the indexed column but matches a large fraction of rows, so each scan reads thousands of tuples. | ||
|
|
||
| ### `02_seed_pg_stat_statements.sql` | ||
|
|
||
| Existing file. Seeds all pg_stat_statements checks. No changes needed. | ||
|
|
||
| --- | ||
|
|
||
| ## Live Session Strategy | ||
|
|
||
| Three background threads open `psycopg3` connections and hold them for the duration of the validation window. | ||
|
|
||
| | Thread | What it does | Checks triggered | | ||
| |---|---|---| | ||
| | **Blocker** | Opens `BEGIN`, runs `UPDATE` on a row, calls `pg_sleep(600)`, then `ROLLBACK` | Current Blocked/Blocking Queries, Lock-Wait-Heavy Active Queries | | ||
| | **Blocked** | Waits for blocker to establish, then attempts `UPDATE` on the same row | Current Blocked/Blocking Queries, Lock-Wait-Heavy Active Queries | | ||
| | **Idle-in-transaction** | Opens `BEGIN`, runs `SELECT 1`, then sleeps in Python for 6 minutes (transaction stays open) | Idle In Transaction Over 5 Minutes | | ||
| | **Long query** | Runs `SELECT pg_sleep(360)` | Long Running Queries (>5 min), Top 10 Expensive Active Queries (>30 sec) | | ||
|
|
||
| ### Startup sequencing | ||
|
|
||
| 1. Start Blocker thread; wait until its lock is confirmed held (poll `pg_locks`) | ||
| 2. Start Blocked thread; wait until it appears in `pg_stat_activity` with `wait_event_type = 'Lock'` | ||
| 3. Start Idle-in-transaction thread; wait until it appears in `pg_stat_activity` with `state = 'idle in transaction'` | ||
| 4. Start Long query thread; wait until it appears in `pg_stat_activity` with runtime > 30 seconds | ||
| 5. Proceed to validation | ||
|
|
||
| All threads are daemon threads. They are cancelled via `pg_terminate_backend()` during cleanup if they haven't exited naturally. | ||
|
|
||
| --- | ||
|
|
||
| ## Replication Slot Guard | ||
|
|
||
| ```python | ||
| try: | ||
| # Requires wal_level = logical and superuser | ||
| conn.execute("SELECT pg_create_logical_replication_slot('pgfirstaid_test_slot', 'test_decoding')") | ||
| # Slot is inactive by definition (no consumer attached) | ||
| # Triggers: Inactive Replication Slots (HIGH) | ||
| replication_slot_created = True | ||
| except psycopg.errors.ObjectNotInPrerequisiteState: | ||
| print("SKIP: wal_level != logical — replication slot checks not seeded") | ||
| replication_slot_created = False | ||
| except psycopg.errors.InsufficientPrivilege: | ||
| print("SKIP: insufficient privilege to create replication slot") | ||
| replication_slot_created = False | ||
| ``` | ||
|
|
||
| The slot is dropped during cleanup if it was created. | ||
|
|
||
| --- | ||
|
|
||
| ## Checks Not Seeded | ||
|
|
||
| | Check | Reason | | ||
| |---|---| | ||
| | High Connection Count (>50 active) | Requires 50+ concurrent connections — out of scope for a seed script; pgbench covers this (existing `07_pgbench_active_query.sql`) | | ||
| | Inactive Replication Slots Near Max WAL | Requires sustained WAL generation to push retained WAL near `safe_wal_size` — not deterministic in a test environment | | ||
| | shared_buffers At Default / work_mem At Default | These fire based on server config, not seeded data — always present on a default-configured server | | ||
| | Server Role (standby) | Always fires as INFO, content depends on actual server role | | ||
| | INFO checks (version, uptime, extensions, log size, etc.) | Always fire — no seeding needed | | ||
|
|
||
| --- | ||
|
|
||
| ## Validation | ||
|
|
||
| After the 6-minute idle-in-transaction window is established, run: | ||
|
|
||
| ```sql | ||
| SELECT check_name, count(*) AS findings | ||
| FROM pg_firstAid() | ||
| GROUP BY check_name | ||
| ORDER BY check_name; | ||
| ``` | ||
|
|
||
| Compare results against an expected set defined in the script. Print a table: | ||
|
|
||
| ``` | ||
| PASS Missing Primary Key | ||
| PASS Duplicate Index | ||
| PASS Idle In Transaction Over 5 Minutes | ||
| FAIL Replication Slots Near Max Wal Size (skipped — wal_level) | ||
| ... | ||
| ``` | ||
|
|
||
| Exit 0 if all non-skipped checks fired. Exit 1 if any non-skipped check produced 0 rows. | ||
|
|
||
| --- | ||
|
|
||
| ## File Layout | ||
|
|
||
| ``` | ||
| testing/ | ||
| seed_and_validate.py ← new: orchestrator | ||
| healthcheck_seed/ | ||
| 01_seed_static_checks.sql ← rewrite: full structural seed | ||
| 02_seed_pg_stat_statements.sql ← existing, unchanged | ||
| 03_session_blocker.sql ← kept for manual use | ||
| 04_session_blocked.sql ← kept for manual use | ||
| 05_session_idle_in_transaction.sql ← kept for manual use | ||
| 06_session_long_running_query.sql ← kept for manual use | ||
| 07_pgbench_active_query.sql ← kept for manual use | ||
| 99_validate_seed_results.sql ← kept for manual use | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## Dependencies | ||
|
|
||
| - Python 3.11+ | ||
| - `psycopg` (psycopg3): `pip install psycopg[binary]` | ||
| - PostgreSQL superuser access to the target server |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| [project] | ||
| name = "pgfirstaid-testing" | ||
| version = "0.1.0" | ||
| requires-python = ">=3.11" | ||
| dependencies = [ | ||
| "psycopg2-binary>=2.9.12", | ||
| "pytest>=8.0", | ||
| ] | ||
|
|
||
| [tool.pytest.ini_options] | ||
| testpaths = ["testing"] |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.