Skip to content

Commit c012fdc

Browse files
committed
fix(tests): self-install eql_v3 in property suites so CI shards pass (CIP-3141)
The fixture/e2e property oracles connect via connect_pool() to the base DATABASE_URL database, not through #[sqlx::test]'s migrated per-test scratch DBs. Under the sharded build-once-archive CI, a shard's base DB is a stock Postgres with no EQL installed (only build-archive ran `sqlx migrate run`, against a different Postgres), so every `::eql_v3.<T>_eq` cast raised `schema "eql_v3" does not exist`. The old per-version CI happened to install EQL into the base DB via test:sqlx:prep's migrate step, which the suites silently relied on; the rebase onto the new CI removed that, turning the suites red. (The failures looked type-specific — int2/int8/text/date/ timestamptz — but that was just nextest fail-fast + shard distribution; the error is type-agnostic, int4 included.) Make the suites self-sufficient: ensure_eql_installed() applies the embedded 001_install_eql.sql installer to the connected DB on first use, guarded by a once-per-process async mutex and an `eql_v3.int4_eq` presence check (so a developer's pre-installed local DB is left untouched — the installer is not idempotent). The installer is include_str!'d in the test target (mod.rs), the same archive-travel mechanism the fixture corpus uses, kept out of the lib so clippy/test:crates contexts don't need the generated file. Also stop swallowing the real Postgres error: the proptest runners rendered anyhow errors with {e}/to_string(), dropping the cause chain and leaving only the "pair query: SELECT …" context line — which is what made this opaque in CI. Render with {e:#} to keep the full chain. Verified: against a fresh DB with no eql_v3, all 11 fixture_oracle tests (including the ones that failed in CI) now pass and the surface is installed on demand; against an already-installed DB the presence check skips the install.
1 parent 07dd0f6 commit c012fdc

4 files changed

Lines changed: 78 additions & 5 deletions

File tree

tests/sqlx/src/property.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,52 @@ use tokio::sync::Mutex;
2222
/// `fixtures.eql_v2_<T>` table exactly once.
2323
static FIXTURE_LOADED: OnceLock<Mutex<HashSet<&'static str>>> = OnceLock::new();
2424

25+
/// Per-process guard ensuring the EQL surface (the `eql_v3` schema the oracle
26+
/// queries cast to) is installed into the connected DB exactly once.
27+
static EQL_INSTALLED: OnceLock<Mutex<bool>> = OnceLock::new();
28+
29+
/// Ensure the EQL surface (the `eql_v3` schema + scalar domains/operators the
30+
/// oracle queries cast to) is present in the DB behind `pool`.
31+
///
32+
/// The property suites connect via `connect_pool()` to the base test database
33+
/// (`DATABASE_URL`), NOT through `#[sqlx::test]`'s migrated per-test scratch
34+
/// DBs. In a CI shard that base DB is a stock Postgres with no EQL installed —
35+
/// only the `build-archive` job ran `sqlx migrate run`, and against a different
36+
/// Postgres — so every `::eql_v3.<T>_eq` cast would raise
37+
/// `schema "eql_v3" does not exist`. This installs the surface on demand so the
38+
/// suites are self-sufficient regardless of where they run (CI shard, local,
39+
/// fork), instead of silently depending on a pre-installed base DB.
40+
///
41+
/// `install_sql` is the EQL installer (`migrations/001_install_eql.sql`),
42+
/// `include_str!`-embedded into the test binary at compile time (see
43+
/// `property/mod.rs`) so it travels inside the prebuilt nextest archive — the
44+
/// same mechanism the fixture corpus uses. A process-wide async mutex
45+
/// guarantees exactly-once execution across the parallel proptest threads, and
46+
/// a presence check (`eql_v3.int4_eq`) skips the install when the DB already
47+
/// has the surface (a developer's pre-installed local DB), where re-running the
48+
/// non-idempotent installer would error on duplicate objects.
49+
pub async fn ensure_eql_installed(pool: &PgPool, install_sql: &str) -> Result<()> {
50+
let guard = EQL_INSTALLED.get_or_init(|| Mutex::new(false));
51+
let mut installed = guard.lock().await;
52+
if *installed {
53+
return Ok(());
54+
}
55+
// Presence check: skip the installer if the surface is already there. int4
56+
// is the reference scalar type and is always part of the surface.
57+
let present: bool = sqlx::query_scalar("SELECT to_regtype('eql_v3.int4_eq') IS NOT NULL")
58+
.fetch_one(pool)
59+
.await
60+
.context("probing for an existing eql_v3 install")?;
61+
if !present {
62+
sqlx::raw_sql(install_sql)
63+
.execute(pool)
64+
.await
65+
.context("installing the EQL surface into the property-test DB")?;
66+
}
67+
*installed = true;
68+
Ok(())
69+
}
70+
2571
/// Materialise the committed fixture corpus (real ciphertext) for `T` into the connected DB.
2672
///
2773
/// The fixture `.sql` files (`tests/sqlx/fixtures/eql_v2_<T>.sql`) are normally

tests/sqlx/tests/encrypted_domain/property/e2e_oracle.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use anyhow::Result;
1111
use eql_tests::fixtures::cipherstash::{column_config_for, encrypt_store};
1212
use eql_tests::fixtures::eql_plaintext::{Cast, EqlPlaintext};
1313
use eql_tests::fixtures::index_kind::IndexKind;
14-
use eql_tests::property::{assert_eq_oracle, assert_ord_oracle, connect_pool, Row};
14+
use eql_tests::property::{
15+
assert_eq_oracle, assert_ord_oracle, connect_pool, ensure_eql_installed, Row,
16+
};
1517
use eql_tests::scalar_domains::ScalarType;
1618
use eql_tests::scalar_domains::Variant;
1719
use proptest::prelude::*;
@@ -64,6 +66,9 @@ where
6466
.enable_all()
6567
.build()?;
6668
let pool: PgPool = rt.block_on(connect_pool())?;
69+
// The base DB this pool connects to is not migrated by `#[sqlx::test]`; in a
70+
// CI shard it has no `eql_v3` surface, so install it before any cast/query.
71+
rt.block_on(ensure_eql_installed(&pool, super::EQL_INSTALL_SQL))?;
6772

6873
// Shrinking is disabled for the e2e suite: every failed shrink attempt would
6974
// trigger another ZeroKMS batch, and ciphertext cannot be meaningfully
@@ -91,7 +96,9 @@ where
9196
values.push(dup1);
9297
let rows = rt
9398
.block_on(encrypt_rows::<T>(table, cast, &values))
94-
.map_err(|e| TestCaseError::fail(format!("encrypt: {e}")))?;
99+
// `{e:#}` keeps anyhow's full cause chain (the underlying error),
100+
// which a plain `{e}` would drop.
101+
.map_err(|e| TestCaseError::fail(format!("encrypt: {e:#}")))?;
95102
rt.block_on(async {
96103
assert_eq_oracle::<T>(&pool, &rows).await?;
97104
if ordered {
@@ -100,7 +107,7 @@ where
100107
}
101108
Ok::<_, anyhow::Error>(())
102109
})
103-
.map_err(|e| TestCaseError::fail(format!("oracle: {e}")))?;
110+
.map_err(|e| TestCaseError::fail(format!("oracle: {e:#}")))?;
104111
Ok(())
105112
})
106113
.map_err(|e| anyhow::anyhow!("e2e property failed: {e}"))

tests/sqlx/tests/encrypted_domain/property/fixture_oracle.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
1111
use anyhow::Result;
1212
use eql_tests::property::{
13-
assert_eq_oracle, assert_ord_oracle, connect_pool, ensure_fixture_loaded, Row,
13+
assert_eq_oracle, assert_ord_oracle, connect_pool, ensure_eql_installed, ensure_fixture_loaded,
14+
Row,
1415
};
1516
use eql_tests::scalar_domains::{ScalarType, Variant};
1617
use proptest::prelude::*;
@@ -63,6 +64,9 @@ fn embedded_fixture_sql<T: ScalarType>() -> &'static str {
6364
/// Ensures the corpus is present in the shared DB first (it lives in
6465
/// `#[sqlx::test]`'s ephemeral DBs by default, not the pool we connect to).
6566
async fn load_fixture_rows<T: ScalarType>(pool: &PgPool) -> Result<Vec<Row<T>>> {
67+
// The base DB this pool connects to is not migrated by `#[sqlx::test]`; in a
68+
// CI shard it has no `eql_v3` surface, so install it before any cast/query.
69+
ensure_eql_installed(pool, super::EQL_INSTALL_SQL).await?;
6670
ensure_fixture_loaded::<T>(pool, embedded_fixture_sql::<T>()).await?;
6771
let sql = format!(
6872
"SELECT plaintext, payload::text FROM {} ORDER BY id",
@@ -118,7 +122,10 @@ where
118122
.run(&strategy, |idxs| {
119123
let corpus = pick(&all, &idxs);
120124
rt.block_on(oracle(pool.clone(), corpus))
121-
.map_err(|e| TestCaseError::fail(e.to_string()))?;
125+
// `{e:#}` renders anyhow's full cause chain inline; plain
126+
// `to_string()` drops it, hiding the real Postgres error (e.g.
127+
// `schema "eql_v3" does not exist`) behind only the context line.
128+
.map_err(|e| TestCaseError::fail(format!("{e:#}")))?;
122129
Ok(())
123130
})
124131
.map_err(|e| anyhow::anyhow!("fixture property failed: {e}"))

tests/sqlx/tests/encrypted_domain/property/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,19 @@
44
//! `scalars::<X>::` test-name prefix, so a `scalars::property::…` test would be
55
//! mis-read as a scalar type and break the catalog cross-check.
66
7+
/// The EQL installer (`migrations/001_install_eql.sql`, the full release),
8+
/// embedded at compile time so the property suites can install the `eql_v3`
9+
/// surface into their base DB on demand (see `property::ensure_eql_installed`).
10+
/// Same embed-into-the-archive rationale as the per-type fixture corpus in
11+
/// `fixture_oracle.rs`: the file is produced by `test:sqlx:prep` before the
12+
/// nextest archive is built, and the CI shards run from that archive without a
13+
/// checkout of the gitignored generated SQL. The path resolves against the
14+
/// `eql_tests` crate root (`tests/sqlx`).
15+
pub(crate) const EQL_INSTALL_SQL: &str = include_str!(concat!(
16+
env!("CARGO_MANIFEST_DIR"),
17+
"/migrations/001_install_eql.sql"
18+
));
19+
720
// NULL / blocker / CHECK-constraint unit tests.
821
mod edge_cases;
922
// fixture suite: oracle over the committed fixture corpus (real ciphertext).

0 commit comments

Comments
 (0)