From b8576df9c7d5d205b4356db1315b8eef5c5fc211 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Mon, 27 Jul 2026 13:55:10 +0000 Subject: [PATCH 1/6] acc/invariant: share config render and deploy prologue across all targets no_drift, migrate, continue_293, delete_idempotent and destroy_idempotent each carried their own copy of the config render (data copy, init script, envsubst), the destroy-on-exit trap, and the deploy + panic-scan + INPUT_CONFIG_OK sequence. Move all of it into acceptance/bundle/invariant/script.prepare, which the harness picks up for the whole subtree, so each script holds only the invariant it asserts. The helpers are functions rather than prepare-time side effects because the harness concatenates script.prepare outside the subshell it wraps the script in, so a trap installed at prepare time would belong to the outer shell. invariant_deploy takes the deploy log name as its first argument, since the targets log to LOG.deploy, LOG.deploy_initial and LOG.deploy.293. Incidental deltas, all confined to LOG* files (logged, not compared): continue_293 gains LOG.config and a traced cleanup destroy, and destroy_idempotent's trap logs to LOG.destroy rather than LOG.destroy_final. --- .../bundle/invariant/continue_293/script | 26 +--------- .../bundle/invariant/delete_idempotent/script | 32 +------------ .../invariant/destroy_idempotent/script | 32 +------------ acceptance/bundle/invariant/migrate/script | 31 +----------- acceptance/bundle/invariant/no_drift/script | 33 +------------ acceptance/bundle/invariant/script.prepare | 48 +++++++++++++++++++ 6 files changed, 58 insertions(+), 144 deletions(-) create mode 100644 acceptance/bundle/invariant/script.prepare diff --git a/acceptance/bundle/invariant/continue_293/script b/acceptance/bundle/invariant/continue_293/script index cd0e57ba9b..544056b474 100644 --- a/acceptance/bundle/invariant/continue_293/script +++ b/acceptance/bundle/invariant/continue_293/script @@ -1,32 +1,10 @@ # Invariant to test: current CLI can deploy on top of state produced by v0.293.0 -cp -r "$TESTDIR/../data/." . &> LOG.cp - -INIT_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-init.sh" -if [ -f "$INIT_SCRIPT" ]; then - source "$INIT_SCRIPT" &> LOG.init -fi - -envsubst < "$TESTDIR/../configs/$INPUT_CONFIG" > databricks.yml - -cleanup() { - $CLI bundle destroy --auto-approve &> LOG.destroy - cat LOG.destroy | contains.py '!panic:' '!internal error' > /dev/null - - CLEANUP_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-cleanup.sh" - if [ -f "$CLEANUP_SCRIPT" ]; then - source "$CLEANUP_SCRIPT" &> LOG.cleanup - fi -} - -trap cleanup EXIT +invariant_setup # Deploy with old CLI to produce v0.293.0 state trace $CLI_293 --version -$CLI_293 bundle deploy &> LOG.deploy.293 -cat LOG.deploy.293 | contains.py '!panic:' '!internal error' > /dev/null - -echo INPUT_CONFIG_OK +invariant_deploy LOG.deploy.293 $CLI_293 bundle deploy # Deploy with current CLI on top of old state $CLI bundle deploy &> LOG.deploy diff --git a/acceptance/bundle/invariant/delete_idempotent/script b/acceptance/bundle/invariant/delete_idempotent/script index e21c2887f0..aba945dbb2 100644 --- a/acceptance/bundle/invariant/delete_idempotent/script +++ b/acceptance/bundle/invariant/delete_idempotent/script @@ -2,36 +2,13 @@ # After a delete succeeds, restoring the pre-delete state and running deploy again # must succeed even though the resources are already gone on the server. -# Copy data files to test directory -cp -r "$TESTDIR/../data/." . &> LOG.cp - -# Run init script if present -INIT_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-init.sh" -if [ -f "$INIT_SCRIPT" ]; then - source "$INIT_SCRIPT" &> LOG.init -fi - -envsubst < $TESTDIR/../configs/$INPUT_CONFIG > databricks.yml - -cp databricks.yml LOG.config +invariant_setup # All configs use `test-bundle-$UNIQUE_NAME`; workspace root/state paths follow # the default `~/.bundle//` template with target=default. bundle_name=test-bundle-$UNIQUE_NAME STATE_PATH=/Workspace/Users/$CURRENT_USER_NAME/.bundle/$bundle_name/default/state -cleanup() { - trace $CLI bundle destroy --auto-approve &> LOG.destroy - cat LOG.destroy | contains.py '!panic:' '!internal error' > /dev/null - - CLEANUP_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-cleanup.sh" - if [ -f "$CLEANUP_SCRIPT" ]; then - source "$CLEANUP_SCRIPT" &> LOG.cleanup - fi -} - -trap cleanup EXIT - # Initial deploy of the resources. Only pre-plan when READPLAN=1 exercises the # saved-plan deploy path; otherwise `bundle deploy` plans on its own. if [[ -n "$READPLAN" ]]; then @@ -39,12 +16,7 @@ if [[ -n "$READPLAN" ]]; then cat LOG.plan_initial.err | contains.py '!panic:' '!internal error' > /dev/null fi -trace $CLI bundle deploy $(readplanarg plan_initial.json) &> LOG.deploy_initial -cat LOG.deploy_initial | contains.py '!panic:' '!internal error' > /dev/null - -# Special message to fuzzer that generated config was fine. -# Any failures after this point will be considered as "bug detected" by fuzzer. -echo INPUT_CONFIG_OK +invariant_deploy LOG.deploy_initial $CLI bundle deploy $(readplanarg plan_initial.json) # Snapshot the deploy state before we delete anything. cp -r .databricks .databricks.backup diff --git a/acceptance/bundle/invariant/destroy_idempotent/script b/acceptance/bundle/invariant/destroy_idempotent/script index d9e76cbaf1..0467b23330 100644 --- a/acceptance/bundle/invariant/destroy_idempotent/script +++ b/acceptance/bundle/invariant/destroy_idempotent/script @@ -2,36 +2,13 @@ # After destroy succeeds, restoring the pre-destroy state and running destroy again # must succeed even though the resources are already gone on the server. -# Copy data files to test directory -cp -r "$TESTDIR/../data/." . &> LOG.cp - -# Run init script if present -INIT_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-init.sh" -if [ -f "$INIT_SCRIPT" ]; then - source "$INIT_SCRIPT" &> LOG.init -fi - -envsubst < $TESTDIR/../configs/$INPUT_CONFIG > databricks.yml - -cp databricks.yml LOG.config +invariant_setup # All configs use `test-bundle-$UNIQUE_NAME`; workspace root path follows the # default `~/.bundle//` template with target=default. bundle_name=test-bundle-$UNIQUE_NAME ROOT_PATH=/Workspace/Users/$CURRENT_USER_NAME/.bundle/$bundle_name/default -final_cleanup() { - trace $CLI bundle destroy --auto-approve &> LOG.destroy_final - cat LOG.destroy_final | contains.py '!panic:' '!internal error' > /dev/null - - CLEANUP_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-cleanup.sh" - if [ -f "$CLEANUP_SCRIPT" ]; then - source "$CLEANUP_SCRIPT" &> LOG.cleanup - fi -} - -trap final_cleanup EXIT - # Initial deploy of the resources. Only pre-plan when READPLAN=1 exercises the # saved-plan deploy path; otherwise `bundle deploy` plans on its own. if [[ -n "$READPLAN" ]]; then @@ -39,12 +16,7 @@ if [[ -n "$READPLAN" ]]; then cat LOG.plan_initial.err | contains.py '!panic:' '!internal error' > /dev/null fi -trace $CLI bundle deploy $(readplanarg plan_initial.json) &> LOG.deploy_initial -cat LOG.deploy_initial | contains.py '!panic:' '!internal error' > /dev/null - -# Special message to fuzzer that generated config was fine. -# Any failures after this point will be considered as "bug detected" by fuzzer. -echo INPUT_CONFIG_OK +invariant_deploy LOG.deploy_initial $CLI bundle deploy $(readplanarg plan_initial.json) # Snapshot the deploy state before we destroy anything. cp -r .databricks .databricks.backup diff --git a/acceptance/bundle/invariant/migrate/script b/acceptance/bundle/invariant/migrate/script index 78f45faa7d..3cbde9a8e8 100644 --- a/acceptance/bundle/invariant/migrate/script +++ b/acceptance/bundle/invariant/migrate/script @@ -3,36 +3,9 @@ unset DATABRICKS_BUNDLE_ENGINE -# Copy data files to test directory -cp -r "$TESTDIR/../data/." . &> LOG.cp +invariant_setup -# Run init script if present -INIT_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-init.sh" -if [ -f "$INIT_SCRIPT" ]; then - source "$INIT_SCRIPT" &> LOG.init -fi - -envsubst < $TESTDIR/../configs/$INPUT_CONFIG > databricks.yml - -cp databricks.yml LOG.config - -cleanup() { - trace $CLI bundle destroy --auto-approve &> LOG.destroy - cat LOG.destroy | contains.py '!panic:' '!internal error' > /dev/null - - # Run cleanup script if present - CLEANUP_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-cleanup.sh" - if [ -f "$CLEANUP_SCRIPT" ]; then - source "$CLEANUP_SCRIPT" &> LOG.cleanup - fi -} - -trap cleanup EXIT - -trace DATABRICKS_BUNDLE_ENGINE=terraform $CLI bundle deploy &> LOG.deploy -cat LOG.deploy | contains.py '!panic:' '!internal error' > /dev/null - -echo INPUT_CONFIG_OK +invariant_deploy LOG.deploy DATABRICKS_BUNDLE_ENGINE=terraform $CLI bundle deploy MIGRATE_ARGS="" # The terraform provider sorts depends_on entries alphabetically by task_key on Read diff --git a/acceptance/bundle/invariant/no_drift/script b/acceptance/bundle/invariant/no_drift/script index ca80ab8544..f4fef449b1 100644 --- a/acceptance/bundle/invariant/no_drift/script +++ b/acceptance/bundle/invariant/no_drift/script @@ -1,31 +1,7 @@ # Invariant to test: no drift after deploy # Additional checks: no internal errors / panics in validate/plan/deploy -# Copy data files to test directory -cp -r "$TESTDIR/../data/." . &> LOG.cp - -# Run init script if present -INIT_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-init.sh" -if [ -f "$INIT_SCRIPT" ]; then - source "$INIT_SCRIPT" &> LOG.init -fi - -envsubst < $TESTDIR/../configs/$INPUT_CONFIG > databricks.yml - -cp databricks.yml LOG.config - -cleanup() { - trace $CLI bundle destroy --auto-approve &> LOG.destroy - cat LOG.destroy | contains.py '!panic:' '!internal error' > /dev/null - - # Run cleanup script if present - CLEANUP_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-cleanup.sh" - if [ -f "$CLEANUP_SCRIPT" ]; then - source "$CLEANUP_SCRIPT" &> LOG.cleanup - fi -} - -trap cleanup EXIT +invariant_setup # Only compute a plan up front when the READPLAN variant actually consumes it via --plan. # Without READPLAN, deploy computes its own plan internally, so a separate `bundle plan` @@ -35,12 +11,7 @@ if [[ -n "$READPLAN" ]]; then cat LOG.plan_initial.err | contains.py '!panic:' '!internal error' > /dev/null fi -trace $CLI bundle deploy $(readplanarg plan.json) &> LOG.deploy -cat LOG.deploy | contains.py '!panic:' '!internal error' > /dev/null - -# Special message to fuzzer that generated config was fine. -# Any failures after this point will be considered as "bug detected" by fuzzer. -echo INPUT_CONFIG_OK +invariant_deploy LOG.deploy $CLI bundle deploy $(readplanarg plan.json) # JSON plan asserts every action is "skip" -- a strict superset of the text # renderer's "Plan: 0 to add, 0 to change, 0 to delete" summary. diff --git a/acceptance/bundle/invariant/script.prepare b/acceptance/bundle/invariant/script.prepare new file mode 100644 index 0000000000..bbf169e486 --- /dev/null +++ b/acceptance/bundle/invariant/script.prepare @@ -0,0 +1,48 @@ +# Shared setup for the invariant target scripts. Every target renders the generated +# config the same way, tears the bundle down on exit, and gates the fuzzer contract on +# its first deploy; only the invariant being asserted differs. + +invariant_cleanup() { + trace $CLI bundle destroy --auto-approve &> LOG.destroy + cat LOG.destroy | contains.py '!panic:' '!internal error' > /dev/null + + # Run cleanup script if present + CLEANUP_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-cleanup.sh" + if [ -f "$CLEANUP_SCRIPT" ]; then + source "$CLEANUP_SCRIPT" &> LOG.cleanup + fi +} + +# Renders $INPUT_CONFIG into databricks.yml and installs the destroy-on-exit trap. +# Targets must call this instead of relying on prepare-time side effects: the harness +# concatenates script.prepare outside the subshell it wraps the script in, so a trap +# installed at prepare time would belong to the outer shell instead. +invariant_setup() { + # Copy data files to test directory + cp -r "$TESTDIR/../data/." . &> LOG.cp + + # Run init script if present + INIT_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-init.sh" + if [ -f "$INIT_SCRIPT" ]; then + source "$INIT_SCRIPT" &> LOG.init + fi + + envsubst < "$TESTDIR/../configs/$INPUT_CONFIG" > databricks.yml + + cp databricks.yml LOG.config + + trap invariant_cleanup EXIT +} + +# Runs the target's first deploy, logging to $1. The remaining arguments form the +# command and may start with VAR=val prefixes, which trace applies. +invariant_deploy() { + local logfile="$1" + shift + trace "$@" &> "$logfile" + cat "$logfile" | contains.py '!panic:' '!internal error' > /dev/null + + # Special message to fuzzer that generated config was fine. + # Any failures after this point will be considered as "bug detected" by fuzzer. + echo INPUT_CONFIG_OK +} From a833f00d60aeedb7fc3fd5a00e52a061f4276ab3 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Tue, 28 Jul 2026 07:12:18 +0000 Subject: [PATCH 2/6] acc/invariant: trim comments in shared script.prepare Drop the comments that restated the line below them and condense the helper docs, keeping only the non-obvious parts: why invariant_setup must be called from the target rather than at prepare time, and what INPUT_CONFIG_OK signals to the fuzzer. --- acceptance/bundle/invariant/script.prepare | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/acceptance/bundle/invariant/script.prepare b/acceptance/bundle/invariant/script.prepare index bbf169e486..6e19045fe1 100644 --- a/acceptance/bundle/invariant/script.prepare +++ b/acceptance/bundle/invariant/script.prepare @@ -1,27 +1,20 @@ -# Shared setup for the invariant target scripts. Every target renders the generated -# config the same way, tears the bundle down on exit, and gates the fuzzer contract on -# its first deploy; only the invariant being asserted differs. +# Shared setup for the invariant targets; each script keeps only the invariant it asserts. invariant_cleanup() { trace $CLI bundle destroy --auto-approve &> LOG.destroy cat LOG.destroy | contains.py '!panic:' '!internal error' > /dev/null - # Run cleanup script if present CLEANUP_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-cleanup.sh" if [ -f "$CLEANUP_SCRIPT" ]; then source "$CLEANUP_SCRIPT" &> LOG.cleanup fi } -# Renders $INPUT_CONFIG into databricks.yml and installs the destroy-on-exit trap. -# Targets must call this instead of relying on prepare-time side effects: the harness -# concatenates script.prepare outside the subshell it wraps the script in, so a trap -# installed at prepare time would belong to the outer shell instead. +# Call from the target, not at prepare time: prepares run outside the subshell wrapping +# the script, so the EXIT trap would otherwise belong to the outer shell. invariant_setup() { - # Copy data files to test directory cp -r "$TESTDIR/../data/." . &> LOG.cp - # Run init script if present INIT_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-init.sh" if [ -f "$INIT_SCRIPT" ]; then source "$INIT_SCRIPT" &> LOG.init @@ -34,15 +27,13 @@ invariant_setup() { trap invariant_cleanup EXIT } -# Runs the target's first deploy, logging to $1. The remaining arguments form the -# command and may start with VAR=val prefixes, which trace applies. +# Runs the gating deploy into log $1; trace applies any VAR=val prefixes in the command. invariant_deploy() { local logfile="$1" shift trace "$@" &> "$logfile" cat "$logfile" | contains.py '!panic:' '!internal error' > /dev/null - # Special message to fuzzer that generated config was fine. - # Any failures after this point will be considered as "bug detected" by fuzzer. + # Tells the fuzzer the generated config was valid; failures after this count as bugs. echo INPUT_CONFIG_OK } From faa212c06a4c8edd60ed2edd65463833679754f9 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Tue, 28 Jul 2026 08:10:06 +0000 Subject: [PATCH 3/6] acc/invariant: make the shared prologue reusable by generated configs Guard INPUT_CONFIG in invariant_cleanup: scripts run under set -u, and a caller that builds its own config leaves the variable unset. Split invariant_render out of invariant_setup so such a caller can override the render alone from its own script.prepare, which the harness concatenates after this file. Lift set -e around the deploy in invariant_deploy so the panic scan still runs when the deploy fails: a config the CLI rejects must not panic on the way out. A clean failure exits with the deploy's own code and prints no INPUT_CONFIG_OK. --- acceptance/bundle/invariant/script.prepare | 24 ++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/acceptance/bundle/invariant/script.prepare b/acceptance/bundle/invariant/script.prepare index 6e19045fe1..a9c6505c8b 100644 --- a/acceptance/bundle/invariant/script.prepare +++ b/acceptance/bundle/invariant/script.prepare @@ -4,15 +4,17 @@ invariant_cleanup() { trace $CLI bundle destroy --auto-approve &> LOG.destroy cat LOG.destroy | contains.py '!panic:' '!internal error' > /dev/null - CLEANUP_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-cleanup.sh" + # INPUT_CONFIG is unset for callers that generate their own config, such as the fuzzer. + CLEANUP_SCRIPT="$TESTDIR/../configs/${INPUT_CONFIG:-}-cleanup.sh" if [ -f "$CLEANUP_SCRIPT" ]; then source "$CLEANUP_SCRIPT" &> LOG.cleanup fi } -# Call from the target, not at prepare time: prepares run outside the subshell wrapping -# the script, so the EXIT trap would otherwise belong to the outer shell. -invariant_setup() { +# Renders the curated config named by INPUT_CONFIG. Callers that build the config +# themselves override this from their own script.prepare, which the harness concatenates +# after this file. +invariant_render() { cp -r "$TESTDIR/../data/." . &> LOG.cp INIT_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-init.sh" @@ -23,16 +25,30 @@ invariant_setup() { envsubst < "$TESTDIR/../configs/$INPUT_CONFIG" > databricks.yml cp databricks.yml LOG.config +} + +# Call from the target, not at prepare time: prepares run outside the subshell wrapping +# the script, so the EXIT trap would otherwise belong to the outer shell. +invariant_setup() { + invariant_render trap invariant_cleanup EXIT } # Runs the gating deploy into log $1; trace applies any VAR=val prefixes in the command. +# set -e is lifted around the deploy so the panic scan still runs when it fails: a config +# the CLI rejects must not panic on the way out. A clean failure exits with the deploy's code. invariant_deploy() { local logfile="$1" shift + set +e trace "$@" &> "$logfile" + local rc=$? + set -e cat "$logfile" | contains.py '!panic:' '!internal error' > /dev/null + if [ "$rc" -ne 0 ]; then + exit "$rc" + fi # Tells the fuzzer the generated config was valid; failures after this count as bugs. echo INPUT_CONFIG_OK From 67a8b872c62128329fc8faf5a0d5c2a2ecfa8204 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Tue, 28 Jul 2026 08:20:58 +0000 Subject: [PATCH 4/6] acc/invariant: extract the no-drift check into the shared prologue no_drift, migrate and continue_293 each ran the same plan + panic-scan + verify_no_drift.py sequence. Move it into invariant_verify_no_drift so a caller whose config the server does not round-trip exactly can override the check alone. migrate now logs the plan to LOG.planjson like the other two. --- acceptance/bundle/invariant/continue_293/script | 4 +--- acceptance/bundle/invariant/migrate/script | 4 +--- acceptance/bundle/invariant/no_drift/script | 6 +----- acceptance/bundle/invariant/script.prepare | 10 ++++++++++ 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/acceptance/bundle/invariant/continue_293/script b/acceptance/bundle/invariant/continue_293/script index 544056b474..2a0e94d971 100644 --- a/acceptance/bundle/invariant/continue_293/script +++ b/acceptance/bundle/invariant/continue_293/script @@ -11,9 +11,7 @@ $CLI bundle deploy &> LOG.deploy cat LOG.deploy | contains.py '!panic:' '!internal error' > /dev/null # Verify no drift after current CLI deploy -$CLI bundle plan -o json > LOG.planjson 2>LOG.planjson.err -cat LOG.planjson.err | contains.py '!panic:' '!internal error' > /dev/null -verify_no_drift.py LOG.planjson +invariant_verify_no_drift $CLI bundle plan 2>LOG.plan.err | contains.py '!panic:' '!internal error' 'Plan: 0 to add, 0 to change, 0 to delete' > LOG.plan cat LOG.plan.err | contains.py '!panic:' '!internal error' > /dev/null diff --git a/acceptance/bundle/invariant/migrate/script b/acceptance/bundle/invariant/migrate/script index 3cbde9a8e8..4cd1957195 100644 --- a/acceptance/bundle/invariant/migrate/script +++ b/acceptance/bundle/invariant/migrate/script @@ -21,6 +21,4 @@ trace $CLI bundle deployment migrate $MIGRATE_ARGS &> LOG.migrate cat LOG.migrate | contains.py '!panic:' '!internal error' > /dev/null -$CLI bundle plan -o json > plan.json 2>plan.json.err -cat plan.json.err | contains.py '!panic:' '!internal error' > /dev/null -verify_no_drift.py plan.json +invariant_verify_no_drift diff --git a/acceptance/bundle/invariant/no_drift/script b/acceptance/bundle/invariant/no_drift/script index f4fef449b1..9f031c0f61 100644 --- a/acceptance/bundle/invariant/no_drift/script +++ b/acceptance/bundle/invariant/no_drift/script @@ -13,8 +13,4 @@ fi invariant_deploy LOG.deploy $CLI bundle deploy $(readplanarg plan.json) -# JSON plan asserts every action is "skip" -- a strict superset of the text -# renderer's "Plan: 0 to add, 0 to change, 0 to delete" summary. -$CLI bundle plan -o json > LOG.planjson 2>LOG.planjson.err -cat LOG.planjson.err | contains.py '!panic:' '!internal error' > /dev/null -verify_no_drift.py LOG.planjson +invariant_verify_no_drift diff --git a/acceptance/bundle/invariant/script.prepare b/acceptance/bundle/invariant/script.prepare index a9c6505c8b..e62bfabf5f 100644 --- a/acceptance/bundle/invariant/script.prepare +++ b/acceptance/bundle/invariant/script.prepare @@ -53,3 +53,13 @@ invariant_deploy() { # Tells the fuzzer the generated config was valid; failures after this count as bugs. echo INPUT_CONFIG_OK } + +# JSON plan asserts every action is "skip" -- a strict superset of the text +# renderer's "Plan: 0 to add, 0 to change, 0 to delete" summary. +# Callers whose config the server does not round-trip exactly override this +# from their own script.prepare. +invariant_verify_no_drift() { + $CLI bundle plan -o json > LOG.planjson 2>LOG.planjson.err + cat LOG.planjson.err | contains.py '!panic:' '!internal error' > /dev/null + verify_no_drift.py LOG.planjson +} From 09dd85a61bacf696ad9701a88818b9c05abdacf5 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Tue, 28 Jul 2026 12:22:01 +0000 Subject: [PATCH 5/6] acc/invariant: keep the shared prologue behavior-neutral The set -e lift in invariant_deploy and the INPUT_CONFIG guard in invariant_cleanup serve only a caller that generates its own config, which does not exist in this PR. Drop both so the change is a pure extraction: every helper now runs the same sequence the five targets ran before, and #5686 adds the two deltas along with the caller that needs them. invariant_render and invariant_verify_no_drift stay separate functions. Both are pure structure -- the code inside them is unchanged -- and splitting them is what lets a config generator override the render or the drift check on its own. --- acceptance/bundle/invariant/script.prepare | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/acceptance/bundle/invariant/script.prepare b/acceptance/bundle/invariant/script.prepare index e62bfabf5f..b47a8cd8ae 100644 --- a/acceptance/bundle/invariant/script.prepare +++ b/acceptance/bundle/invariant/script.prepare @@ -4,8 +4,7 @@ invariant_cleanup() { trace $CLI bundle destroy --auto-approve &> LOG.destroy cat LOG.destroy | contains.py '!panic:' '!internal error' > /dev/null - # INPUT_CONFIG is unset for callers that generate their own config, such as the fuzzer. - CLEANUP_SCRIPT="$TESTDIR/../configs/${INPUT_CONFIG:-}-cleanup.sh" + CLEANUP_SCRIPT="$TESTDIR/../configs/$INPUT_CONFIG-cleanup.sh" if [ -f "$CLEANUP_SCRIPT" ]; then source "$CLEANUP_SCRIPT" &> LOG.cleanup fi @@ -36,19 +35,11 @@ invariant_setup() { } # Runs the gating deploy into log $1; trace applies any VAR=val prefixes in the command. -# set -e is lifted around the deploy so the panic scan still runs when it fails: a config -# the CLI rejects must not panic on the way out. A clean failure exits with the deploy's code. invariant_deploy() { local logfile="$1" shift - set +e trace "$@" &> "$logfile" - local rc=$? - set -e cat "$logfile" | contains.py '!panic:' '!internal error' > /dev/null - if [ "$rc" -ne 0 ]; then - exit "$rc" - fi # Tells the fuzzer the generated config was valid; failures after this count as bugs. echo INPUT_CONFIG_OK From 8f17e46de6c54beb160426e9f67bbcf6b8162be2 Mon Sep 17 00:00:00 2001 From: Rada Kamysheva Date: Tue, 28 Jul 2026 12:29:51 +0000 Subject: [PATCH 6/6] acc/invariant: shorten the helper comments Drop the sentences that restated the body (what invariant_render renders, what invariant_deploy deploys) and keep one line per helper for the part the code cannot show: why setup must run from the target, which helpers are override seams, and that invariant_deploy accepts VAR=val prefixes. --- acceptance/bundle/invariant/script.prepare | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/acceptance/bundle/invariant/script.prepare b/acceptance/bundle/invariant/script.prepare index b47a8cd8ae..54f2a3dbf2 100644 --- a/acceptance/bundle/invariant/script.prepare +++ b/acceptance/bundle/invariant/script.prepare @@ -10,9 +10,8 @@ invariant_cleanup() { fi } -# Renders the curated config named by INPUT_CONFIG. Callers that build the config -# themselves override this from their own script.prepare, which the harness concatenates -# after this file. +# Separate from invariant_setup so a caller that generates its own config can override the +# render alone; child script.prepare files are concatenated after this one. invariant_render() { cp -r "$TESTDIR/../data/." . &> LOG.cp @@ -26,15 +25,15 @@ invariant_render() { cp databricks.yml LOG.config } -# Call from the target, not at prepare time: prepares run outside the subshell wrapping -# the script, so the EXIT trap would otherwise belong to the outer shell. +# Call from the target, not at prepare time: prepare runs outside the subshell wrapping the +# script, so the trap would belong to the outer shell. invariant_setup() { invariant_render trap invariant_cleanup EXIT } -# Runs the gating deploy into log $1; trace applies any VAR=val prefixes in the command. +# Goes through trace, so callers can prefix the command with VAR=val. invariant_deploy() { local logfile="$1" shift @@ -47,8 +46,7 @@ invariant_deploy() { # JSON plan asserts every action is "skip" -- a strict superset of the text # renderer's "Plan: 0 to add, 0 to change, 0 to delete" summary. -# Callers whose config the server does not round-trip exactly override this -# from their own script.prepare. +# Overridable for a caller whose config the server does not round-trip exactly. invariant_verify_no_drift() { $CLI bundle plan -o json > LOG.planjson 2>LOG.planjson.err cat LOG.planjson.err | contains.py '!panic:' '!internal error' > /dev/null