gum draws its confirm UI on stderr, so the migration's `2>/dev/null` threw away the whole prompt while gum still held the terminal in raw mode reading keys. With a browser open, an update stopped after "Running migration (1786643346)" on an unpainted screen with no way to tell it was waiting for an answer. Nothing else in the repo suppresses gum's stderr; the redirect only ever hid gum's own error in the no-terminal case, where the migration already explains itself on stderr before deferring. Fixes #6841 Claude-Session: https://claude.ai/code/session_01So4yKzTDdJJp7orjnF8i1J Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
196 lines
9.8 KiB
Bash
196 lines
9.8 KiB
Bash
#!/bin/bash
|
|
|
|
set -euo pipefail
|
|
|
|
source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh"
|
|
|
|
require_command jq
|
|
require_command python3
|
|
|
|
migration="$ROOT/migrations/1786643346.sh"
|
|
test_dir=$(mktemp -d)
|
|
trap 'rm -rf "$test_dir"' EXIT
|
|
|
|
home="$test_dir/home"
|
|
preferences="$home/.config/chromium/Default/Preferences"
|
|
mkdir -p "$(dirname "$preferences")"
|
|
|
|
# Any id Chromium once derived from the extension's keyless load path; the
|
|
# repair keys off the registered command name, not the id.
|
|
ghost_id="ikkebdkaanlebnifjnbeiaklodhbjcci"
|
|
pinned_id="bgpiichlckmfanooecilcjemknkcpngb"
|
|
|
|
write_stale_preferences() {
|
|
jq -n --arg ghost "$ghost_id" --arg pinned "$pinned_id" '{extensions: {commands: {"linux:Alt+Shift+L": {command_name: "copy-url", extension: $ghost, global: false}}, settings: {($ghost): {commands: {"copy-url": {suggested_key: "Alt+Shift+L", was_assigned: true}}}, ($pinned): {commands: {"copy-url": {suggested_key: "Alt+Shift+L"}}}}}}' >"$preferences"
|
|
}
|
|
|
|
stub_bin="$test_dir/bin"
|
|
mkdir -p "$stub_bin"
|
|
|
|
run_migration() {
|
|
HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2>&1
|
|
}
|
|
|
|
# A running browser prompts for the windows to be closed; declining (or having
|
|
# no terminal to ask in) defers the repair so a rewrite-on-exit cannot revert
|
|
# it.
|
|
printf '#!/bin/bash\nexit 0\n' >"$stub_bin/pgrep"
|
|
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/gum"
|
|
chmod +x "$stub_bin/pgrep" "$stub_bin/gum"
|
|
write_stale_preferences
|
|
before_hash=$(sha256sum "$preferences" | cut -d' ' -f1)
|
|
|
|
run_migration && fail "migration defers while a browser is running"
|
|
[[ $(sha256sum "$preferences" | cut -d' ' -f1) == "$before_hash" ]] ||
|
|
fail "migration leaves preferences alone while a browser is running"
|
|
pass "migration defers the repair while a browser is running"
|
|
|
|
# gum paints its prompt on stderr, so that stream has to stay attached:
|
|
# suppressing it leaves gum reading keys behind an unpainted screen, which
|
|
# reads as a hung update.
|
|
cat >"$stub_bin/gum" <<'STUB'
|
|
#!/bin/bash
|
|
echo "gum-prompt-painted" >&2
|
|
exit 1
|
|
STUB
|
|
prompt_stderr="$test_dir/prompt-stderr"
|
|
HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2>"$prompt_stderr" &&
|
|
fail "migration defers when the browser prompt is declined"
|
|
grep -q "gum-prompt-painted" "$prompt_stderr" || fail "migration keeps the browser prompt visible"
|
|
pass "migration keeps the browser prompt visible"
|
|
|
|
# Confirming the prompt after closing the browser lets the repair proceed.
|
|
cat >"$stub_bin/gum" <<'STUB'
|
|
#!/bin/bash
|
|
touch "${GUM_CALLED:?}"
|
|
exit 0
|
|
STUB
|
|
cat >"$stub_bin/pgrep" <<'STUB'
|
|
#!/bin/bash
|
|
count_file="${PGREP_COUNT_FILE:?}"
|
|
count=$(( $(cat "$count_file" 2>/dev/null || echo 0) + 1 ))
|
|
printf '%s\n' "$count" >"$count_file"
|
|
(( count == 1 )) && exit 0 || exit 1
|
|
STUB
|
|
rm -f "$test_dir/pgrep-count"
|
|
GUM_CALLED="$test_dir/gum-called" PGREP_COUNT_FILE="$test_dir/pgrep-count" \
|
|
HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2>&1 ||
|
|
fail "migration proceeds once the browser prompt is confirmed"
|
|
[[ -e $test_dir/gum-called ]] || fail "migration asks before repairing under a running browser"
|
|
jq -e --arg pinned "$pinned_id" '.extensions.commands["linux:Alt+Shift+L"].extension == $pinned' "$preferences" >/dev/null ||
|
|
fail "migration repairs after the browser prompt is confirmed"
|
|
pass "migration asks to close the browser and repairs on confirmation"
|
|
rm -f "$preferences.omarchy-copy-url-repair.bak"
|
|
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/gum"
|
|
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep"
|
|
write_stale_preferences
|
|
|
|
# With browsers closed the ghost registration moves to the pinned id.
|
|
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep"
|
|
run_migration || fail "migration repairs the shortcut when no browser is running"
|
|
|
|
jq -e --arg ghost "$ghost_id" --arg pinned "$pinned_id" '
|
|
.extensions.commands["linux:Alt+Shift+L"].extension == $pinned and
|
|
(.extensions.settings | has($ghost) | not) and
|
|
.extensions.settings[$pinned].commands["copy-url"].was_assigned == true
|
|
' "$preferences" >/dev/null || fail "migration rebinds the Copy URL shortcut to the pinned extension id"
|
|
[[ -f $preferences.omarchy-copy-url-repair.bak ]] ||
|
|
fail "migration backs up preferences before the repair"
|
|
pass "migration rebinds the Copy URL shortcut to the pinned extension id"
|
|
|
|
# A repaired profile has no ghost registration left, so nothing is pending —
|
|
# even while a browser is running.
|
|
rm "$preferences.omarchy-copy-url-repair.bak"
|
|
repaired_hash=$(sha256sum "$preferences" | cut -d' ' -f1)
|
|
printf '#!/bin/bash\nexit 0\n' >"$stub_bin/pgrep"
|
|
run_migration || fail "migration reruns cleanly after the repair"
|
|
[[ $(sha256sum "$preferences" | cut -d' ' -f1) == "$repaired_hash" && ! -e $preferences.omarchy-copy-url-repair.bak ]] ||
|
|
fail "migration is idempotent after the repair"
|
|
pass "migration is idempotent after the repair"
|
|
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep"
|
|
|
|
# A remapped shortcut keeps the user's chosen key while moving to the pinned id.
|
|
jq -n --arg ghost "$ghost_id" '{extensions: {commands: {"linux:Ctrl+Alt+P": {command_name: "copy-url", extension: $ghost, global: false}}, settings: {}}}' >"$preferences"
|
|
run_migration || fail "migration repairs remapped shortcuts"
|
|
jq -e --arg pinned "$pinned_id" '.extensions.commands["linux:Ctrl+Alt+P"].extension == $pinned' "$preferences" >/dev/null ||
|
|
fail "migration keeps the remapped key while rebinding to the pinned id"
|
|
pass "migration keeps remapped shortcut keys"
|
|
|
|
# When the pinned extension already holds a copy-url binding (the user fixed
|
|
# it by hand), the ghost is dropped rather than doubled into a second binding.
|
|
jq -n --arg ghost "$ghost_id" --arg pinned "$pinned_id" '{extensions: {commands: {"linux:Ctrl+Alt+P": {command_name: "copy-url", extension: $pinned, global: false}, "linux:Alt+Shift+L": {command_name: "copy-url", extension: $ghost, global: false}}, settings: {}}}' >"$preferences"
|
|
run_migration || fail "migration cleans ghosts alongside a manual repair"
|
|
jq -e --arg pinned "$pinned_id" '
|
|
(.extensions.commands | has("linux:Alt+Shift+L") | not) and
|
|
.extensions.commands["linux:Ctrl+Alt+P"].extension == $pinned
|
|
' "$preferences" >/dev/null || fail "migration drops the ghost instead of double-binding the pinned extension"
|
|
pass "migration never double-binds the pinned extension"
|
|
|
|
# A browser starting mid-repair may write stale Preferences back on exit, so
|
|
# the migration must stay pending for a later browser-free run to verify.
|
|
write_stale_preferences
|
|
cat >"$stub_bin/pgrep" <<'STUB'
|
|
#!/bin/bash
|
|
count_file="${PGREP_COUNT_FILE:?}"
|
|
count=$(( $(cat "$count_file" 2>/dev/null || echo 0) + 1 ))
|
|
printf '%s\n' "$count" >"$count_file"
|
|
(( count >= 2 )) && exit 0 || exit 1
|
|
STUB
|
|
rm -f "$test_dir/pgrep-count"
|
|
if PGREP_COUNT_FILE="$test_dir/pgrep-count" HOME="$home" PATH="$stub_bin:$PATH" \
|
|
bash -euo pipefail "$migration" >/dev/null 2>&1; then
|
|
fail "migration stays pending when a browser starts mid-repair"
|
|
fi
|
|
jq -e --arg pinned "$pinned_id" '.extensions.commands["linux:Alt+Shift+L"].extension == $pinned' "$preferences" >/dev/null ||
|
|
fail "migration still repairs preferences before deferring on a late browser"
|
|
pass "migration stays pending when a browser starts mid-repair"
|
|
rm -f "$preferences.omarchy-copy-url-repair.bak"
|
|
|
|
# A browser that started and exited mid-repair restores stale Preferences
|
|
# before the final process check; the post-repair file verification catches it.
|
|
write_stale_preferences
|
|
cp "$preferences" "$test_dir/stale-preferences"
|
|
cat >"$stub_bin/pgrep" <<'STUB'
|
|
#!/bin/bash
|
|
count_file="${PGREP_COUNT_FILE:?}"
|
|
count=$(( $(cat "$count_file" 2>/dev/null || echo 0) + 1 ))
|
|
printf '%s\n' "$count" >"$count_file"
|
|
(( count == 2 )) && cp "$STALE_PREFERENCES" "$REPAIRED_PREFERENCES"
|
|
exit 1
|
|
STUB
|
|
rm -f "$test_dir/pgrep-count"
|
|
if PGREP_COUNT_FILE="$test_dir/pgrep-count" STALE_PREFERENCES="$test_dir/stale-preferences" \
|
|
REPAIRED_PREFERENCES="$preferences" HOME="$home" PATH="$stub_bin:$PATH" \
|
|
bash -euo pipefail "$migration" >/dev/null 2>&1; then
|
|
fail "migration stays pending when a briefly-lived browser undoes the repair"
|
|
fi
|
|
pass "migration stays pending when a briefly-lived browser undoes the repair"
|
|
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep"
|
|
write_stale_preferences
|
|
run_migration || fail "migration recovers after a reverted repair"
|
|
rm -f "$preferences.omarchy-copy-url-repair.bak"
|
|
|
|
# A repair attempted while a browser was open leaves its backup behind. A
|
|
# rerun that sees a clean disk while that browser still runs must stay
|
|
# pending — the browser can restore the ghost on exit — and only a
|
|
# browser-free rerun verifies the repair and completes.
|
|
write_stale_preferences
|
|
run_migration || fail "repair run before the verification scenario"
|
|
[[ -f $preferences.omarchy-copy-url-repair.bak ]] || fail "verification scenario has a repair backup"
|
|
printf '#!/bin/bash\nexit 0\n' >"$stub_bin/pgrep"
|
|
run_migration && fail "migration must not complete an unverified repair while a browser runs"
|
|
pass "migration keeps an unverified repair pending while a browser runs"
|
|
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/pgrep"
|
|
run_migration || fail "migration completes once the repair is verified with browsers closed"
|
|
pass "migration verifies an attempted repair on a browser-free rerun"
|
|
rm -f "$preferences.omarchy-copy-url-repair.bak"
|
|
|
|
# An installed third-party extension with a command that happens to be named
|
|
# copy-url keeps its own registration.
|
|
jq -n '{extensions: {commands: {"linux:Alt+Shift+L": {command_name: "copy-url", extension: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", global: false}}, settings: {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa: {path: "/home/user/.config/some-extension", commands: {}}}}}' >"$preferences"
|
|
untouched_hash=$(sha256sum "$preferences" | cut -d' ' -f1)
|
|
run_migration || fail "migration leaves installed third-party extensions alone"
|
|
[[ $(sha256sum "$preferences" | cut -d' ' -f1) == "$untouched_hash" ]] ||
|
|
fail "migration does not steal a third-party copy-url command registration"
|
|
pass "migration leaves installed third-party extensions alone"
|