@biffo/cli 0.257.0 → 0.258.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/pg-test-db.sh +129 -3
package/package.json
CHANGED
package/scripts/pg-test-db.sh
CHANGED
|
@@ -79,6 +79,43 @@
|
|
|
79
79
|
# the fingerprinted schema (see step 2 below) instead of rebuilding it under a
|
|
80
80
|
# fresh identity every time. `BIFFO_PG_PORT`, `BIFFO_PG_DB`, and
|
|
81
81
|
# `BIFFO_PG_CONTAINER` remain explicit overrides; only the *default* changed.
|
|
82
|
+
#
|
|
83
|
+
# ## The case that key does NOT cover: two runs from ONE checkout
|
|
84
|
+
#
|
|
85
|
+
# Deriving from `$REPO_ROOT` isolates two *checkouts*. It cannot isolate two
|
|
86
|
+
# *runs of the same checkout*, because it is deterministic on purpose -- that
|
|
87
|
+
# determinism is what makes reuse work. So a developer running the suite while
|
|
88
|
+
# `git push` fires the pre-push gate gets two concurrent sessions against ONE
|
|
89
|
+
# database, and neither is doing anything wrong: the gate is automatic, and the
|
|
90
|
+
# developer never chose to run two things at once.
|
|
91
|
+
#
|
|
92
|
+
# What that costs is not a lost race, it is a MISATTRIBUTED one. Measured on
|
|
93
|
+
# 2026-08-09: a push gate reported `test_lead_unsubscribe_pg.py` failing with a
|
|
94
|
+
# foreign-key violation, in a file the change under test never touched, while a
|
|
95
|
+
# full suite from the same worktree was concurrently deleting and re-seeding the
|
|
96
|
+
# rows it depends on. The gate was believed, the change was suspected, and the
|
|
97
|
+
# database had to be recreated before the red would clear.
|
|
98
|
+
#
|
|
99
|
+
# Per-test-file tenant namespaces (see `setup_pg`'s advisory-lock note) make ONE
|
|
100
|
+
# run internally parallel-safe. They do nothing here, because the colliding
|
|
101
|
+
# writers are the SAME file run twice.
|
|
102
|
+
#
|
|
103
|
+
# ## The fix, and why it is a clone rather than a lock
|
|
104
|
+
#
|
|
105
|
+
# The fingerprinted database is now a TEMPLATE, never handed out. Each run gets
|
|
106
|
+
# its own `..._r<key>` clone via `CREATE DATABASE ... TEMPLATE`, and the DSN
|
|
107
|
+
# points at that.
|
|
108
|
+
#
|
|
109
|
+
# This keeps the principle the port/name key already established -- make the
|
|
110
|
+
# value unique rather than make users take turns -- and it is affordable because
|
|
111
|
+
# a template clone is a file copy the server does itself: **0.10s for a 25 MB
|
|
112
|
+
# schema**, measured on the tabsii-platform lane, against the ~0.3s reuse path
|
|
113
|
+
# this script already advertises as fast. A `flock` would instead have cost the
|
|
114
|
+
# pushing developer the full runtime of whatever else was running.
|
|
115
|
+
#
|
|
116
|
+
# `BIFFO_PG_SHARED=1` opts out and returns the template directly, for the case
|
|
117
|
+
# that genuinely wants a stable name across invocations -- attaching a psql
|
|
118
|
+
# session to inspect what a failing run left behind.
|
|
82
119
|
|
|
83
120
|
set -eu
|
|
84
121
|
|
|
@@ -102,7 +139,21 @@ HOST="${BIFFO_PG_HOST:-localhost}"
|
|
|
102
139
|
PORT="${BIFFO_PG_PORT:-$_checkout_port}"
|
|
103
140
|
USER_="${BIFFO_PG_USER:-postgres}"
|
|
104
141
|
PASS="${BIFFO_PG_PASSWORD:-postgres}"
|
|
142
|
+
# The TEMPLATE: fingerprinted, rebuilt only when the schema inputs change, and
|
|
143
|
+
# never handed to a caller unless sharing is requested. An explicit BIFFO_PG_DB
|
|
144
|
+
# still names it, so that override keeps meaning what it always did.
|
|
105
145
|
DB="${BIFFO_PG_DB:-biffo_test_$_checkout_suffix}"
|
|
146
|
+
|
|
147
|
+
# Opt out of per-run cloning (see the concurrency note above). Naming a database
|
|
148
|
+
# explicitly implies it: BIFFO_PG_DB is a request for THAT database.
|
|
149
|
+
SHARED="${BIFFO_PG_SHARED:-0}"
|
|
150
|
+
[ -n "${BIFFO_PG_DB:-}" ] && SHARED=1
|
|
151
|
+
|
|
152
|
+
# Minutes an abandoned clone survives before a later run reaps it. Generous on
|
|
153
|
+
# purpose: a clone with no connections may simply be between `--export` and the
|
|
154
|
+
# first test connecting, and dropping one out from under a caller is a worse
|
|
155
|
+
# failure than leaving a few megabytes on disk.
|
|
156
|
+
CLONE_TTL_MIN="${BIFFO_PG_CLONE_TTL_MIN:-240}"
|
|
106
157
|
# Keyed the same way as PORT and for the same reason: the container is where
|
|
107
158
|
# the port mapping actually lives (`docker run -p "$PORT:5432"`), so if the
|
|
108
159
|
# container name stayed fixed while the port became per-checkout, a second
|
|
@@ -243,14 +294,76 @@ if ! psql_admin -c 'SELECT 1' >/dev/null 2>&1; then
|
|
|
243
294
|
fi
|
|
244
295
|
|
|
245
296
|
DSN="postgresql+asyncpg://$USER_:$PASS@$HOST:$PORT/$DB"
|
|
297
|
+
|
|
298
|
+
# What the caller is handed. Rewritten to a per-run clone below unless sharing
|
|
299
|
+
# was requested; `DSN` keeps naming the template, because that is what the
|
|
300
|
+
# alembic/DDL build steps must connect to.
|
|
301
|
+
RUN_DB="$DB"
|
|
302
|
+
RUN_DSN="$DSN"
|
|
303
|
+
|
|
304
|
+
# ── Per-run clone ────────────────────────────────────────────────────────────
|
|
305
|
+
#
|
|
306
|
+
# Called after the template is known-good, so a clone can never predate the
|
|
307
|
+
# schema it is supposed to carry.
|
|
308
|
+
clone_for_this_run() {
|
|
309
|
+
[ "$SHARED" -eq 1 ] && {
|
|
310
|
+
say "BIFFO_PG_SHARED - using the template $DB directly, NOT isolated from a concurrent run"
|
|
311
|
+
return 0
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
# Reap first, so a long-lived checkout does not accumulate clones forever.
|
|
315
|
+
# Only ones with no backends AND older than the TTL: `datconnlimit`-style
|
|
316
|
+
# liveness alone would drop a clone in the gap between `--export` and the
|
|
317
|
+
# first test connecting.
|
|
318
|
+
# Both ways this query can fail degrade to NOT reaping, which is the safe
|
|
319
|
+
# direction — it leaks disk rather than dropping a database out from under a
|
|
320
|
+
# live caller. `pg_stat_file` needs superuser (or pg_read_server_files), which
|
|
321
|
+
# the container has and a managed server may not; and a database in a custom
|
|
322
|
+
# tablespace is not under `base/`, so the file is missing and `missing_ok`
|
|
323
|
+
# returns NULL, which COALESCE turns into "not old enough".
|
|
324
|
+
_stale=$(psql_admin -tAc "
|
|
325
|
+
SELECT d.datname
|
|
326
|
+
FROM pg_database d
|
|
327
|
+
WHERE d.datname LIKE '${DB}_r%'
|
|
328
|
+
AND NOT EXISTS (SELECT 1 FROM pg_stat_activity a WHERE a.datname = d.datname)
|
|
329
|
+
AND COALESCE((pg_stat_file('base/' || d.oid || '/PG_VERSION', true)).modification,
|
|
330
|
+
now()) < now() - interval '$CLONE_TTL_MIN minutes'
|
|
331
|
+
" 2>/dev/null || true)
|
|
332
|
+
for _old in $_stale; do
|
|
333
|
+
psql_admin -c "DROP DATABASE IF EXISTS \"$_old\" WITH (FORCE)" >/dev/null 2>&1 || true
|
|
334
|
+
say "reaped abandoned clone $_old"
|
|
335
|
+
done
|
|
336
|
+
|
|
337
|
+
# $$ is this shell; the seconds make a second run in the same second (or a
|
|
338
|
+
# recycled pid) distinct. Short enough to read in a psql prompt.
|
|
339
|
+
_run_key=$(printf '%s%s' "$$" "$(date +%s)" | sha256sum | cut -c1-8)
|
|
340
|
+
RUN_DB="${DB}_r${_run_key}"
|
|
341
|
+
|
|
342
|
+
# WITH (FORCE) so a previous clone under the same name (only possible if the
|
|
343
|
+
# key collided) cannot wedge this run behind someone else's idle session.
|
|
344
|
+
psql_admin -c "DROP DATABASE IF EXISTS \"$RUN_DB\" WITH (FORCE)" >/dev/null 2>&1 || true
|
|
345
|
+
if ! psql_admin -c "CREATE DATABASE \"$RUN_DB\" TEMPLATE \"$DB\"" >/dev/null 2>&1; then
|
|
346
|
+
# The one failure mode worth naming: CREATE DATABASE ... TEMPLATE refuses
|
|
347
|
+
# while any session is connected to the template. That means something is
|
|
348
|
+
# using the template directly -- almost always a psql attached by hand, or a
|
|
349
|
+
# run started before this change with an exported DSN.
|
|
350
|
+
say "could not clone $DB - is something still connected to it?"
|
|
351
|
+
psql_admin -tAc "SELECT DISTINCT usename FROM pg_stat_activity WHERE datname = '$DB'" 2>/dev/null |
|
|
352
|
+
while read -r _u; do [ -n "$_u" ] && say " template in use by: $_u"; done
|
|
353
|
+
say "disconnect it, or set BIFFO_PG_SHARED=1 to use the template directly"
|
|
354
|
+
exit 1
|
|
355
|
+
fi
|
|
356
|
+
RUN_DSN="postgresql+asyncpg://$USER_:$PASS@$HOST:$PORT/$RUN_DB"
|
|
357
|
+
say "cloned $DB -> $RUN_DB (isolated from any concurrent run)"
|
|
358
|
+
}
|
|
246
359
|
emit() {
|
|
247
360
|
if [ "$EXPORT" -eq 1 ]; then
|
|
248
361
|
# Both names, deliberately (tabsii-platform#755): whichever a consumer
|
|
249
362
|
# reads, an `eval` of this line alone is enough -- see the Usage note above.
|
|
250
|
-
echo "export BIFFO_TEST_PG_DSN='$
|
|
251
|
-
echo "export TABSII_TEST_PG_DSN='$
|
|
363
|
+
echo "export BIFFO_TEST_PG_DSN='$RUN_DSN'"
|
|
364
|
+
echo "export TABSII_TEST_PG_DSN='$RUN_DSN'"
|
|
252
365
|
else
|
|
253
|
-
echo "$
|
|
366
|
+
echo "$RUN_DSN"
|
|
254
367
|
fi
|
|
255
368
|
}
|
|
256
369
|
|
|
@@ -277,6 +390,7 @@ fi
|
|
|
277
390
|
|
|
278
391
|
if [ -n "$HAVE" ] && [ "$HAVE" = "$WANT" ]; then
|
|
279
392
|
say "schema is current, reusing $DB"
|
|
393
|
+
clone_for_this_run
|
|
280
394
|
emit
|
|
281
395
|
exit 0
|
|
282
396
|
fi
|
|
@@ -285,6 +399,13 @@ fi
|
|
|
285
399
|
|
|
286
400
|
# --- 3. rebuild the way the app and CI do ------------------------------------
|
|
287
401
|
say "rebuilding $DB"
|
|
402
|
+
# Every existing clone carries the OLD schema, so they are stale by definition
|
|
403
|
+
# the moment the template is rebuilt. Dropping them here is also what makes
|
|
404
|
+
# `CREATE DATABASE ... TEMPLATE` possible afterwards without waiting out the TTL.
|
|
405
|
+
for _c in $(psql_admin -tAc "SELECT datname FROM pg_database WHERE datname LIKE '${DB}_r%'" 2>/dev/null || true); do
|
|
406
|
+
psql_admin -c "DROP DATABASE IF EXISTS \"$_c\" WITH (FORCE)" >/dev/null 2>&1 || true
|
|
407
|
+
say "dropped clone $_c of the previous schema"
|
|
408
|
+
done
|
|
288
409
|
psql_admin -c "DROP DATABASE IF EXISTS $DB WITH (FORCE)" >/dev/null
|
|
289
410
|
psql_admin -c "CREATE DATABASE $DB" >/dev/null
|
|
290
411
|
|
|
@@ -331,5 +452,10 @@ psql_db \
|
|
|
331
452
|
-c "TRUNCATE biffo_pg_test_fingerprint" \
|
|
332
453
|
-c "INSERT INTO biffo_pg_test_fingerprint (value) VALUES ('$WANT')" >/dev/null
|
|
333
454
|
|
|
455
|
+
# Only now, with the fingerprint recorded against a schema that passed the
|
|
456
|
+
# check above, is the template fit to copy. Cloning earlier would hand out a
|
|
457
|
+
# half-built database and record the failure against whoever ran next.
|
|
458
|
+
clone_for_this_run
|
|
459
|
+
|
|
334
460
|
say "ready"
|
|
335
461
|
emit
|