Skip to content

Commit 9bd00cf

Browse files
authored
Merge pull request #138 from cipherstash/feature/sqlx-infrastructure
feat(testing): add SQLx/Rust test framework infrastructure
2 parents 9524f33 + 7d036c7 commit 9bd00cf

27 files changed

+3670
-3
lines changed

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,7 @@ release/
206206
__pycache__
207207

208208
# dbdev
209-
eql--*.sql
209+
eql--*.sql
210+
211+
# Generated SQLx migration (built from src/, never commit)
212+
tests/sqlx/migrations/001_install_eql.sql

docs/assertion-counts.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Assertion Count Report
2+
3+
Generated: 2025-10-24
4+
5+
# SQL Test Assertions
6+
7+
| File | ASSERT | PERFORM assert_* | SELECT checks | Total |
8+
|------|--------|------------------|---------------|-------|
9+
| `src/blake3/compare_test.sql` | 9 | 0 | 0 | 9 |
10+
| `src/bloom_filter/functions_test.sql` | 0 | 2 | 0 | 2 |
11+
| `src/config/config_test.sql` | 26 | 4 | 11 | 41 |
12+
| `src/encrypted/aggregates_test.sql` | 4 | 0 | 2 | 6 |
13+
| `src/encrypted/constraints_test.sql` | 0 | 6 | 0 | 6 |
14+
| `src/encryptindex/functions_test.sql` | 21 | 1 | 19 | 41 |
15+
| `src/hmac_256/compare_test.sql` | 9 | 0 | 0 | 9 |
16+
| `src/hmac_256/functions_test.sql` | 1 | 2 | 0 | 3 |
17+
| `src/jsonb/functions_test.sql` | 4 | 24 | 0 | 28 |
18+
| `src/operators/->>_test.sql` | 0 | 6 | 0 | 6 |
19+
| `src/operators/->_test.sql` | 2 | 9 | 0 | 11 |
20+
| `src/operators/<=_ore_cllw_u64_8_test.sql` | 0 | 3 | 3 | 6 |
21+
| `src/operators/<=_ore_cllw_var_8_test.sql` | 0 | 3 | 3 | 6 |
22+
| `src/operators/<=_test.sql` | 0 | 8 | 4 | 12 |
23+
| `src/operators/<>_ore_cllw_u64_8_test.sql` | 0 | 3 | 0 | 3 |
24+
| `src/operators/<>_ore_cllw_var_8_test.sql` | 0 | 3 | 0 | 3 |
25+
| `src/operators/<>_ore_test.sql` | 0 | 8 | 0 | 8 |
26+
| `src/operators/<>_test.sql` | 0 | 12 | 2 | 14 |
27+
| `src/operators/<@_test.sql` | 0 | 2 | 0 | 2 |
28+
| `src/operators/<_test.sql` | 0 | 12 | 1 | 13 |
29+
| `src/operators/=_ore_cllw_u64_8_test.sql` | 0 | 3 | 3 | 6 |
30+
| `src/operators/=_ore_cllw_var_8_test.sql` | 0 | 3 | 3 | 6 |
31+
| `src/operators/=_ore_test.sql` | 0 | 8 | 4 | 12 |
32+
| `src/operators/=_test.sql` | 0 | 16 | 12 | 28 |
33+
| `src/operators/>=_test.sql` | 0 | 14 | 10 | 24 |
34+
| `src/operators/>_test.sql` | 0 | 12 | 1 | 13 |
35+
| `src/operators/@>_test.sql` | 4 | 2 | 0 | 6 |
36+
| `src/operators/compare_test.sql` | 63 | 0 | 0 | 63 |
37+
| `src/operators/operator_class_test.sql` | 16 | 1 | 24 | 41 |
38+
| `src/operators/order_by_test.sql` | 0 | 16 | 4 | 20 |
39+
| `src/operators/~~_test.sql` | 0 | 10 | 0 | 10 |
40+
| `src/ore_block_u64_8_256/compare_test.sql` | 9 | 0 | 0 | 9 |
41+
| `src/ore_block_u64_8_256/functions_test.sql` | 1 | 5 | 2 | 8 |
42+
| `src/ore_cllw_u64_8/compare_test.sql` | 9 | 0 | 0 | 9 |
43+
| `src/ore_cllw_var_8/compare_test.sql` | 9 | 0 | 0 | 9 |
44+
| `src/ore_cllw_var_8/functions_test.sql` | 0 | 0 | 0 | 0 |
45+
| `src/ste_vec/functions_test.sql` | 18 | 0 | 0 | 18 |
46+
| `src/version_test.sql` | 0 | 1 | 1 | 2 |
47+
48+
**Total SQL assertions:** 513 across 38 files
49+
50+
# Rust Test Assertions
51+
52+
| File | assert* | expect* | is_err/is_ok | Total |
53+
|------|---------|---------|--------------|-------|
54+
| `rust-tests/tests/add_column_test.rs` | 0 | 0 | 0 | 0 |
55+
| `rust-tests/tests/test_helpers_test.rs` | 0 | 0 | 0 | 0 |
56+
57+
**Total Rust assertions:** 0 across 2 files

docs/test-inventory.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# Test Inventory - SQL to SQLx Migration
2+
3+
Generated: 2025-10-24
4+
5+
| # | SQL Test File | Test Cases | Lines | Status | Rust Test File | Notes |
6+
|---|---------------|------------|-------|--------|----------------|-------|
7+
| 1 | `src/blake3/compare_test.sql` | 1 | 26 | ❌ TODO | `*TBD*` | |
8+
| 2 | `src/bloom_filter/functions_test.sql` | 1 | 14 | ❌ TODO | `*TBD*` | |
9+
| 3 | `src/config/config_test.sql` | 9 | 331 | ❌ TODO | `*TBD*` | |
10+
| 4 | `src/encrypted/aggregates_test.sql` | 1 | 50 | ❌ TODO | `*TBD*` | |
11+
| 5 | `src/encrypted/constraints_test.sql` | 3 | 79 | ❌ TODO | `*TBD*` | |
12+
| 6 | `src/encryptindex/functions_test.sql` | 7 | 290 | ❌ TODO | `*TBD*` | |
13+
| 7 | `src/hmac_256/compare_test.sql` | 1 | 26 | ❌ TODO | `*TBD*` | |
14+
| 8 | `src/hmac_256/functions_test.sql` | 2 | 26 | ❌ TODO | `*TBD*` | |
15+
| 9 | `src/jsonb/functions_test.sql` | 12 | 338 | ❌ TODO | `*TBD*` | |
16+
| 10 | `src/operators/->>_test.sql` | 4 | 68 | ❌ TODO | `*TBD*` | |
17+
| 11 | `src/operators/->_test.sql` | 6 | 118 | ❌ TODO | `*TBD*` | |
18+
| 12 | `src/operators/<=_ore_cllw_u64_8_test.sql` | 1 | 56 | ❌ TODO | `*TBD*` | |
19+
| 13 | `src/operators/<=_ore_cllw_var_8_test.sql` | 1 | 52 | ❌ TODO | `*TBD*` | |
20+
| 14 | `src/operators/<=_test.sql` | 2 | 83 | ❌ TODO | `*TBD*` | |
21+
| 15 | `src/operators/<>_ore_cllw_u64_8_test.sql` | 1 | 56 | ❌ TODO | `*TBD*` | |
22+
| 16 | `src/operators/<>_ore_cllw_var_8_test.sql` | 1 | 55 | ❌ TODO | `*TBD*` | |
23+
| 17 | `src/operators/<>_ore_test.sql` | 2 | 86 | ❌ TODO | `*TBD*` | |
24+
| 18 | `src/operators/<>_test.sql` | 5 | 164 | ❌ TODO | `*TBD*` | |
25+
| 19 | `src/operators/<@_test.sql` | 1 | 43 | ❌ TODO | `*TBD*` | |
26+
| 20 | `src/operators/<_test.sql` | 4 | 158 | ❌ TODO | `*TBD*` | |
27+
| 21 | `src/operators/=_ore_cllw_u64_8_test.sql` | 1 | 55 | ❌ TODO | `*TBD*` | |
28+
| 22 | `src/operators/=_ore_cllw_var_8_test.sql` | 1 | 52 | ❌ TODO | `*TBD*` | |
29+
| 23 | `src/operators/=_ore_test.sql` | 2 | 86 | ❌ TODO | `*TBD*` | |
30+
| 24 | `src/operators/=_test.sql` | 6 | 195 | ❌ TODO | `*TBD*` | |
31+
| 25 | `src/operators/>=_test.sql` | 4 | 174 | ❌ TODO | `*TBD*` | |
32+
| 26 | `src/operators/>_test.sql` | 4 | 158 | ❌ TODO | `*TBD*` | |
33+
| 27 | `src/operators/@>_test.sql` | 3 | 93 | ❌ TODO | `*TBD*` | |
34+
| 28 | `src/operators/compare_test.sql` | 7 | 207 | ❌ TODO | `*TBD*` | |
35+
| 29 | `src/operators/operator_class_test.sql` | 3 | 239 | ❌ TODO | `*TBD*` | |
36+
| 30 | `src/operators/order_by_test.sql` | 3 | 148 | ❌ TODO | `*TBD*` | |
37+
| 31 | `src/operators/~~_test.sql` | 3 | 107 | ❌ TODO | `*TBD*` | |
38+
| 32 | `src/ore_block_u64_8_256/compare_test.sql` | 1 | 27 | ❌ TODO | `*TBD*` | |
39+
| 33 | `src/ore_block_u64_8_256/functions_test.sql` | 3 | 58 | ❌ TODO | `*TBD*` | |
40+
| 34 | `src/ore_cllw_u64_8/compare_test.sql` | 1 | 29 | ❌ TODO | `*TBD*` | |
41+
| 35 | `src/ore_cllw_var_8/compare_test.sql` | 1 | 29 | ❌ TODO | `*TBD*` | |
42+
| 36 | `src/ore_cllw_var_8/functions_test.sql` | 0 | 0 | ❌ TODO | `*TBD*` | |
43+
| 37 | `src/ste_vec/functions_test.sql` | 6 | 132 | ❌ TODO | `*TBD*` | |
44+
| 38 | `src/version_test.sql` | 1 | 9 | ❌ TODO | `*TBD*` | |
45+
46+
## Summary
47+
48+
- **Total SQL Test Files:** 38
49+
- **Total Test Cases:** 115
50+
- **Total Lines:** 3917
51+
52+
## Usage
53+
54+
Update this inventory as you port tests:
55+
1. Mark status ✅ when Rust test passes
56+
2. Add Rust test file path
57+
3. Add notes for any deviations
58+
59+
Regenerate: `./tools/generate-test-inventory.sh`

docs/test-migration-guide.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# SQL to SQLx Test Migration Guide
2+
3+
This guide explains how to port SQL tests to Rust/SQLx while ensuring complete coverage.
4+
5+
## Overview
6+
7+
We have three tools to track coverage during migration:
8+
9+
1. **Test Inventory** - Which tests have been ported
10+
2. **Assertion Counts** - How many test assertions ported
11+
3. **Function Call Tracking** - Which SQL functions are tested
12+
13+
## Workflow
14+
15+
### Before Starting Migration
16+
17+
1. **Generate baseline coverage:**
18+
19+
```bash
20+
# Enable function tracking
21+
psql postgres://cipherstash:password@localhost:7432/postgres -c "ALTER SYSTEM SET track_functions = 'all';"
22+
psql postgres://cipherstash:password@localhost:7432/postgres -c "SELECT pg_reload_conf();"
23+
24+
# Reset stats and run SQL tests
25+
psql postgres://cipherstash:password@localhost:7432/postgres -c "SELECT pg_stat_reset();"
26+
mise run test
27+
28+
# Capture baseline
29+
TEST_TYPE=sql ./tools/track-function-calls.sh sql-function-calls.json
30+
./tools/count-assertions.sh
31+
./tools/generate-test-inventory.sh
32+
```
33+
34+
2. **Review baseline metrics:**
35+
- 38 SQL test files
36+
- ~3,917 lines of tests
37+
- ~513 assertions
38+
- Function call coverage saved in `sql-function-calls.json`
39+
40+
### During Migration
41+
42+
For each SQL test file you port:
43+
44+
1. **Pick a test from inventory** (`docs/test-inventory.md`)
45+
46+
2. **Read the SQL test**, understand what it tests
47+
48+
3. **Write equivalent Rust test** in `rust-tests/tests/`
49+
50+
4. **Run the Rust test:**
51+
```bash
52+
cd rust-tests
53+
cargo test <test_name> -- --nocapture
54+
```
55+
56+
5. **Verify function coverage:**
57+
```bash
58+
psql postgres://cipherstash:password@localhost:7432/postgres -c "SELECT pg_stat_reset();"
59+
cd rust-tests && cargo test
60+
TEST_TYPE=rust ./tools/track-function-calls.sh rust-function-calls.json
61+
./tools/compare-function-calls.sh sql-function-calls.json rust-function-calls.json
62+
```
63+
64+
6. **Update test inventory:**
65+
```bash
66+
./tools/generate-test-inventory.sh
67+
```
68+
69+
Manually edit `docs/test-inventory.md` to mark test as ✅ Ported.
70+
71+
7. **Check assertion coverage:**
72+
```bash
73+
./tools/compare-assertions.sh
74+
```
75+
76+
8. **Commit** when test passes and coverage verified:
77+
```bash
78+
git add rust-tests/tests/<test_file>.rs docs/test-inventory.md
79+
git commit -m "test: port <feature> tests from SQL to SQLx"
80+
```
81+
82+
### After Migration Complete
83+
84+
1. **Verify 100% coverage:**
85+
```bash
86+
./tools/check-test-coverage.sh
87+
```
88+
89+
2. **Ensure all checks pass:**
90+
- ✅ All 38 tests marked as ported
91+
- ✅ Rust assertion count ≥ SQL assertion count
92+
- ✅ Rust function calls cover same functions as SQL
93+
94+
3. **Delete SQL tests** (only after verification):
95+
```bash
96+
# DO NOT DO THIS UNTIL MIGRATION COMPLETE
97+
git rm src/**/*_test.sql
98+
```
99+
100+
## Coverage Metrics Explained
101+
102+
### Test Inventory
103+
104+
Shows 1:1 mapping of SQL test files to Rust test files:
105+
106+
- **Status ❌ TODO** - Not yet ported
107+
- **Status ✅ Ported** - Rust equivalent exists and passes
108+
109+
### Assertion Counts
110+
111+
Tracks test thoroughness:
112+
113+
- **SQL:** ASSERT statements, PERFORM assert_*, SELECT checks
114+
- **Rust:** assert!(), assert_eq!(), .expect()
115+
116+
Goal: Rust count ≥ SQL count
117+
118+
### Function Call Tracking
119+
120+
Ensures same code paths exercised:
121+
122+
- Uses PostgreSQL `pg_stat_user_functions`
123+
- Compares which `eql_v2.*` functions called in SQL vs Rust tests
124+
- Identifies gaps: functions only in SQL tests = missing coverage
125+
126+
## Troubleshooting
127+
128+
**Q: Rust test passes but function coverage shows missing functions?**
129+
130+
A: Your test might not be exercising the same code paths. Review the SQL test to see which functions it calls.
131+
132+
**Q: Assertion count much lower in Rust?**
133+
134+
A: You may be using fewer, but more comprehensive assertions. That's OK if function coverage matches. Document in test inventory notes.
135+
136+
**Q: pg_stat_user_functions not tracking?**
137+
138+
A: Verify `track_functions = 'all'` in postgresql.conf and PostgreSQL reloaded.
139+
140+
## Tips
141+
142+
- **Port tests in related groups** (e.g., all operator tests together)
143+
- **Keep both test suites running** until migration complete
144+
- **Update inventory frequently** to track progress
145+
- **Compare function coverage often** to catch gaps early
146+
147+
## See Also
148+
149+
- `tools/check-test-coverage.sh` - Run all coverage checks
150+
- `docs/test-inventory.md` - Current migration status
151+
- `docs/assertion-counts.md` - Detailed assertion breakdown

mise.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
# "./tests/mise.tls.toml",
88
# ]
99
[task_config]
10-
includes = ["tasks", "tasks/postgres.toml"]
10+
includes = ["tasks", "tasks/postgres.toml", "tasks/rust.toml"]
1111

1212
[env]
1313
POSTGRES_DB = "cipherstash"

tasks/rust.toml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
["test:sqlx"]
2+
description = "Run SQLx tests with hybrid migration approach"
3+
dir = "{{config_root}}"
4+
env = { DATABASE_URL = "postgresql://{{get_env(name='POSTGRES_USER', default='cipherstash')}}:{{get_env(name='POSTGRES_PASSWORD', default='password')}}@{{get_env(name='POSTGRES_HOST', default='localhost')}}:{{get_env(name='POSTGRES_PORT', default='7432')}}/{{get_env(name='POSTGRES_DB', default='cipherstash')}}" }
5+
run = """
6+
# Build EQL SQL from source
7+
echo "Building EQL SQL..."
8+
mise run build --force
9+
10+
# Copy built SQL to SQLx migrations (EQL install is generated, not static)
11+
echo "Updating SQLx migrations with built EQL..."
12+
cp release/cipherstash-encrypt.sql tests/sqlx/migrations/001_install_eql.sql
13+
14+
# Ensure PostgreSQL is running
15+
echo "Starting PostgreSQL..."
16+
mise run postgres:up --extra-args "--detach --wait"
17+
18+
# Run SQLx migrations and tests
19+
echo "Running SQLx migrations..."
20+
cd tests/sqlx
21+
sqlx migrate run
22+
23+
echo "Running Rust tests..."
24+
cargo test
25+
"""
26+
27+
["test:sqlx:watch"]
28+
description = "Run SQLx tests in watch mode (rebuild EQL on changes)"
29+
dir = "{{config_root}}/tests/sqlx"
30+
run = """
31+
cargo watch -x test
32+
"""

tests/docker-compose.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
services:
22
postgres: &postgres
33
container_name: postgres
4-
image: postgres
4+
image: postgres:17
5+
command: postgres -c track_functions=all
56
ports:
67
- 7432:7432
78
environment:

0 commit comments

Comments
 (0)