Merge quattro into USB authorization branch
This commit is contained in:
commit
27bbbb2fe9
121 files changed
+2208
-1118
No files matched your search
@@ -43,7 +43,7 @@ if (( EUID != 0 )); then
|
||||
fi
|
||||
|
||||
if (( ! root_runtime_available )); then
|
||||
pass "no unprivileged user namespace; skipping the root lock-helper lookup matrix"
|
||||
skip "no unprivileged user namespace; skipping the root lock-helper lookup matrix"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
require_compositor "bar icon geometry test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping bar icon geometry test"
|
||||
skip "quickshell not installed; skipping bar icon geometry test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ trap cleanup EXIT
|
||||
require_compositor "bar widget contract test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping bar widget contract test"
|
||||
skip "quickshell not installed; skipping bar widget contract test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -14,6 +14,10 @@ pass() {
|
||||
printf 'ok - %s\n' "$1"
|
||||
}
|
||||
|
||||
skip() {
|
||||
printf 'ok - %s # SKIP\n' "$1"
|
||||
}
|
||||
|
||||
fail() {
|
||||
local description="$1"
|
||||
local detail="${2:-}"
|
||||
@@ -72,7 +76,7 @@ require_compositor() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
pass "no Wayland compositor; skipping $description"
|
||||
skip "no Wayland compositor; skipping $description"
|
||||
exit 0
|
||||
}
|
||||
|
||||
|
||||
@@ -123,7 +123,7 @@ if [[ -n $real_hiddev ]]; then
|
||||
fail "wrapper did not trust a valid cached hiddev node: $real_hiddev"
|
||||
pass "wrapper trusts a cached hiddev character device without re-detecting"
|
||||
else
|
||||
pass "no /dev/hiddev* character device present; skipping the valid-cache case"
|
||||
skip "no /dev/hiddev* character device present; skipping the valid-cache case"
|
||||
fi
|
||||
|
||||
# --- With no XDG_RUNTIME_DIR, the predictable /tmp cache is not consulted ------
|
||||
@@ -148,5 +148,5 @@ if mkfifo "$tmp_cache" 2>/dev/null; then
|
||||
"it blocked reading the FIFO decoy at $tmp_cache"
|
||||
pass "wrapper ignores the /tmp cache path when XDG_RUNTIME_DIR is unset"
|
||||
else
|
||||
pass "$tmp_cache already present or not safely creatable; skipping the /tmp-fallback case"
|
||||
skip "$tmp_cache already present or not safely creatable; skipping the /tmp-fallback case"
|
||||
fi
|
||||
@@ -46,7 +46,7 @@ mode=$(stat -c '%a' "$write_dir/color.json")
|
||||
pass "theme colour writes a 0644 color.json"
|
||||
|
||||
if (( EUID == 0 )); then
|
||||
pass "running as root; skipping the mktemp-failure check"
|
||||
skip "running as root; skipping the mktemp-failure check"
|
||||
else
|
||||
chmod u+w "$write_dir"
|
||||
export TMPDIR=$test_tmp/missing-tmp
|
||||
|
||||
@@ -82,7 +82,7 @@ SH
|
||||
chmod +x "$stub_bin/sudo"
|
||||
|
||||
if ((EUID == 0)); then
|
||||
pass "running as root; skipping the elevation checks, which would rewrite this machine's browser policy"
|
||||
skip "running as root; skipping the elevation checks, which would rewrite this machine's browser policy"
|
||||
else
|
||||
elevation_for() {
|
||||
: >"$test_tmp/elevation"
|
||||
|
||||
@@ -20,7 +20,7 @@ JS
|
||||
require_compositor "Button hover geometry runtime test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping Button hover geometry runtime test"
|
||||
skip "quickshell not installed; skipping Button hover geometry runtime test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
# Exercise the privileged installer without writing to the host's /usr/share.
|
||||
if ! command -v bwrap >/dev/null || ! bwrap --ro-bind / / --unshare-user --uid 0 --gid 0 true 2>/dev/null; then
|
||||
skip "user namespaces unavailable; skipping isolated Claude extension installation"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
mkdir -p "$test_tmp/share" "$test_tmp/bin"
|
||||
installer="$ROOT/bin/omarchy-install-chromium-claude"
|
||||
extension_id=fcoeoabgfenejglbffodgkkbkcdhcgfn
|
||||
sandbox=(bwrap --ro-bind / / --bind "$test_tmp" "$test_tmp" --dev /dev --proc /proc --unshare-user)
|
||||
|
||||
"${sandbox[@]}" --uid 0 --gid 0 --bind "$test_tmp/share" /usr/share bash "$installer"
|
||||
for browser in chromium google-chrome microsoft-edge; do
|
||||
file="$test_tmp/share/$browser/extensions/$extension_id.json"
|
||||
jq -e '.external_update_url == "https://clients2.google.com/service/update2/crx"' "$file" >/dev/null ||
|
||||
fail "$browser registers the official Claude Web Store extension"
|
||||
[[ $(stat -c '%a' "$file") == "644" ]] || fail "$browser extension registration is readable"
|
||||
done
|
||||
pass "Claude extension installer registers all supported browser families"
|
||||
|
||||
cat >"$test_tmp/bin/pkexec" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$@" >"$AUTH_LOG"
|
||||
exit 42
|
||||
SH
|
||||
chmod +x "$test_tmp/bin/pkexec"
|
||||
export AUTH_LOG="$test_tmp/auth-log"
|
||||
export PATH="$test_tmp/bin:$PATH"
|
||||
|
||||
# A read-only /usr/share and a failing auth stub prove that a repeated run
|
||||
# neither rewrites the files nor requests authentication.
|
||||
"${sandbox[@]}" --uid 1000 --gid 1000 --ro-bind "$test_tmp/share" /usr/share bash "$installer" </dev/null
|
||||
[[ ! -e $AUTH_LOG ]] || fail "configured Claude extensions need no authentication"
|
||||
pass "configured Claude extensions need neither writes nor authentication"
|
||||
|
||||
rm "$test_tmp/share/google-chrome/extensions/$extension_id.json"
|
||||
status=0
|
||||
"${sandbox[@]}" --uid 1000 --gid 1000 --ro-bind "$test_tmp/share" /usr/share bash "$installer" </dev/null || status=$?
|
||||
[[ $status == 42 ]] || fail "Claude extension installer propagates authentication failure"
|
||||
[[ $(cat "$AUTH_LOG") == "/usr/bin/omarchy-install-chromium-claude" ]] ||
|
||||
fail "menu installation elevates only the packaged installer"
|
||||
pass "missing registration uses pkexec and propagates authentication failure"
|
||||
@@ -277,12 +277,12 @@ printf '%s\n' "$1" >"$EDITOR_PATH_OUT"
|
||||
cat "$1" >"$EDITOR_TEXT_OUT"
|
||||
SH
|
||||
|
||||
cat >"$TMPDIR/bin/tensaku-edit" <<'SH'
|
||||
cat >"$TMPDIR/bin/omasnap" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$*" >"$TENSAKU_OUT"
|
||||
printf '%s\n' "$*" >"$OMASNAP_OUT"
|
||||
SH
|
||||
|
||||
chmod +x "$TMPDIR/bin/wl-copy" "$TMPDIR/bin/wl-paste" "$TMPDIR/bin/wtype" "$TMPDIR/bin/omarchy-launch-browser" "$TMPDIR/bin/omarchy-launch-editor" "$TMPDIR/bin/tensaku-edit"
|
||||
chmod +x "$TMPDIR/bin/wl-copy" "$TMPDIR/bin/wl-paste" "$TMPDIR/bin/wtype" "$TMPDIR/bin/omarchy-launch-browser" "$TMPDIR/bin/omarchy-launch-editor" "$TMPDIR/bin/omasnap"
|
||||
|
||||
capture_output=$(XDG_RUNTIME_DIR="$TMPDIR" XDG_STATE_HOME="$TMPDIR/state" PATH="$TMPDIR/bin:$PATH" "$ROOT/shell/plugins/clipboard/capture.sh")
|
||||
[[ $capture_output == '{"type":"text","text":"terminal copy"}' ]] || fail "clipboard capture records normal text events"
|
||||
@@ -513,8 +513,8 @@ pass "clipboard open helper opens text entries in editor"
|
||||
[[ $(<"$TMPDIR/editor-path") == "$TMPDIR"/state/omarchy/clipboard-open/clipboard.*.txt ]] || fail "clipboard open helper writes text entries to a temporary file"
|
||||
pass "clipboard open helper writes text entries to a temporary file"
|
||||
|
||||
TENSAKU_OUT="$TMPDIR/tensaku" HOME="$TMPDIR/home" PATH="$TMPDIR/bin:$PATH" \
|
||||
OMASNAP_OUT="$TMPDIR/omasnap" HOME="$TMPDIR/home" PATH="$TMPDIR/bin:$PATH" \
|
||||
"$ROOT/bin/omarchy-clipboard-open" --history-index 2
|
||||
|
||||
[[ $(<"$TMPDIR/tensaku") == "$TMPDIR/image.png" ]] || fail "clipboard open helper opens image entries in Tensaku"
|
||||
pass "clipboard open helper opens image entries in Tensaku"
|
||||
[[ $(<"$TMPDIR/omasnap") == "$TMPDIR/image.png" ]] || fail "clipboard open helper opens image entries in Omasnap"
|
||||
pass "clipboard open helper opens image entries in Omasnap"
|
||||
@@ -62,7 +62,7 @@ run_guard() {
|
||||
' bash "$SHELL_TEST_DIR" 2>&1 || printf 'guard exited %s\n' "$?"
|
||||
}
|
||||
|
||||
skipped="ok - no Wayland compositor; skipping sample runtime test"
|
||||
skipped="ok - no Wayland compositor; skipping sample runtime test # SKIP"
|
||||
|
||||
output=$(run_guard -u WAYLAND_DISPLAY XDG_RUNTIME_DIR="$runtime_dir")
|
||||
[[ $output == "$skipped" ]] || fail "guard skips without a display" "$output"
|
||||
@@ -76,7 +76,7 @@ pass "guard skips when the socket is unreachable"
|
||||
|
||||
# Everything below needs a socket to stand in for a live or abandoned compositor.
|
||||
if (( ! socket_bound )); then
|
||||
pass "cannot bind a Unix socket here; skipping the cases that need one"
|
||||
skip "cannot bind a Unix socket here; skipping the cases that need one"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -33,6 +33,15 @@ jq -e '
|
||||
' "$ROOT/config/omarchy/shell.json" >/dev/null
|
||||
pass "default center anchor exists in center layout"
|
||||
|
||||
jq -e '
|
||||
def ids: map(.id // .);
|
||||
(.bar.layout.center | ids) as $ids |
|
||||
($ids | index("omacom.elsewhen")) as $elsewhen |
|
||||
($ids | index("omarchy.clock")) as $clock |
|
||||
$elsewhen != null and $clock == $elsewhen + 1
|
||||
' "$ROOT/config/omarchy/shell.json" >/dev/null
|
||||
pass "default center layout puts elsewhen immediately before the clock"
|
||||
|
||||
jq -e '
|
||||
any(.bar.layout.center[]; (.id // .) == "omarchy.clock" and (.formatAlt // "") == "d MMMM \u0027W\u0027ww yyyy")
|
||||
' "$ROOT/config/omarchy/shell.json" >/dev/null
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
#!/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"
|
||||
profile_root="$home/.config/chromium"
|
||||
preferences="$profile_root/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"
|
||||
|
||||
cat >"$stub_bin/python3" <<'STUB'
|
||||
#!/bin/bash
|
||||
exit 127
|
||||
STUB
|
||||
chmod +x "$stub_bin/python3"
|
||||
|
||||
# Test stubs must delegate to the system interpreter, not a user shim that can
|
||||
# route python3 back through the stubs and recurse.
|
||||
REAL_PYTHON=$(PATH="$stub_bin:$PATH" command -p -v python3)
|
||||
[[ $REAL_PYTHON != "$stub_bin/python3" ]] || fail "real Python resolution bypasses user shims"
|
||||
export REAL_PYTHON
|
||||
rm -f "$stub_bin/python3"
|
||||
|
||||
run_migration() {
|
||||
HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# A running Chromium-family browser marks its profile root with a SingletonLock
|
||||
# symlink to <hostname>-<pid>, a target that never exists on disk. That lock —
|
||||
# not the mere presence of a browser process — is what the migration waits on.
|
||||
open_browser() {
|
||||
mkdir -p "$profile_root"
|
||||
ln -sfn "test-host-1234" "$profile_root/SingletonLock"
|
||||
}
|
||||
close_browser() {
|
||||
rm -f "$profile_root/SingletonLock"
|
||||
}
|
||||
|
||||
# The affected profile being open 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 1\n' >"$stub_bin/gum"
|
||||
chmod +x "$stub_bin/gum"
|
||||
write_stale_preferences
|
||||
open_browser
|
||||
|
||||
before_hash=$(sha256sum "$preferences" | cut -d' ' -f1)
|
||||
|
||||
run_migration && fail "migration defers while the affected profile is open"
|
||||
[[ $(sha256sum "$preferences" | cut -d' ' -f1) == "$before_hash" ]] ||
|
||||
fail "migration leaves preferences alone while the affected profile is open"
|
||||
pass "migration defers the repair while the affected profile is open"
|
||||
|
||||
# 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"
|
||||
|
||||
# A browser holding a different profile root cannot revert this repair, so it
|
||||
# must not hold the update: the repair goes through without ever reaching the
|
||||
# prompt, which the still-declining gum stub would otherwise fail.
|
||||
close_browser
|
||||
mkdir -p "$home/.config/google-chrome"
|
||||
ln -sfn "test-host-1234" "$home/.config/google-chrome/SingletonLock"
|
||||
write_stale_preferences
|
||||
run_migration || fail "migration repairs while a different profile root is open"
|
||||
jq -e --arg pinned "$pinned_id" '.extensions.commands["linux:Alt+Shift+L"].extension == $pinned' "$preferences" >/dev/null ||
|
||||
fail "migration repairs the shortcut while a different profile root is open"
|
||||
pass "migration ignores a browser on a different profile root"
|
||||
rm -f "$home/.config/google-chrome/SingletonLock" "$preferences.omarchy-copy-url-repair.bak"
|
||||
|
||||
# Closing the affected profile and confirming the prompt lets the repair
|
||||
# proceed.
|
||||
write_stale_preferences
|
||||
open_browser
|
||||
cat >"$stub_bin/gum" <<'STUB'
|
||||
#!/bin/bash
|
||||
"$CLOSE_BROWSER"
|
||||
touch "${GUM_CALLED:?}"
|
||||
exit 0
|
||||
STUB
|
||||
cat >"$stub_bin/close-browser" <<'STUB'
|
||||
#!/bin/bash
|
||||
rm -f "$HOME/.config/chromium/SingletonLock"
|
||||
STUB
|
||||
chmod +x "$stub_bin/gum" "$stub_bin/close-browser"
|
||||
GUM_CALLED="$test_dir/gum-called" CLOSE_BROWSER="$stub_bin/close-browser" \
|
||||
HOME="$home" PATH="$stub_bin:$PATH" bash -euo pipefail "$migration" >/dev/null 2>&1 ||
|
||||
fail "migration proceeds once the profile is closed and the prompt 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"
|
||||
|
||||
# With the affected profile closed the ghost registration moves to the pinned id.
|
||||
printf '#!/bin/bash\nexit 1\n' >"$stub_bin/gum"
|
||||
close_browser
|
||||
write_stale_preferences
|
||||
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 that same profile is open.
|
||||
rm "$preferences.omarchy-copy-url-repair.bak"
|
||||
repaired_hash=$(sha256sum "$preferences" | cut -d' ' -f1)
|
||||
open_browser
|
||||
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"
|
||||
close_browser
|
||||
|
||||
# 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. A
|
||||
# stub hands the repair call through and opens the profile right after it.
|
||||
write_stale_preferences
|
||||
close_browser
|
||||
rm -f "$preferences.omarchy-copy-url-repair.bak"
|
||||
cat >"$stub_bin/python3" <<'STUB'
|
||||
#!/bin/bash
|
||||
# Called as `python3 -c <script> <preferences> <pinned_id> <check|repair>`, and
|
||||
# the check calls report a surviving ghost through their exit status.
|
||||
"${REAL_PYTHON}" "$@"
|
||||
status=$?
|
||||
[[ ${5:-} == "repair" ]] && ln -sfn "test-host-1234" "$HOME/.config/chromium/SingletonLock"
|
||||
exit $status
|
||||
STUB
|
||||
chmod +x "$stub_bin/python3"
|
||||
if 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 "$stub_bin/python3" "$preferences.omarchy-copy-url-repair.bak"
|
||||
close_browser
|
||||
|
||||
# A browser that started and exited mid-repair restores stale Preferences
|
||||
# before the final profile check; the post-repair file verification catches it.
|
||||
write_stale_preferences
|
||||
cp "$preferences" "$test_dir/stale-preferences"
|
||||
cat >"$stub_bin/python3" <<'STUB'
|
||||
#!/bin/bash
|
||||
"${REAL_PYTHON}" "$@"
|
||||
status=$?
|
||||
[[ ${5:-} == "repair" ]] && cp "${STALE_PREFERENCES:?}" "${REPAIRED_PREFERENCES:?}"
|
||||
exit $status
|
||||
STUB
|
||||
chmod +x "$stub_bin/python3"
|
||||
if HOME="$home" PATH="$stub_bin:$PATH" STALE_PREFERENCES="$test_dir/stale-preferences" \
|
||||
REPAIRED_PREFERENCES="$preferences" 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"
|
||||
rm -f "$stub_bin/python3"
|
||||
close_browser
|
||||
write_stale_preferences
|
||||
run_migration || fail "migration recovers after a reverted repair"
|
||||
rm -f "$preferences.omarchy-copy-url-repair.bak"
|
||||
|
||||
# A repair attempted while the affected profile was open leaves its backup
|
||||
# behind. A rerun that sees a clean disk while that profile 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"
|
||||
open_browser
|
||||
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"
|
||||
close_browser
|
||||
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"
|
||||
@@ -22,6 +22,15 @@ menu_log="$test_tmp/menu"
|
||||
muse_login_log="$test_tmp/muse-login"
|
||||
mkdir -p "$mock_bin" "$test_home"
|
||||
|
||||
cat >"$mock_bin/omarchy-install-chromium-claude" <<'SH'
|
||||
#!/bin/bash
|
||||
echo claude-extension >>"$OMARCHY_TEST_STUB_LOG"
|
||||
if [[ ${OMARCHY_TEST_EXTENSION_FAIL:-false} == "true" ]]; then
|
||||
echo "Extension installation failed" >&2
|
||||
exit 1
|
||||
fi
|
||||
SH
|
||||
|
||||
cat >"$mock_bin/omarchy-notification-send" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\0' "$@" >>"$OMARCHY_TEST_NOTIFICATION_HISTORY"
|
||||
@@ -406,9 +415,16 @@ declare -A expected_packages=(
|
||||
for selection in "${!expected_agents[@]}"; do
|
||||
expected=${expected_agents[$selection]}
|
||||
: >"$agent_open_log"
|
||||
: >"$stub_log"
|
||||
OMARCHY_TEST_AGENT_INSTALLED=true omarchy-default-agent "$selection"
|
||||
[[ $(omarchy-default-agent) == $expected ]] || fail "default agent canonicalizes $selection"
|
||||
|
||||
if [[ $expected == "claude" ]]; then
|
||||
grep -qx claude-extension "$stub_log" || fail "Claude selection installs the browser extension"
|
||||
else
|
||||
[[ ! -s $stub_log ]] || fail "other agents do not install the Claude extension"
|
||||
fi
|
||||
|
||||
mapfile -d '' -t mise_args <"$mise_log"
|
||||
[[ ${mise_args[0]} == "use" && ${mise_args[1]} == "-g" ]] ||
|
||||
fail "default agent installs $selection globally through mise"
|
||||
@@ -426,6 +442,14 @@ pass "default agent selects and opens every supported provider and alias"
|
||||
fail "default agent stores its selection in Omarchy user config"
|
||||
pass "default agent stores its selection in Omarchy user config"
|
||||
|
||||
OMARCHY_TEST_AGENT_INSTALLED=true omarchy-default-agent pi
|
||||
: >"$agent_open_log"
|
||||
OMARCHY_TEST_AGENT_INSTALLED=true OMARCHY_TEST_EXTENSION_FAIL=true omarchy-default-agent claude >"$test_tmp/extension-failure" 2>&1
|
||||
[[ $(omarchy-default-agent) == "claude" ]] || fail "extension installation failure still selects Claude"
|
||||
mapfile -d '' -t agent_open_args <"$agent_open_log"
|
||||
[[ ${agent_open_args[*]} == "omarchy-agent" ]] || fail "extension installation failure still launches Claude"
|
||||
[[ ! -s $test_tmp/extension-failure ]] || fail "extension installation failure is silent"
|
||||
pass "extension installation failure silently continues selecting and launching Claude"
|
||||
OMARCHY_TEST_AGENT_INSTALLED=true omarchy-default-agent pi
|
||||
: >"$notification_history"
|
||||
: >"$agent_open_log"
|
||||
|
||||
@@ -81,13 +81,13 @@ SH
|
||||
rm -rf "$poison_dir"
|
||||
pass "root omarchy-dns resolves system helpers from a trusted PATH, not the invocation PATH"
|
||||
else
|
||||
pass "no unprivileged user namespace; skipping the root trusted-PATH probe"
|
||||
skip "no unprivileged user namespace; skipping the root trusted-PATH probe"
|
||||
fi
|
||||
|
||||
# require_root returns immediately for root, so the stubs below would not stand
|
||||
# between the script and the host's real NetworkManager and resolved config.
|
||||
if (( EUID == 0 )); then
|
||||
pass "running as root; skipping the elevation checks, which would rewrite this machine's DNS"
|
||||
skip "running as root; skipping the elevation checks, which would rewrite this machine's DNS"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
require_command jq
|
||||
|
||||
test_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$test_dir"' EXIT
|
||||
mkdir -p "$test_dir/bin" "$test_dir/home"
|
||||
export CALL_LOG="$test_dir/calls"
|
||||
|
||||
cat >"$test_dir/bin/omarchy-pkg-add" <<'SH'
|
||||
#!/bin/bash
|
||||
printf 'package %s\n' "$*" >>"$CALL_LOG"
|
||||
exit "${PACKAGE_STATUS:-0}"
|
||||
SH
|
||||
cat >"$test_dir/bin/omarchy-shell" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$*" >>"$CALL_LOG"
|
||||
[[ $2 != "rescanPlugins" ]] || exit "${SCAN_STATUS:-0}"
|
||||
printf '%s\n' "${TEST_PUT_RESULT:-ok}"
|
||||
SH
|
||||
cat >"$test_dir/bin/omarchy-restart-shell" <<'SH'
|
||||
#!/bin/bash
|
||||
echo 'migration must leave the restart to omarchy update' >&2
|
||||
exit 1
|
||||
SH
|
||||
chmod +x "$test_dir/bin/"*
|
||||
|
||||
mkdir -p "$test_dir/packaged/shell/plugins/omacom.elsewhen"
|
||||
sed "s|/usr/share/omarchy|$test_dir/packaged|g" "$ROOT/migrations/1789581661.sh" >"$test_dir/migration.sh"
|
||||
|
||||
plugin="$test_dir/home/.config/omarchy/plugins/omacom.elsewhen"
|
||||
run_migration() {
|
||||
: >"$CALL_LOG"
|
||||
HOME="$test_dir/home" OMARCHY_PATH="${1:-$test_dir/packaged}" PATH="$test_dir/bin:$ROOT/bin:$PATH" \
|
||||
bash -euo pipefail "$test_dir/migration.sh" >"$test_dir/output" 2>&1
|
||||
}
|
||||
|
||||
if PACKAGE_STATUS=1 run_migration; then
|
||||
fail "package failure stops the migration"
|
||||
fi
|
||||
[[ ! -e $plugin && ! -L $plugin && $(cat "$CALL_LOG") == "package elsewhen" ]] ||
|
||||
fail "package failure leaves the plugin and shell untouched"
|
||||
pass "package failure stops before changing the shell"
|
||||
|
||||
run_migration
|
||||
[[ ! -e $plugin && ! -L $plugin ]] || fail "migration does not create a user plugin link"
|
||||
[[ ! -e $ROOT/config/omarchy/plugins/omacom.elsewhen && ! -L $ROOT/config/omarchy/plugins/omacom.elsewhen ]] || fail "fresh installs do not ship a user plugin link"
|
||||
pass "migration and fresh installs rely on the packaged plugin directory"
|
||||
|
||||
expected=$'package elsewhen\nshell rescanPlugins\nshell putBarWidget omacom.elsewhen {"before":"omarchy.clock"}'
|
||||
[[ $(cat "$CALL_LOG") == "$expected" ]] || fail "install, scan and placement run in order" "$(cat "$CALL_LOG")"
|
||||
pass "real bar helper enables and places before the clock without restarting during reload"
|
||||
|
||||
run_migration
|
||||
[[ $(cat "$CALL_LOG") == "$expected" ]] || fail "migration can be rerun"
|
||||
[[ ! -e $plugin && ! -L $plugin ]] || fail "rerunning the migration does not create a user plugin link"
|
||||
pass "migration can be rerun without a user plugin link"
|
||||
|
||||
run_migration "$ROOT"
|
||||
[[ $(readlink "$plugin") == "$test_dir/packaged/shell/plugins/omacom.elsewhen" ]] || fail "dev checkout links the packaged plugin"
|
||||
pass "dev checkout discovers Elsewhen through a user plugin link"
|
||||
run_migration "$ROOT"
|
||||
[[ $(readlink "$plugin") == "$test_dir/packaged/shell/plugins/omacom.elsewhen" ]] || fail "dev link survives a rerun"
|
||||
pass "dev link is idempotent"
|
||||
rm "$plugin"
|
||||
|
||||
mkdir -p "$plugin"
|
||||
printf 'local work\n' >"$plugin/notes"
|
||||
run_migration "$ROOT"
|
||||
[[ ! -L $plugin && $(cat "$plugin/notes") == "local work" ]] || fail "existing checkout is preserved"
|
||||
pass "existing checkout and local files are preserved"
|
||||
|
||||
rm "$plugin/notes"
|
||||
rmdir "$plugin"
|
||||
ln -s "$test_dir/custom-plugin" "$plugin"
|
||||
run_migration "$ROOT"
|
||||
[[ $(readlink "$plugin") == "$test_dir/custom-plugin" ]] || fail "existing symlink is preserved"
|
||||
pass "existing symlink is preserved, including a missing target"
|
||||
|
||||
for failure in 'SCAN_STATUS=1' 'TEST_PUT_RESULT=unknown'; do
|
||||
if env "$failure" HOME="$test_dir/home" OMARCHY_PATH="$ROOT" PATH="$test_dir/bin:$ROOT/bin:$PATH" \
|
||||
bash -euo pipefail "$test_dir/migration.sh" >"$test_dir/output" 2>&1; then
|
||||
fail "$failure must leave the migration pending"
|
||||
fi
|
||||
pass "$failure leaves the migration pending"
|
||||
done
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
if (( EUID != 0 )); then
|
||||
if unshare --user --map-root-user true 2>/dev/null; then
|
||||
exec unshare --user --map-root-user bash "$0"
|
||||
fi
|
||||
skip "no unprivileged user namespace; skipping factory account cleanup"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
|
||||
# Load the production functions without self-elevation or the reset entrypoint.
|
||||
awk '
|
||||
/^[a-z_]+\(\) \{/ { copying = 1 }
|
||||
copying { print }
|
||||
/^}/ { copying = 0 }
|
||||
' "$ROOT/bin/omarchy-system-factory-reset" >"$test_tmp/functions"
|
||||
|
||||
cat >"$test_tmp/reset" <<'SH'
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
source "$1/functions"
|
||||
TOP_MNT="$2"
|
||||
NEXT_NAME=@omarchy-reset-next
|
||||
PROVISIONING_DIR=/var/lib/omarchy/provisioning
|
||||
LOG_FILE="$TOP_MNT/reset.log"
|
||||
|
||||
log() { printf '%s\n' "$1" >>"$LOG_FILE"; }
|
||||
fail() { log "$1"; exit 1; }
|
||||
|
||||
# Account tools are real. Only snapshots, boot rebuilding, and system services
|
||||
# are replaced: all writes stay inside this test's disposable directory.
|
||||
btrfs() {
|
||||
if [[ $1 == "subvolume" && $2 == "snapshot" ]]; then
|
||||
mkdir -p "$4"
|
||||
cp -a "$3/." "$4/"
|
||||
elif [[ $1 == "property" ]]; then
|
||||
printf '%s\n' "$6" >"$4/read-only"
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
systemd-id128() { printf '%032d\n' 1; }
|
||||
install_provisioning_units() { :; }
|
||||
encrypted_install() { return 1; }
|
||||
rebuild_next_boot() { touch "$TOP_MNT/rebuilt"; }
|
||||
sync() { :; }
|
||||
|
||||
userdel() {
|
||||
[[ ${FAIL_COMMAND:-} == "userdel" && $2 == "$FAIL_ROOT" ]] && return 42
|
||||
command userdel "$@"
|
||||
}
|
||||
usermod() {
|
||||
[[ ${FAIL_COMMAND:-} == "usermod" && $2 == "$FAIL_ROOT" ]] && return 42
|
||||
command usermod "$@"
|
||||
}
|
||||
rm() {
|
||||
[[ ${FAIL_COMMAND:-} == "rm" && $* == *"$FAIL_ROOT/etc/shadow-"* ]] && return 42
|
||||
command rm "$@"
|
||||
}
|
||||
|
||||
stage_full_reset
|
||||
SH
|
||||
|
||||
make_fixture() {
|
||||
local top="$1" root_hash="${2:-original-root-hash}"
|
||||
local factory="$top/@factory"
|
||||
mkdir -p "$top/@" "$factory/etc" "$factory/home/seller" \
|
||||
"$factory/usr/bin" "$factory/usr/share/omarchy/install/provisioning" \
|
||||
"$factory/var/lib/omarchy/provisioning/packages"
|
||||
touch "$top/@/old-system" "$factory/home/seller/private-file" \
|
||||
"$factory/usr/share/omarchy/install/provisioning/omarchy-provision-owner.service" \
|
||||
"$factory/var/lib/omarchy/provisioning/packages/node-v0.tar.gz"
|
||||
printf '#!/bin/bash\n' >"$factory/usr/bin/omarchy-provision-owner"
|
||||
chmod +x "$factory/usr/bin/omarchy-provision-owner"
|
||||
printf 'true\n' >"$factory/read-only"
|
||||
cat >"$factory/etc/passwd" <<'EOF'
|
||||
root:x:0:0:root:/root:/bin/bash
|
||||
daemon:x:1:1:daemon:/:/usr/bin/nologin
|
||||
seller:x:1000:1000:Seller:/home/seller:/bin/bash
|
||||
EOF
|
||||
printf 'root:%s:20000:0:99999:7:::\ndaemon:*:20000:0:99999:7:::\nseller:original-user-hash:20000:0:99999:7:::\n' \
|
||||
"$root_hash" >"$factory/etc/shadow"
|
||||
printf 'root:x:0:\ndaemon:x:1:\nseller:x:1000:\nwheel:x:998:seller\n' >"$factory/etc/group"
|
||||
printf 'root:!::\ndaemon:!::\nseller:!::\nwheel:!::seller\n' >"$factory/etc/gshadow"
|
||||
printf 'USERGROUPS_ENAB yes\n' >"$factory/etc/login.defs"
|
||||
printf 'seller:100000:65536\n' >"$factory/etc/subuid"
|
||||
printf 'seller:100000:65536\n' >"$factory/etc/subgid"
|
||||
chmod 600 "$factory/etc/"{shadow,gshadow}
|
||||
for file in passwd shadow group gshadow subuid subgid; do
|
||||
cp "$factory/etc/$file" "$factory/etc/$file-"
|
||||
done
|
||||
}
|
||||
|
||||
assert_scrubbed() {
|
||||
local root="$1" file
|
||||
[[ $(awk -F: '$1 == "root" { print $2 }' "$root/etc/shadow") == "!" ]] ||
|
||||
fail "reset erases the root hash while keeping the account locked"
|
||||
! grep -q 'original-.*-hash\|seller' "$root/etc/"{passwd,shadow,group,gshadow} ||
|
||||
fail "reset removes seller account credentials and group membership"
|
||||
[[ ! -e $root/home/seller ]] || fail "reset removes the seller's baseline home"
|
||||
grep -q '^daemon:\*:' "$root/etc/shadow" || fail "reset preserves service accounts"
|
||||
[[ $(stat -c '%a' "$root/etc/shadow") == "600" ]] || fail "shadow stays private"
|
||||
for file in passwd shadow group gshadow subuid subgid; do
|
||||
[[ ! -e $root/etc/$file- ]] || fail "reset removes the $file backup"
|
||||
done
|
||||
}
|
||||
|
||||
for scenario in normal locked; do
|
||||
top="$test_tmp/$scenario"
|
||||
if [[ $scenario == "locked" ]]; then
|
||||
make_fixture "$top" '!'
|
||||
else
|
||||
make_fixture "$top"
|
||||
fi
|
||||
bash "$test_tmp/reset" "$test_tmp" "$top" || fail "$scenario reset stages successfully"
|
||||
assert_scrubbed "$top/@factory"
|
||||
assert_scrubbed "$top/@"
|
||||
[[ $(cat "$top/@factory/read-only") == "true" ]] || fail "baseline returns to read-only"
|
||||
[[ -f $top/@/var/lib/omarchy/provisioning/pending && -f $top/rebuilt ]] ||
|
||||
fail "reset reaches provisioning after cleanup"
|
||||
|
||||
bash "$test_tmp/reset" "$test_tmp" "$top" || fail "$scenario reset can be repeated"
|
||||
assert_scrubbed "$top/@factory"
|
||||
assert_scrubbed "$top/@"
|
||||
pass "$scenario reset scrubs both roots, preserves service accounts, and can be repeated"
|
||||
done
|
||||
|
||||
for target in @omarchy-reset-next @factory; do
|
||||
for command in userdel usermod rm; do
|
||||
top="$test_tmp/fail-$target-$command"
|
||||
make_fixture "$top"
|
||||
if FAIL_COMMAND="$command" FAIL_ROOT="$top/$target" bash "$test_tmp/reset" "$test_tmp" "$top"; then
|
||||
fail "reset accepted failed $command in $target"
|
||||
fi
|
||||
[[ -f $top/@/old-system && ! -e $top/rebuilt ]] ||
|
||||
fail "failed cleanup must not activate or rebuild the reset system"
|
||||
[[ $(cat "$top/@factory/read-only") == "true" ]] ||
|
||||
fail "failed cleanup must leave the baseline read-only"
|
||||
pass "failed $command in $target aborts reset before activation"
|
||||
done
|
||||
done
|
||||
@@ -4,7 +4,7 @@ source "$(dirname "$0")/base-test.sh"
|
||||
|
||||
# The hook guards on the real /etc/pam.d path, which can't be mocked via PATH.
|
||||
if [[ -f /etc/pam.d/omarchy-lock-fingerprint ]]; then
|
||||
pass "fingerprint invitation test skipped: host already has fingerprint auth configured"
|
||||
skip "fingerprint invitation test skipped: host already has fingerprint auth configured"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
|
||||
ShellRoot {
|
||||
id: root
|
||||
property var view
|
||||
property var wallpaper
|
||||
property var feed
|
||||
property int step: 0
|
||||
|
||||
Item { id: host; width: 1000; height: 700 }
|
||||
|
||||
function findItem(item, name) {
|
||||
if (item.objectName === name) return item
|
||||
for (var i = 0; i < item.children.length; i++) {
|
||||
var found = findItem(item.children[i], name)
|
||||
if (found) return found
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
function check(condition, message) {
|
||||
if (!condition) throw new Error(message)
|
||||
}
|
||||
|
||||
Timer {
|
||||
interval: 150
|
||||
repeat: true
|
||||
running: true
|
||||
onTriggered: {
|
||||
try {
|
||||
if (root.step === 0) {
|
||||
var component = Qt.createComponent("file://" + Quickshell.env("OMARCHY_PATH") + "/shell/plugins/lock/LockView.qml")
|
||||
check(component.status === Component.Ready, component.errorString())
|
||||
root.view = component.createObject(host, {width: 1000, height: 700, backgroundPath: "/still.png", loadBackground: false})
|
||||
check(root.view !== null, component.errorString())
|
||||
root.wallpaper = findItem(root.view, "lockWallpaper")
|
||||
root.feed = findItem(root.view, "lockFeedLoader")
|
||||
check(root.wallpaper.path === "", "hidden still must not decode")
|
||||
root.view.backgroundPath = "/video.mp4"
|
||||
root.view.videoPosterPath = "/poster.jpg"
|
||||
} else if (root.step === 1) {
|
||||
check(!root.feed.active && root.feed.item === null, "hidden video must not load a feed client")
|
||||
root.view.loadBackground = true
|
||||
root.view.powerSaverActive = true
|
||||
} else if (root.step === 2) {
|
||||
check(root.wallpaper.path === "/poster.jpg", "power saver keeps the video poster")
|
||||
check(!root.feed.active, "power saver must not connect to the feed")
|
||||
root.view.powerSaverActive = false
|
||||
} else if (root.step === 3) {
|
||||
check(root.feed.active, "visible video enables the isolated feed loader")
|
||||
check(root.wallpaper.path === "/poster.jpg", "poster stays beneath the feed before its first frame")
|
||||
root.view.displaysBlank = true
|
||||
} else if (root.step === 4) {
|
||||
check(!root.feed.active && root.feed.item === null, "blanking releases the feed client")
|
||||
root.view.loadBackground = false
|
||||
} else {
|
||||
check(root.wallpaper.path === "", "unlock releases the poster image")
|
||||
console.log("OWE_LOCK_TEST_PASS")
|
||||
Qt.quit()
|
||||
}
|
||||
root.step++
|
||||
} catch (error) {
|
||||
console.error("OWE_LOCK_TEST_FAIL: " + error)
|
||||
Qt.quit()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh"
|
||||
|
||||
work_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$work_dir"' EXIT
|
||||
|
||||
fake_home="$work_dir/home"
|
||||
mkdir -p "$fake_home/.config/omarchy/hooks" "$fake_home/.local/state/omarchy"
|
||||
|
||||
# --- omarchy-hook --------------------------------------------------------------
|
||||
|
||||
# A hook name is a label, not a path. One carrying a slash, or one that is a
|
||||
# bare `.` or `..`, would run a script from outside the hooks directory.
|
||||
|
||||
cat >"$fake_home/.config/omarchy/hooks/test-hook" <<'SH'
|
||||
touch "$HOME/hook-ran"
|
||||
SH
|
||||
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-hook" test-hook
|
||||
[[ -f $fake_home/hook-ran ]] ||
|
||||
fail "omarchy hook runs a named hook from the hooks directory"
|
||||
pass "omarchy hook runs a named hook from the hooks directory"
|
||||
|
||||
# Dots inside a name are not a path. a..b stays inside the hooks directory.
|
||||
cat >"$fake_home/.config/omarchy/hooks/a..b" <<'SH'
|
||||
touch "$HOME/dotted-hook-ran"
|
||||
SH
|
||||
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-hook" a..b
|
||||
[[ -f $fake_home/dotted-hook-ran ]] ||
|
||||
fail "omarchy hook accepts a hook name with dots in the middle"
|
||||
pass "omarchy hook accepts a hook name with dots in the middle"
|
||||
|
||||
for name in . ..; do
|
||||
status=0
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-hook" "$name" >/dev/null 2>&1 || status=$?
|
||||
(( status == 2 )) ||
|
||||
fail "omarchy hook refuses a hook name of $name" "exit: $status"
|
||||
pass "omarchy hook refuses a hook name of $name"
|
||||
done
|
||||
|
||||
# This file sits where a name of ../../evil would resolve: hooks/../.. is
|
||||
# ~/.config.
|
||||
cat >"$fake_home/.config/evil" <<'SH'
|
||||
touch "$HOME/escape-ran"
|
||||
SH
|
||||
chmod +x "$fake_home/.config/evil"
|
||||
|
||||
status=0
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-hook" "../../evil" >/dev/null 2>&1 || status=$?
|
||||
(( status == 2 )) ||
|
||||
fail "omarchy hook refuses a hook name with a dot-dot" "exit: $status"
|
||||
[[ ! -e $fake_home/escape-ran ]] ||
|
||||
fail "omarchy hook runs nothing when it refuses the name"
|
||||
pass "omarchy hook refuses a hook name with a dot-dot"
|
||||
|
||||
status=0
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-hook" "sub/dir" >/dev/null 2>&1 || status=$?
|
||||
(( status == 2 )) ||
|
||||
fail "omarchy hook refuses a hook name with a slash" "exit: $status"
|
||||
pass "omarchy hook refuses a hook name with a slash"
|
||||
|
||||
# --- omarchy-hook-install ------------------------------------------------------
|
||||
|
||||
# The installer joins the type into ~/.config/omarchy/hooks/<type>.d before
|
||||
# mkdir/cp. The runner already refuses a slashed type; install must too, or a
|
||||
# name the runner will not run still lands on disk.
|
||||
|
||||
source_hook="$work_dir/source-hook"
|
||||
cat >"$source_hook" <<'SH'
|
||||
#!/bin/bash
|
||||
true
|
||||
SH
|
||||
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-hook-install" post-update "$source_hook" >/dev/null
|
||||
[[ -f $fake_home/.config/omarchy/hooks/post-update.d/source-hook ]] ||
|
||||
fail "omarchy hook install still installs a named hook"
|
||||
pass "omarchy hook install still installs a named hook"
|
||||
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-hook-install" a..b "$source_hook" >/dev/null
|
||||
[[ -f $fake_home/.config/omarchy/hooks/a..b.d/source-hook ]] ||
|
||||
fail "omarchy hook install accepts a hook name with dots in the middle"
|
||||
pass "omarchy hook install accepts a hook name with dots in the middle"
|
||||
|
||||
for name in . ..; do
|
||||
status=0
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-hook-install" "$name" "$source_hook" >/dev/null 2>&1 || status=$?
|
||||
(( status == 2 )) ||
|
||||
fail "omarchy hook install refuses a hook name of $name" "exit: $status"
|
||||
[[ ! -e $fake_home/.config/omarchy/hooks/${name}.d ]] ||
|
||||
fail "omarchy hook install creates no directory for a hook name of $name"
|
||||
pass "omarchy hook install refuses a hook name of $name"
|
||||
done
|
||||
|
||||
# hooks/../../evil.d is ~/.config/evil.d. The guard must fire before mkdir.
|
||||
status=0
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-hook-install" "../../evil" "$source_hook" >/dev/null 2>&1 || status=$?
|
||||
(( status == 2 )) ||
|
||||
fail "omarchy hook install refuses a hook name with a dot-dot" "exit: $status"
|
||||
[[ ! -e $fake_home/.config/evil.d ]] ||
|
||||
fail "omarchy hook install creates nothing outside the hooks directory"
|
||||
pass "omarchy hook install refuses a hook name with a dot-dot"
|
||||
|
||||
status=0
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-hook-install" "sub/dir" "$source_hook" >/dev/null 2>&1 || status=$?
|
||||
(( status == 2 )) ||
|
||||
fail "omarchy hook install refuses a hook name with a slash" "exit: $status"
|
||||
[[ ! -e $fake_home/.config/omarchy/hooks/sub ]] ||
|
||||
fail "omarchy hook install creates no nested directory from a slashed name"
|
||||
pass "omarchy hook install refuses a hook name with a slash"
|
||||
|
||||
# --- omarchy-state -------------------------------------------------------------
|
||||
|
||||
state_dir="$fake_home/.local/state/omarchy"
|
||||
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-state" set reboot-required
|
||||
[[ -f $state_dir/reboot-required ]] ||
|
||||
fail "omarchy state set still creates a plain state file"
|
||||
pass "omarchy state set still creates a plain state file"
|
||||
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-state" set v1..2
|
||||
[[ -f $state_dir/v1..2 ]] ||
|
||||
fail "omarchy state set accepts a state name with dots in the middle"
|
||||
pass "omarchy state set accepts a state name with dots in the middle"
|
||||
|
||||
for name in . ..; do
|
||||
status=0
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-state" set "$name" >/dev/null 2>&1 || status=$?
|
||||
(( status == 2 )) ||
|
||||
fail "omarchy state set refuses a state name of $name" "exit: $status"
|
||||
pass "omarchy state set refuses a state name of $name"
|
||||
done
|
||||
|
||||
# state/../.. is ~/.local. The guard must fire before touch gets there.
|
||||
status=0
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-state" set "../../escape" >/dev/null 2>&1 || status=$?
|
||||
(( status == 2 )) ||
|
||||
fail "omarchy state set refuses a state name with a dot-dot" "exit: $status"
|
||||
[[ ! -e $fake_home/.local/escape ]] ||
|
||||
fail "omarchy state set creates nothing outside the state directory"
|
||||
pass "omarchy state set refuses a state name with a dot-dot"
|
||||
|
||||
status=0
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-state" set "sub/dir" >/dev/null 2>&1 || status=$?
|
||||
(( status == 2 )) ||
|
||||
fail "omarchy state set refuses a state name with a slash" "exit: $status"
|
||||
pass "omarchy state set refuses a state name with a slash"
|
||||
|
||||
# clear takes patterns by design ("state-name-or-pattern") and matches
|
||||
# basenames through find -name, so it can never walk out of the directory.
|
||||
touch "$state_dir/restart-a-required" "$state_dir/restart-b-required" "$state_dir/keep-me"
|
||||
HOME="$fake_home" "$ROOT/bin/omarchy-state" clear "restart-*-required"
|
||||
[[ ! -e $state_dir/restart-a-required && ! -e $state_dir/restart-b-required && -f $state_dir/keep-me ]] ||
|
||||
fail "omarchy state clear still clears matching patterns only"
|
||||
pass "omarchy state clear still clears matching patterns only"
|
||||
@@ -21,7 +21,7 @@ trap cleanup EXIT
|
||||
require_compositor "QML contract test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping QML contract test"
|
||||
skip "quickshell not installed; skipping QML contract test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
tmp_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
mkdir -p "$tmp_dir/bin"
|
||||
export INSTALLED_PACKAGES="$tmp_dir/installed" CALL_LOG="$tmp_dir/calls"
|
||||
export PATH="$tmp_dir/bin:$ROOT/bin:$PATH"
|
||||
|
||||
# Keep the real package helpers, but contain every pacman transaction here.
|
||||
cat > "$tmp_dir/bin/pacman" <<'SH'
|
||||
#!/bin/bash
|
||||
case "$1" in
|
||||
-Q) grep -Fxq -- "$2" "$INSTALLED_PACKAGES" ;;
|
||||
-S)
|
||||
[[ ${FAIL_INSTALL:-0} == 0 ]] || exit 1
|
||||
shift 3 # -S --noconfirm --needed
|
||||
printf '%s\n' "$@" >> "$INSTALLED_PACKAGES"
|
||||
printf '%s\n' "$@" >> "$CALL_LOG"
|
||||
;;
|
||||
*) exit 1 ;;
|
||||
esac
|
||||
SH
|
||||
cat > "$tmp_dir/bin/sudo" <<'SH'
|
||||
#!/bin/bash
|
||||
[[ $1 == "pacman" ]] || exit 1
|
||||
"$@"
|
||||
SH
|
||||
chmod +x "$tmp_dir/bin/"*
|
||||
|
||||
migration="$ROOT/migrations/1789444024.sh"
|
||||
for kernels in linux-omarchy linux-t2 'linux-omarchy linux-t2'; do
|
||||
read -ra installed <<< "$kernels"
|
||||
printf '%s\n' linux linux-headers "${installed[@]}" > "$INSTALLED_PACKAGES"
|
||||
: > "$CALL_LOG"
|
||||
bash -euo pipefail "$migration" >/dev/null
|
||||
for kernel in "${installed[@]}"; do
|
||||
grep -Fxq "$kernel-headers" "$INSTALLED_PACKAGES" || fail "$kernel gets its headers"
|
||||
done
|
||||
: > "$CALL_LOG"
|
||||
bash -euo pipefail "$migration" >/dev/null
|
||||
[[ ! -s $CALL_LOG ]] || fail "header repair is idempotent"
|
||||
pass "missing headers are repaired once for $kernels"
|
||||
done
|
||||
|
||||
printf '%s\n' linux linux-aarch64 > "$INSTALLED_PACKAGES"
|
||||
: > "$CALL_LOG"
|
||||
bash -euo pipefail "$migration" >/dev/null
|
||||
[[ ! -s $CALL_LOG ]] || fail "header repair skips unrelated kernels"
|
||||
pass "header repair skips unrelated kernels"
|
||||
|
||||
echo linux-omarchy > "$INSTALLED_PACKAGES"
|
||||
if FAIL_INSTALL=1 bash -euo pipefail "$migration" >/dev/null; then
|
||||
fail "a failed header installation must leave the migration pending"
|
||||
fi
|
||||
pass "header installation failure is propagated"
|
||||
@@ -150,5 +150,5 @@ pass "font controls create missing Kitty overrides without restoring the theme i
|
||||
if "$ROOT/bin/omarchy-cmd-present" kitty; then
|
||||
kitty +runpy "$(cat "$ROOT/test/shell.d/fixtures/kitty/check-config.py")"
|
||||
else
|
||||
pass "Kitty not installed; skipping native config parser checks"
|
||||
skip "Kitty not installed; skipping native config parser checks"
|
||||
fi
|
||||
@@ -4,6 +4,9 @@ set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
# Start with color enabled; the no-color scenarios opt in below.
|
||||
unset NO_COLOR
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
|
||||
|
||||
@@ -32,15 +32,6 @@ check(command == ["/usr/bin/updatedb", *options],
|
||||
"locate service runs updatedb directly with fixed Btrfs options")
|
||||
check("ConditionACPower=true" in (root / "etc/systemd/system/plocate-updatedb.service.d/ac-only.conf").read_text(),
|
||||
"scheduled locate indexing keeps its AC-power condition")
|
||||
check(not (root / "install/config/locate.sh").exists() and not (root / "migrations/1784809451.sh").exists(),
|
||||
"the retired locate configuration helper and migration are absent")
|
||||
for directory in ("bin", "install", "migrations"):
|
||||
for path in (root / directory).rglob("*"):
|
||||
if path.is_file():
|
||||
content = path.read_text()
|
||||
if "OMARCHY_UPDATEDB_CONF_PATH" in content or "config/locate.sh" in content:
|
||||
raise SystemExit("not ok - retired locate configuration path remains in " + str(path))
|
||||
check(True, "runtime and installation no longer reference the configuration rewrite")
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="omarchy-locate-") as scratch:
|
||||
scratch = Path(scratch)
|
||||
|
||||
@@ -21,7 +21,7 @@ trap cleanup EXIT
|
||||
require_compositor "lock fingerprint indicator test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping lock fingerprint indicator test"
|
||||
skip "quickshell not installed; skipping lock fingerprint indicator test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ trap cleanup EXIT
|
||||
require_compositor "lock password overflow test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping lock password overflow test"
|
||||
skip "quickshell not installed; skipping lock password overflow test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ trap cleanup EXIT
|
||||
require_compositor "manifest entrypoint load test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping manifest entrypoint load test"
|
||||
skip "quickshell not installed; skipping manifest entrypoint load test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
source "$(dirname "${BASH_SOURCE[0]}")/base-test.sh"
|
||||
|
||||
require_command mise
|
||||
|
||||
test_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$test_dir"' EXIT
|
||||
migration="$ROOT/migrations/1789095456.sh"
|
||||
|
||||
run_migration() {
|
||||
local test_home="$1"
|
||||
|
||||
env -i \
|
||||
HOME="$test_home" \
|
||||
XDG_CACHE_HOME="$test_home/.cache" \
|
||||
XDG_CONFIG_HOME="$test_home/.config" \
|
||||
XDG_DATA_HOME="$test_home/.local/share" \
|
||||
XDG_STATE_HOME="$test_home/.local/state" \
|
||||
MISE_PARANOID="${OMARCHY_TEST_MISE_PARANOID:-false}" \
|
||||
PATH=/usr/bin \
|
||||
bash -euo pipefail "$migration"
|
||||
}
|
||||
|
||||
run_mise() {
|
||||
local test_home="$1"
|
||||
shift
|
||||
|
||||
env -i \
|
||||
HOME="$test_home" \
|
||||
XDG_CACHE_HOME="$test_home/.cache" \
|
||||
XDG_CONFIG_HOME="$test_home/.config" \
|
||||
XDG_DATA_HOME="$test_home/.local/share" \
|
||||
XDG_STATE_HOME="$test_home/.local/state" \
|
||||
MISE_PARANOID="${OMARCHY_TEST_MISE_PARANOID:-false}" \
|
||||
PATH=/usr/bin \
|
||||
mise "$@"
|
||||
}
|
||||
|
||||
mise_environment() {
|
||||
local test_home="$1"
|
||||
local project="$2"
|
||||
|
||||
(
|
||||
cd "$project"
|
||||
run_mise "$test_home" env -s bash
|
||||
)
|
||||
}
|
||||
|
||||
mise_path_active() {
|
||||
local test_home="$1"
|
||||
local project="$2"
|
||||
local output
|
||||
|
||||
if ! output=$(mise_environment "$test_home" "$project" 2>/dev/null); then
|
||||
return 1
|
||||
fi
|
||||
|
||||
grep -F "$project/bin" <<<"$output" >/dev/null
|
||||
}
|
||||
|
||||
assert_unsafe_variant_removed() {
|
||||
local variant="$1"
|
||||
local assignment="$2"
|
||||
local variant_home="$test_dir/$variant-home"
|
||||
local variant_config="$variant_home/Work/.mise.toml"
|
||||
local variant_project="$variant_home/Work/tries/untrusted-repository"
|
||||
|
||||
mkdir -p "$variant_project/bin"
|
||||
printf '[env]\n%s\n' "$assignment" >"$variant_config"
|
||||
run_mise "$variant_home" trust "$variant_config" >/dev/null
|
||||
|
||||
mise_path_active "$variant_home" "$variant_project" || fail "$variant legacy config prepends the repository bin directory"
|
||||
|
||||
run_migration "$variant_home" >/dev/null
|
||||
if mise_path_active "$variant_home" "$variant_project"; then
|
||||
fail "$variant repository bin directory remains in PATH after migration"
|
||||
fi
|
||||
}
|
||||
|
||||
install_home="$test_dir/install-home"
|
||||
install_log="$test_dir/install-mise.log"
|
||||
mkdir -p "$install_home" "$test_dir/bin"
|
||||
cat >"$test_dir/bin/mise" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\n' "$*" >>"$MISE_TEST_LOG"
|
||||
SH
|
||||
chmod +x "$test_dir/bin/mise"
|
||||
|
||||
env \
|
||||
HOME="$install_home" \
|
||||
MISE_TEST_LOG="$install_log" \
|
||||
OMARCHY_SETUP_CONTEXT=runtime \
|
||||
PATH="$test_dir/bin:/usr/bin" \
|
||||
bash -euo pipefail -c 'source "$1"' bash "$ROOT/install/user/mise-work.sh"
|
||||
|
||||
[[ -d $install_home/Work/tries ]] || fail "installer creates the work and tries directories"
|
||||
[[ ! -e $install_home/Work/.mise.toml ]] || fail "installer does not create a trusted Work Mise config"
|
||||
[[ $(<"$install_log") == "use -g node@latest" ]] || fail "installer only invokes Mise for the global Node setup"
|
||||
pass "new installs do not add project bin directories to PATH"
|
||||
|
||||
stock_home="$test_dir/stock-home"
|
||||
stock_config="$stock_home/Work/.mise.toml"
|
||||
stock_project="$stock_home/Work/tries/untrusted-repository"
|
||||
mkdir -p "$stock_project/bin"
|
||||
cat >"$stock_config" <<'TOML'
|
||||
[env]
|
||||
_.path = "{{ cwd }}/bin"
|
||||
TOML
|
||||
run_mise "$stock_home" trust "$stock_config" >/dev/null
|
||||
|
||||
mise_path_active "$stock_home" "$stock_project" || fail "legacy config prepends the repository bin directory"
|
||||
|
||||
run_migration "$stock_home" >/dev/null
|
||||
[[ ! -e $stock_config ]] || fail "migration removes the stock Work Mise config"
|
||||
cat >"$stock_config" <<'TOML'
|
||||
[env]
|
||||
_.path = "{{ cwd }}/bin"
|
||||
TOML
|
||||
if mise_path_active "$stock_home" "$stock_project"; then
|
||||
fail "recreated Work config remains trusted after migration"
|
||||
fi
|
||||
run_migration "$stock_home" >/dev/null
|
||||
[[ ! -e $stock_config ]] || fail "stock migration is idempotent"
|
||||
pass "migration removes the repository bin directory and revokes the Work trust root"
|
||||
|
||||
assert_unsafe_variant_removed inline-comment '_.path = "{{ cwd }}/bin" # Omarchy default'
|
||||
assert_unsafe_variant_removed single-quoted "_.path = '{{ cwd }}/bin'"
|
||||
pass "migration removes annotated and single-quoted project bin paths"
|
||||
|
||||
custom_home="$test_dir/custom-home"
|
||||
custom_config="$custom_home/Work/.mise.toml"
|
||||
mkdir -p "$(dirname "$custom_config")"
|
||||
cat >"$custom_config" <<'TOML'
|
||||
[env]
|
||||
KEEP = "yes"
|
||||
_.path = "{{ cwd }}/bin"
|
||||
# _.path = "{{ cwd }}/bin"
|
||||
|
||||
[tools]
|
||||
ruby = "latest"
|
||||
|
||||
[other]
|
||||
_.path = "{{ cwd }}/bin"
|
||||
TOML
|
||||
cp "$custom_config" "$test_dir/custom-original"
|
||||
cat >"$test_dir/custom-expected" <<'TOML'
|
||||
[env]
|
||||
KEEP = "yes"
|
||||
# _.path = "{{ cwd }}/bin"
|
||||
|
||||
[tools]
|
||||
ruby = "latest"
|
||||
|
||||
[other]
|
||||
_.path = "{{ cwd }}/bin"
|
||||
TOML
|
||||
chmod 600 "$custom_config"
|
||||
run_mise "$custom_home" trust "$custom_config" >/dev/null
|
||||
|
||||
custom_output=$(run_migration "$custom_home")
|
||||
cmp -s "$test_dir/custom-expected" "$custom_config" || fail "migration preserves unrelated custom Mise settings"
|
||||
[[ $(stat -c %a "$custom_config") == "600" ]] || fail "migration preserves custom config permissions"
|
||||
grep -F "mise trust $custom_config" <<<"$custom_output" >/dev/null || fail "migration explains how to review and re-trust a custom config"
|
||||
custom_backups=("$custom_config".bak.*)
|
||||
[[ -f ${custom_backups[0]} ]] || fail "migration backs up a customized Mise config"
|
||||
(( ${#custom_backups[@]} == 1 )) || fail "migration creates one custom config backup"
|
||||
cmp -s "$test_dir/custom-original" "${custom_backups[0]}" || fail "custom config backup preserves the original"
|
||||
|
||||
run_migration "$custom_home" >/dev/null
|
||||
custom_backups=("$custom_config".bak.*)
|
||||
(( ${#custom_backups[@]} == 1 )) || fail "custom migration does not create another backup on rerun"
|
||||
cmp -s "$test_dir/custom-expected" "$custom_config" || fail "custom migration is idempotent"
|
||||
pass "custom Mise settings, permissions, and original backup survive the repair"
|
||||
|
||||
unrelated_home="$test_dir/unrelated-home"
|
||||
unrelated_config="$unrelated_home/Work/.mise.toml"
|
||||
unrelated_project="$unrelated_home/Work/tries/untrusted-repository"
|
||||
mkdir -p "$unrelated_project/bin"
|
||||
printf '[env]\nKEEP = "yes"\n' >"$unrelated_config"
|
||||
cp "$unrelated_config" "$test_dir/unrelated-original"
|
||||
run_mise "$unrelated_home" trust "$unrelated_config" >/dev/null
|
||||
run_migration "$unrelated_home" >/dev/null
|
||||
cmp -s "$test_dir/unrelated-original" "$unrelated_config" || fail "unrelated Mise config remains unchanged"
|
||||
unrelated_backups=("$unrelated_config".bak.*)
|
||||
[[ ! -e ${unrelated_backups[0]} ]] || fail "unchanged Mise config is not backed up"
|
||||
printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$unrelated_config"
|
||||
if mise_path_active "$unrelated_home" "$unrelated_project"; then
|
||||
fail "safe Work config retains its old trust grant"
|
||||
fi
|
||||
|
||||
absent_home="$test_dir/absent-home"
|
||||
absent_config="$absent_home/Work/.mise.toml"
|
||||
absent_project="$absent_home/Work/tries/untrusted-repository"
|
||||
mkdir -p "$(dirname "$absent_config")"
|
||||
printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$absent_config"
|
||||
run_mise "$absent_home" trust "$absent_config" >/dev/null
|
||||
rm "$absent_config"
|
||||
rmdir "$absent_home/Work"
|
||||
run_migration "$absent_home" >/dev/null
|
||||
[[ ! -e $absent_home/Work ]] || fail "migration does not retain a temporary Work directory"
|
||||
mkdir -p "$absent_project/bin"
|
||||
printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$absent_config"
|
||||
if mise_path_active "$absent_home" "$absent_project"; then
|
||||
fail "deleted Work directory retains its stale trust grant"
|
||||
fi
|
||||
pass "migration leaves unrelated configs alone and revokes dangling Work trust"
|
||||
|
||||
paranoid_home="$test_dir/paranoid-home"
|
||||
paranoid_work="$paranoid_home/Work"
|
||||
paranoid_config="$paranoid_work/.mise.toml"
|
||||
paranoid_project="$paranoid_work/tries/untrusted-repository"
|
||||
mkdir -p "$paranoid_project/bin"
|
||||
printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$paranoid_config"
|
||||
OMARCHY_TEST_MISE_PARANOID=true run_mise "$paranoid_home" trust "$paranoid_config" >/dev/null
|
||||
OMARCHY_TEST_MISE_PARANOID=true mise_path_active "$paranoid_home" "$paranoid_project" || fail "paranoid legacy config prepends the repository bin directory"
|
||||
rm -r "$paranoid_work"
|
||||
|
||||
OMARCHY_TEST_MISE_PARANOID=true run_migration "$paranoid_home" >/dev/null
|
||||
[[ ! -e $paranoid_work ]] || fail "paranoid migration removes its temporary Work directory"
|
||||
mkdir -p "$paranoid_project/bin"
|
||||
printf '[env]\n_.path = "{{ cwd }}/bin"\n' >"$paranoid_config"
|
||||
if OMARCHY_TEST_MISE_PARANOID=true mise_path_active "$paranoid_home" "$paranoid_project"; then
|
||||
fail "paranoid migration retains content-bound trust for the deleted legacy config"
|
||||
fi
|
||||
pass "migration revokes stale content-bound trust in Mise paranoid mode"
|
||||
|
||||
ignored_home="$test_dir/ignored-home"
|
||||
ignored_config="$ignored_home/Work/.mise.toml"
|
||||
ignored_project="$ignored_home/Work/tries/untrusted-repository"
|
||||
mkdir -p "$ignored_project/bin"
|
||||
cat >"$ignored_config" <<'TOML'
|
||||
[env]
|
||||
_.path = "{{ cwd }}/bin"
|
||||
KEEP = "yes"
|
||||
TOML
|
||||
run_mise "$ignored_home" trust "$ignored_config" >/dev/null
|
||||
run_mise "$ignored_home" trust --ignore "$ignored_config" >/dev/null
|
||||
|
||||
ignored_output=$(run_migration "$ignored_home")
|
||||
grep -F '{{ cwd }}/bin' "$ignored_config" >/dev/null && fail "ignored config retains the unsafe path"
|
||||
grep -Fx 'KEEP = "yes"' "$ignored_config" >/dev/null || fail "ignored config keeps unrelated settings"
|
||||
ignored_entries=("$ignored_home/.local/state/mise/ignored-configs/"*)
|
||||
[[ -L ${ignored_entries[0]} ]] || fail "migration preserves the explicit Mise ignore marker"
|
||||
(( ${#ignored_entries[@]} == 1 )) || fail "migration preserves exactly one Mise ignore marker"
|
||||
ignored_target=$(readlink "${ignored_entries[0]}")
|
||||
[[ $ignored_target == $ignored_home/Work || $ignored_target == $ignored_config ]] || fail "preserved Mise ignore marker still targets the Work config"
|
||||
grep -F "remains ignored by Mise" <<<"$ignored_output" >/dev/null || fail "migration reports that the custom config remains ignored"
|
||||
if mise_path_active "$ignored_home" "$ignored_project"; then
|
||||
fail "ignored config becomes active after migration"
|
||||
fi
|
||||
pass "migration preserves an explicit decision to ignore the Work config"
|
||||
|
||||
symlink_home="$test_dir/symlink-home"
|
||||
symlink_config="$symlink_home/Work/.mise.toml"
|
||||
symlink_target="$test_dir/dotfiles-mise.toml"
|
||||
mkdir -p "$(dirname "$symlink_config")"
|
||||
cat >"$symlink_target" <<'TOML'
|
||||
[env]
|
||||
_.path = "{{ cwd }}/bin"
|
||||
KEEP = "yes"
|
||||
TOML
|
||||
ln -s "$symlink_target" "$symlink_config"
|
||||
|
||||
run_migration "$symlink_home" >/dev/null
|
||||
[[ -L $symlink_config ]] || fail "migration preserves a dotfile symlink"
|
||||
grep -F '{{ cwd }}/bin' "$symlink_target" >/dev/null && fail "symlink target retains the unsafe path"
|
||||
grep -Fx 'KEEP = "yes"' "$symlink_target" >/dev/null || fail "symlink target keeps unrelated settings"
|
||||
pass "custom dotfile symlinks survive the repair"
|
||||
@@ -7,6 +7,14 @@ source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
mkdir -p "$tmp/bin"
|
||||
export QR_ROUTE_EXIT=0
|
||||
|
||||
# Loopback is never wireless, so exercise the connected-Wi-Fi fallback.
|
||||
cat >"$tmp/bin/ip" <<'EOF'
|
||||
#!/bin/bash
|
||||
[[ $QR_ROUTE_EXIT == "0" ]] || exit "$QR_ROUTE_EXIT"
|
||||
printf '1.1.1.1 dev lo\n'
|
||||
EOF
|
||||
|
||||
cat >"$tmp/bin/nmcli" <<'EOF'
|
||||
#!/bin/bash
|
||||
@@ -28,13 +36,13 @@ payload=$(</dev/stdin)
|
||||
printf '%s' "$payload" >"$QR_PAYLOAD_FILE"
|
||||
printf '## \n ## \n ##\n'
|
||||
EOF
|
||||
chmod +x "$tmp/bin/nmcli" "$tmp/bin/qrencode"
|
||||
chmod +x "$tmp/bin/ip" "$tmp/bin/nmcli" "$tmp/bin/qrencode"
|
||||
|
||||
run_success_case() {
|
||||
local description=$1 fields=$2 expected_payload=$3
|
||||
shift 3
|
||||
local output meta matrix payload arg with_meta=false
|
||||
local expected_matrix expected_security expected_ssid expected_iface="*"
|
||||
local expected_matrix expected_security expected_ssid expected_iface="wlan0"
|
||||
|
||||
for arg in "$@"; do
|
||||
[[ $arg == "--meta" ]] && with_meta=true || expected_iface=$arg
|
||||
@@ -49,9 +57,8 @@ run_success_case() {
|
||||
meta=$(head -n1 <<<"$output")
|
||||
matrix=$(tail -n +2 <<<"$output")
|
||||
|
||||
# The meta line leads with the shared interface, security, and SSID. With
|
||||
# no interface argument the helper detects one from the live host, so that
|
||||
# field is only pinned when the case pinned it.
|
||||
# The metadata reports the requested interface, or the connected Wi-Fi
|
||||
# interface supplied by the nmcli stub when no interface was requested.
|
||||
expected_security=${expected_payload#WIFI:T:}
|
||||
expected_security=${expected_security%%;*}
|
||||
expected_ssid=$(head -n1 <<<"$fields")
|
||||
@@ -88,6 +95,12 @@ run_success_case \
|
||||
'WIFI:T:WPA;S:Cafe Detected;P:secret;;' \
|
||||
--meta
|
||||
|
||||
QR_ROUTE_EXIT=2 run_success_case \
|
||||
"network QR helper detects Wi-Fi when route lookup fails" \
|
||||
$'Cafe No Route\nwpa-psk\nsecret\nno\n' \
|
||||
'WIFI:T:WPA;S:Cafe No Route;P:secret;;' \
|
||||
--meta
|
||||
|
||||
run_success_case \
|
||||
"network QR helper supports open networks" \
|
||||
$'Cafe Open\nnone\n\nno\n' \
|
||||
|
||||
@@ -140,7 +140,6 @@ export OMARCHY_PTL_REBUILD_MARKER="$scratch/state/1789095456"
|
||||
export OMARCHY_MIGRATION_STATE="$scratch/user-markers"
|
||||
touch "$OMARCHY_PTL_REBUILD_MARKER" "$OMARCHY_MIGRATION_STATE/1789095456.sh"
|
||||
cp "$migration" "$scratch/omarchy/migrations/"
|
||||
[[ ! -e $ROOT/migrations/1789095456.sh ]] || fail "the superseded migration must not install the PTL variant first"
|
||||
pending=$(OMARCHY_PATH="$scratch/omarchy" "$ROOT/bin/omarchy-migrate" --pending)
|
||||
[[ $pending == "1789325478.sh" ]] || fail "the renamed migration is pending after completing the old migration"
|
||||
OMARCHY_PATH="$scratch/omarchy" "$ROOT/bin/omarchy-migrate" > "$scratch/output" 2>&1
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
packages="$ROOT/install/omarchy-base.packages"
|
||||
migration="$ROOT/migrations/1788129995.sh"
|
||||
|
||||
grep -qxF omasnap "$packages" || fail "fresh installs include Omasnap"
|
||||
! grep -qxF tensaku "$packages" || fail "fresh installs no longer include Tensaku"
|
||||
! grep -qxF satty "$packages" || fail "fresh installs no longer include Satty"
|
||||
pass "fresh installs use Omasnap as the screenshot tool"
|
||||
|
||||
grep -Fq 'namespace = "^omasnap$"' "$ROOT/default/hypr/apps/screenshot-selection.lua" ||
|
||||
fail "Omasnap has a layer rule"
|
||||
grep -Fq 'no_screen_share = true' "$ROOT/default/hypr/apps/screenshot-selection.lua" ||
|
||||
fail "the Omasnap overlay is excluded from screen sharing"
|
||||
! grep -Fq 'dev.tensaku.Tensaku' "$ROOT/default/hypr/apps/system.lua" ||
|
||||
fail "the removed Tensaku window rules are gone"
|
||||
pass "Hyprland applies Omasnap's overlay policy without stale Tensaku rules"
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
|
||||
stub_bin="$test_tmp/bin"
|
||||
mkdir -p "$stub_bin"
|
||||
|
||||
cat >"$stub_bin/omasnap" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s\t%s\n' "${OMASNAP_SCREENSHOT_DIR:-}" "$*" >>"$OMASNAP_TEST_LOG"
|
||||
SH
|
||||
chmod +x "$stub_bin/omasnap"
|
||||
|
||||
capture_log="$test_tmp/capture.log"
|
||||
OMASNAP_TEST_LOG="$capture_log" PATH="$stub_bin:$PATH" \
|
||||
"$ROOT/bin/omarchy-capture-screenshot"
|
||||
[[ $(<"$capture_log") == $'\t' ]] || fail "the default screenshot opens Omasnap without extra arguments"
|
||||
|
||||
: >"$capture_log"
|
||||
OMASNAP_TEST_LOG="$capture_log" OMARCHY_SCREENSHOT_DIR="$test_tmp/legacy-output" PATH="$stub_bin:$PATH" \
|
||||
"$ROOT/bin/omarchy-capture-screenshot" windows copy
|
||||
[[ $(<"$capture_log") == "$test_tmp/legacy-output"$'\twindows --copy' ]] ||
|
||||
fail "the screenshot command maps the legacy directory and copy argument to Omasnap"
|
||||
|
||||
: >"$capture_log"
|
||||
OMASNAP_TEST_LOG="$capture_log" OMARCHY_SCREENSHOT_DIR="$test_tmp/legacy-output" OMASNAP_SCREENSHOT_DIR="$test_tmp/native-output" PATH="$stub_bin:$PATH" \
|
||||
"$ROOT/bin/omarchy-capture-screenshot" fullscreen save
|
||||
[[ $(<"$capture_log") == "$test_tmp/native-output"$'\tfullscreen --save' ]] ||
|
||||
fail "the native Omasnap directory wins while legacy save syntax still works"
|
||||
|
||||
: >"$capture_log"
|
||||
OMASNAP_TEST_LOG="$capture_log" PATH="$stub_bin:$PATH" \
|
||||
"$ROOT/bin/omarchy-capture-screenshot" region slurp
|
||||
[[ $(<"$capture_log") == $'\tregion' ]] || fail "the former default slurp argument remains a harmless compatibility no-op"
|
||||
|
||||
: >"$capture_log"
|
||||
OMASNAP_TEST_LOG="$capture_log" PATH="$stub_bin:$PATH" \
|
||||
"$ROOT/bin/omarchy-capture-screenshot" scroll --save
|
||||
[[ $(<"$capture_log") == $'\tscroll --save' ]] || fail "native Omasnap modes and flags pass through unchanged"
|
||||
|
||||
pass "the Omarchy screenshot route delegates compatible arguments to Omasnap"
|
||||
|
||||
cat >"$stub_bin/omarchy-pkg-add" <<'SH'
|
||||
#!/bin/bash
|
||||
printf 'add\t%s\n' "$*" >>"$OMASNAP_MIGRATION_LOG"
|
||||
exit "${OMASNAP_PACKAGE_STATUS:-0}"
|
||||
SH
|
||||
cat >"$stub_bin/omarchy-pkg-drop" <<'SH'
|
||||
#!/bin/bash
|
||||
printf 'drop\t%s\n' "$*" >>"$OMASNAP_MIGRATION_LOG"
|
||||
SH
|
||||
chmod +x "$stub_bin/omarchy-pkg-add" "$stub_bin/omarchy-pkg-drop"
|
||||
|
||||
migration_home="$test_tmp/home"
|
||||
mkdir -p "$migration_home/.config/imv" "$migration_home/dotfiles"
|
||||
cat >"$migration_home/dotfiles/imv.config" <<'EOF'
|
||||
[binds]
|
||||
|
||||
# Edit the current image in Tensaku and quit the viewer
|
||||
<Ctrl+e> = exec tensaku-edit "$imv_current_file" & ; quit
|
||||
<Ctrl+x> = exec custom-editor "$imv_current_file" & ; quit
|
||||
EOF
|
||||
ln -s "$migration_home/dotfiles/imv.config" "$migration_home/.config/imv/config"
|
||||
|
||||
migration_log="$test_tmp/migration.log"
|
||||
run_migration() {
|
||||
: >"$migration_log"
|
||||
OMASNAP_MIGRATION_LOG="$migration_log" HOME="$migration_home" PATH="$stub_bin:$PATH" \
|
||||
bash -euo pipefail "$migration" >/dev/null
|
||||
}
|
||||
|
||||
run_migration
|
||||
|
||||
[[ $(sed -n '1p' "$migration_log") == $'add\tomasnap' ]] || fail "the migration installs Omasnap first"
|
||||
[[ $(sed -n '2p' "$migration_log") == $'drop\tsatty tensaku' ]] || fail "the migration removes Satty and Tensaku after Omasnap is ready"
|
||||
grep -Fq '# Edit the current image in Omasnap and quit the viewer' "$migration_home/.config/imv/config" ||
|
||||
fail "the migration updates the stock imv editor comment"
|
||||
grep -Fq '<Ctrl+e> = exec omasnap "$imv_current_file" & ; quit' "$migration_home/.config/imv/config" ||
|
||||
fail "the migration sends the stock imv edit binding to Omasnap"
|
||||
grep -Fq '<Ctrl+x> = exec custom-editor "$imv_current_file" & ; quit' "$migration_home/.config/imv/config" ||
|
||||
fail "the migration preserves custom imv bindings"
|
||||
[[ -L $migration_home/.config/imv/config ]] || fail "the migration preserves a dotfile-managed imv symlink"
|
||||
[[ $(stat -c '%a' "$migration") == 644 ]] || fail "the Omasnap migration has mode 0644"
|
||||
|
||||
pass "the migration swaps packages and safely updates only the stock imv binding"
|
||||
|
||||
cat >"$migration_home/dotfiles/imv.config" <<'EOF'
|
||||
[binds]
|
||||
|
||||
# Edit the current image in Satty and quit the viewer
|
||||
<Ctrl+e> = exec satty --filename "$imv_current_file" & ; quit
|
||||
<Ctrl+x> = exec custom-editor "$imv_current_file" & ; quit
|
||||
EOF
|
||||
cp "$migration_home/dotfiles/imv.config" "$test_tmp/imv-before"
|
||||
legacy_desktop="$migration_home/.local/share/applications/omasnap.desktop"
|
||||
mkdir -p "$(dirname "$legacy_desktop")"
|
||||
cat >"$legacy_desktop" <<'EOF'
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Omasnap
|
||||
Exec=omasnap
|
||||
NoDisplay=true
|
||||
EOF
|
||||
if OMASNAP_PACKAGE_STATUS=1 run_migration; then
|
||||
fail "Omasnap install failure stops the migration"
|
||||
fi
|
||||
[[ $(<"$migration_log") == $'add\tomasnap' ]] || fail "install failure leaves old screenshot packages installed"
|
||||
cmp -s "$test_tmp/imv-before" "$migration_home/dotfiles/imv.config" || fail "install failure leaves the imv binding untouched"
|
||||
[[ -f $legacy_desktop ]] || fail "install failure preserves the existing launcher entry"
|
||||
pass "Omasnap install failure preserves the previous screenshot setup"
|
||||
|
||||
run_migration
|
||||
[[ ! -e $legacy_desktop && ! -L $legacy_desktop ]] || fail "the migration removes the old user-local Omasnap desktop entry"
|
||||
pass "the migration removes the desktop entry that hides the packaged launcher"
|
||||
grep -Fq '# Edit the current image in Omasnap and quit the viewer' "$migration_home/.config/imv/config" ||
|
||||
fail "the migration updates the stock Satty comment"
|
||||
grep -Fq '<Ctrl+e> = exec omasnap "$imv_current_file" & ; quit' "$migration_home/.config/imv/config" ||
|
||||
fail "the migration replaces the stock Satty binding before removing Satty"
|
||||
grep -Fq '<Ctrl+x> = exec custom-editor "$imv_current_file" & ; quit' "$migration_home/.config/imv/config" ||
|
||||
fail "the Satty migration preserves custom bindings"
|
||||
[[ -L $migration_home/.config/imv/config ]] || fail "the Satty migration preserves the imv symlink"
|
||||
pass "the migration upgrades the stock Satty binding to Omasnap"
|
||||
|
||||
cp "$migration_home/dotfiles/imv.config" "$test_tmp/imv-migrated"
|
||||
run_migration
|
||||
cmp -s "$test_tmp/imv-migrated" "$migration_home/dotfiles/imv.config" || fail "rerunning the migration preserves the updated imv config"
|
||||
pass "the migration can be rerun"
|
||||
|
||||
printf '<Ctrl+e> = exec custom-editor "$imv_current_file" & ; quit\n' >"$migration_home/dotfiles/imv.config"
|
||||
cp "$migration_home/dotfiles/imv.config" "$test_tmp/imv-custom"
|
||||
run_migration
|
||||
cmp -s "$test_tmp/imv-custom" "$migration_home/dotfiles/imv.config" || fail "the migration preserves a custom edit shortcut"
|
||||
pass "custom imv edit shortcuts remain unchanged"
|
||||
@@ -0,0 +1,90 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
work=$(mktemp -d)
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
mkdir -p "$work/bin" "$work/home"
|
||||
export TEST_CALLS="$work/calls"
|
||||
for command in omarchy-pkg-add omarchy-hook-install systemctl; do
|
||||
cat >"$work/bin/$command" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s %s\n' "${0##*/}" "$*" >>"$TEST_CALLS"
|
||||
case "$*" in
|
||||
'--user enable owed.service') [[ ${TEST_TTY:-0} == 0 ]] ;;
|
||||
'--user is-active --quiet graphical-session.target') [[ ${TEST_TTY:-0} == 0 ]] ;;
|
||||
'--user start owed.service') [[ ${TEST_START_FAIL:-0} == 0 ]] ;;
|
||||
*) exit 0 ;;
|
||||
esac
|
||||
SH
|
||||
chmod +x "$work/bin/$command"
|
||||
done
|
||||
|
||||
migration="$ROOT/migrations/1789764927.sh"
|
||||
run_migration() {
|
||||
HOME="$work/home" PATH="$work/bin:$PATH" bash -euo pipefail "$migration"
|
||||
}
|
||||
run_migration
|
||||
run_migration
|
||||
grep -Fx 'omarchy-pkg-add owe owe-lockfeed' "$TEST_CALLS" >/dev/null
|
||||
grep -Fx 'omarchy-hook-install theme-set /usr/share/owe/10-owe-sync' "$TEST_CALLS" >/dev/null
|
||||
grep -Fx 'systemctl --user start owed.service' "$TEST_CALLS" >/dev/null
|
||||
pass "migration installs OWE, its theme refresh hook, and starts the graphical session service"
|
||||
|
||||
: >"$TEST_CALLS"
|
||||
TEST_TTY=1 run_migration
|
||||
TEST_TTY=1 run_migration
|
||||
[[ $(readlink "$work/home/.config/systemd/user/graphical-session.target.wants/owed.service") == /usr/lib/systemd/user/owed.service ]]
|
||||
if grep -F 'systemctl --user start' "$TEST_CALLS" >/dev/null; then
|
||||
fail "TTY migration must not start the renderer"
|
||||
fi
|
||||
pass "TTY migrations enable the next login without starting a renderer"
|
||||
|
||||
: >"$TEST_CALLS"
|
||||
OMARCHY_UPGRADE_TO_QUATTRO_LIVE=1 run_migration
|
||||
if grep -F 'systemctl --user start' "$TEST_CALLS" >/dev/null; then
|
||||
fail "Quattro upgrade must defer OWE until the new graphical session"
|
||||
fi
|
||||
pass "Quattro upgrade enables OWE without starting it in the old shell"
|
||||
|
||||
if TEST_START_FAIL=1 run_migration; then
|
||||
fail "a failed live service start leaves the migration pending"
|
||||
fi
|
||||
pass "a failed live service start fails the migration"
|
||||
|
||||
: >"$TEST_CALLS"
|
||||
HOME="$work/home" PATH="$work/bin:$PATH" bash "$ROOT/install/user/first-run/enable-user-units.sh"
|
||||
grep -E '^systemctl --user enable --now .*owed.service' "$TEST_CALLS" >/dev/null
|
||||
grep -Fx 'omarchy-hook-install theme-set /usr/share/owe/10-owe-sync' "$TEST_CALLS" >/dev/null
|
||||
pass "fresh installs enable OWE and install the theme refresh hook"
|
||||
|
||||
cat >"$work/bin/ffmpegthumbnailer" <<'SH'
|
||||
#!/bin/bash
|
||||
printf 'thumbnail\n' >>"$TEST_CALLS"
|
||||
while (( $# )); do
|
||||
case "$1" in
|
||||
-o) output=$2; shift 2 ;;
|
||||
*) shift ;;
|
||||
esac
|
||||
done
|
||||
[[ ${TEST_POSTER_FAIL:-0} == 0 ]] || exit 1
|
||||
printf 'poster\n' >"$output"
|
||||
SH
|
||||
chmod +x "$work/bin/ffmpegthumbnailer"
|
||||
source_path="$work/video with 'quotes'.mp4"
|
||||
printf 'source\n' >"$source_path"
|
||||
poster() {
|
||||
PATH="$work/bin:$PATH" XDG_CACHE_HOME="$work/cache" bash "$ROOT/shell/plugins/lock/poster.sh" "$source_path"
|
||||
}
|
||||
: >"$TEST_CALLS"
|
||||
first=$(poster)
|
||||
[[ -s $first && $(poster) == "$first" ]]
|
||||
[[ $(wc -l <"$TEST_CALLS") == 1 ]]
|
||||
printf 'changed source\n' >>"$source_path"
|
||||
second=$(poster)
|
||||
[[ -s $second && $second != "$first" && ! -e $first ]]
|
||||
pass "lock posters are cached, refreshed after source changes, and old frames are evicted"
|
||||
printf 'broken source\n' >>"$source_path"
|
||||
if TEST_POSTER_FAIL=1 poster; then fail "failed poster generation must fail"; fi
|
||||
[[ -z $(find "$work/cache" -name '.poster-*.jpg' -print -quit) ]]
|
||||
pass "failed poster generation never publishes a partial image"
|
||||
@@ -0,0 +1,30 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
skip "quickshell unavailable; skipping lock feed QML lifecycle"
|
||||
exit 0
|
||||
fi
|
||||
work=$(mktemp -d)
|
||||
trap 'rm -rf "$work"' EXIT
|
||||
mkdir -p "$work/config" "$work/runtime" "$work/home"
|
||||
chmod 700 "$work/runtime"
|
||||
cp "$SHELL_TEST_DIR/fixtures/owe-lock/shell.qml" "$work/config/shell.qml"
|
||||
ln -s "$ROOT/shell/Ui" "$work/config/Ui"
|
||||
ln -s "$ROOT/shell/Commons" "$work/config/Commons"
|
||||
|
||||
# Offscreen rendering exercises the real QML bindings without taking a lock
|
||||
# or connecting to the user's compositor.
|
||||
HOME="$work/home" XDG_RUNTIME_DIR="$work/runtime" \
|
||||
OMARCHY_PATH="$ROOT" QT_QPA_PLATFORM=offscreen QT_QPA_PLATFORMTHEME= \
|
||||
QT_STYLE_OVERRIDE= QT_QUICK_BACKEND=software \
|
||||
timeout 15 quickshell -p "$work/config" --no-color >"$work/log" 2>&1 || {
|
||||
cat "$work/log" >&2
|
||||
fail "lock feed QML lifecycle runs"
|
||||
}
|
||||
if ! grep -q 'OWE_LOCK_TEST_PASS' "$work/log" || grep -q 'OWE_LOCK_TEST_FAIL' "$work/log"; then
|
||||
cat "$work/log" >&2
|
||||
fail "lock feed respects visibility and keeps its poster fallback"
|
||||
fi
|
||||
pass "lock feed respects visibility, power saver and blanking; hidden views release images"
|
||||
@@ -158,7 +158,7 @@ if script -qec true /dev/null >/dev/null 2>&1; then
|
||||
fail "plugin add reached git clone for an option-shaped gum-prompt URL"
|
||||
pass "plugin add guard rejects an option-shaped URL from the interactive prompt"
|
||||
else
|
||||
pass "script -qec unavailable; skipping the interactive gum-prompt guard case"
|
||||
skip "script -qec unavailable; skipping the interactive gum-prompt guard case"
|
||||
fi
|
||||
|
||||
# Legitimate URL forms pass the guard and reach git clone (stubbed, no network).
|
||||
|
||||
@@ -156,7 +156,7 @@ pass "service facades resolve enabled clones without widening replacement-bar ac
|
||||
require_compositor "plugin authentication boundary runtime test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping plugin authentication boundary runtime test"
|
||||
skip "quickshell not installed; skipping plugin authentication boundary runtime test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ trap cleanup EXIT
|
||||
require_compositor "plugin registry contract test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping plugin registry contract test"
|
||||
skip "quickshell not installed; skipping plugin registry contract test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ JS
|
||||
require_compositor "pointer movement gate runtime test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping pointer movement gate runtime test"
|
||||
skip "quickshell not installed; skipping pointer movement gate runtime test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ trap cleanup EXIT
|
||||
require_compositor "shell runtime smoke test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping shell runtime smoke test"
|
||||
skip "quickshell not installed; skipping shell runtime smoke test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
@@ -299,3 +299,114 @@ grep -F 'move = { "(monitor_w-monitor_h*2/9-40)", "(monitor_h-monitor_h/4-40)" }
|
||||
grep -F 'move = { "(monitor_w-monitor_h*3/10-40)", "(monitor_h-monitor_h*27/80-40)" }' "$webcam_rules" >/dev/null || \
|
||||
fail "large webcam starts at its final corner position"
|
||||
pass "webcam size rules place the initial window in its final corner"
|
||||
|
||||
# The stop path reads the recording state file back and uses its contents as a
|
||||
# path -- ffmpeg writes beside it, `mv` replaces it, `rm -f` deletes its
|
||||
# preview -- so it has to live in the per-user runtime directory rather than
|
||||
# under a name in world-writable /tmp that another account can create first.
|
||||
recording_dir="$tmp_dir/recordings"
|
||||
mkdir -p "$recording_dir"
|
||||
|
||||
cat >"$stub_bin/pgrep" <<'SH'
|
||||
#!/bin/bash
|
||||
exit 1
|
||||
SH
|
||||
|
||||
cat >"$stub_bin/omarchy-hyprland-monitor-focused" <<'SH'
|
||||
#!/bin/bash
|
||||
printf 'DP-1\n'
|
||||
SH
|
||||
|
||||
cat >"$stub_bin/gpu-screen-recorder" <<'SH'
|
||||
#!/bin/bash
|
||||
for i in "$@"; do
|
||||
[[ -n ${take_next:-} ]] && { : >"$i"; break; }
|
||||
[[ $i == "-o" ]] && take_next=1
|
||||
done
|
||||
sleep 5
|
||||
SH
|
||||
|
||||
cat >"$stub_bin/omarchy-shell" <<'SH'
|
||||
#!/bin/bash
|
||||
exit 0
|
||||
SH
|
||||
|
||||
chmod +x "$stub_bin"/pgrep "$stub_bin"/omarchy-hyprland-monitor-focused \
|
||||
"$stub_bin"/gpu-screen-recorder "$stub_bin"/omarchy-shell
|
||||
|
||||
# Compare that name across the run rather than demanding it be absent: the
|
||||
# whole point of the finding is that anyone can own it already, and a leftover
|
||||
# from a pre-fix recording would red-light the fixed script.
|
||||
tmp_state="/tmp/omarchy-screenrecord-filename"
|
||||
tmp_state_before=$(stat -c '%y %s' "$tmp_state" 2>/dev/null || true)
|
||||
|
||||
OMARCHY_SCREENRECORD_DIR="$recording_dir" \
|
||||
"$ROOT/bin/omarchy-capture-screenrecording" --fullscreen >/dev/null 2>&1
|
||||
|
||||
pkill -f "$stub_bin/gpu-screen-recorder" 2>/dev/null || true
|
||||
|
||||
[[ $(stat -c '%y %s' "$tmp_state" 2>/dev/null) == "$tmp_state_before" ]] ||
|
||||
fail "screen recording keeps no state under a fixed /tmp name"
|
||||
pass "screen recording keeps no state under a fixed /tmp name"
|
||||
|
||||
[[ -s $XDG_RUNTIME_DIR/omarchy-screenrecord-filename ]] ||
|
||||
fail "the recording state file lives in the per-user runtime directory" \
|
||||
"$(ls -a "$XDG_RUNTIME_DIR")"
|
||||
pass "the recording state file lives in the per-user runtime directory"
|
||||
|
||||
[[ $(<"$XDG_RUNTIME_DIR/omarchy-screenrecord-filename") == "$recording_dir"/* ]] ||
|
||||
fail "the recording state file names the recording that was started" \
|
||||
"$(<"$XDG_RUNTIME_DIR/omarchy-screenrecord-filename")"
|
||||
pass "the recording state file names the recording that was started"
|
||||
|
||||
# The :-/tmp fallback would reopen the hole this PR closes. A recording
|
||||
# started without a session runtime dir has to land under the state directory.
|
||||
state_home="$tmp_dir/state-home"
|
||||
mkdir -p "$state_home/omarchy" "$tmp_dir/home-fallback"
|
||||
chmod 755 "$state_home/omarchy"
|
||||
tmp_state_before=$(stat -c '%y %s' "$tmp_state" 2>/dev/null || true)
|
||||
|
||||
env -u XDG_RUNTIME_DIR \
|
||||
HOME="$tmp_dir/home-fallback" \
|
||||
XDG_STATE_HOME="$state_home" \
|
||||
OMARCHY_SCREENRECORD_DIR="$recording_dir" \
|
||||
"$ROOT/bin/omarchy-capture-screenrecording" --fullscreen >/dev/null 2>&1
|
||||
|
||||
pkill -f "$stub_bin/gpu-screen-recorder" 2>/dev/null || true
|
||||
|
||||
[[ $(stat -c '%y %s' "$tmp_state" 2>/dev/null) == "$tmp_state_before" ]] ||
|
||||
fail "without a runtime dir, screen recording still keeps no state under a fixed /tmp name"
|
||||
pass "without a runtime dir, screen recording still keeps no state under a fixed /tmp name"
|
||||
|
||||
fallback_file="$state_home/omarchy/omarchy-screenrecord-filename"
|
||||
[[ -s $fallback_file ]] ||
|
||||
fail "without a runtime dir the recording state file lives in the state directory" \
|
||||
"$(ls -la "$state_home/omarchy" 2>/dev/null || true)"
|
||||
pass "without a runtime dir the recording state file lives in the state directory"
|
||||
|
||||
[[ $(<"$fallback_file") == "$recording_dir"/* ]] ||
|
||||
fail "the fallback state file names the recording that was started" \
|
||||
"$(<"$fallback_file")"
|
||||
pass "the fallback state file names the recording that was started"
|
||||
|
||||
# The overlay resizer reads the region file the recorder writes, so the two
|
||||
# have to resolve the same fallback as well as the same runtime dir.
|
||||
: >"$OMARCHY_TEST_HYPRCTL_ARGS"
|
||||
echo "800x600+100+100" >"$state_home/omarchy/omarchy-screenrecord-region"
|
||||
env -u XDG_RUNTIME_DIR \
|
||||
HOME="$tmp_dir/home-fallback" \
|
||||
XDG_STATE_HOME="$state_home" \
|
||||
"$ROOT/bin/omarchy-capture-webcam-resize" reset
|
||||
|
||||
printf '%s\n' \
|
||||
'dispatch hl.dsp.window.resize({ window = "address:0xabc", x = 133, y = 150 })' \
|
||||
'dispatch hl.dsp.window.move({ window = "address:0xabc", x = 727, y = 510 })' >"$expected_hyprctl_args"
|
||||
|
||||
if ! cmp -s "$OMARCHY_TEST_HYPRCTL_ARGS" "$expected_hyprctl_args"; then
|
||||
fail "without a runtime dir the webcam anchors to the recorded region" "$(diff -u "$expected_hyprctl_args" "$OMARCHY_TEST_HYPRCTL_ARGS")"
|
||||
fi
|
||||
pass "without a runtime dir the webcam anchors to the recorded region"
|
||||
|
||||
mode=$(stat -c '%a' "$state_home/omarchy" 2>/dev/null || stat -f '%Lp' "$state_home/omarchy")
|
||||
[[ $mode == "700" ]] || fail "fallback directory is private even when it already existed" "mode: $mode"
|
||||
pass "fallback directory is private even when it already existed"
|
||||
@@ -21,17 +21,17 @@ trap cleanup EXIT
|
||||
require_compositor "screenshot sanity test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping screenshot sanity test"
|
||||
skip "quickshell not installed; skipping screenshot sanity test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if pgrep -x slurp >/dev/null 2>&1; then
|
||||
pass "slurp is already running; skipping screenshot sanity test"
|
||||
if pgrep -x omasnap >/dev/null 2>&1; then
|
||||
skip "omasnap is already running; skipping screenshot sanity test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
require_command hyprctl
|
||||
require_command grim
|
||||
require_command omasnap
|
||||
require_command jq
|
||||
require_command python3
|
||||
|
||||
@@ -118,13 +118,13 @@ jq -e '
|
||||
fail_with_log "screenshot test shell rendered visible bar widgets"
|
||||
}
|
||||
|
||||
screenshot=$(
|
||||
OMARCHY_PATH="$test_root" \
|
||||
OMARCHY_SCREENSHOT_DIR="$screenshot_dir" \
|
||||
HOME="$test_home" \
|
||||
PATH="$stub_bin:$ROOT/bin:$PATH" \
|
||||
"$ROOT/bin/omarchy" capture screenshot fullscreen save 2>"$screenshot_err" | tail -n 1
|
||||
)
|
||||
OMARCHY_PATH="$test_root" \
|
||||
OMASNAP_SCREENSHOT_DIR="$screenshot_dir" \
|
||||
HOME="$test_home" \
|
||||
PATH="$stub_bin:$ROOT/bin:$PATH" \
|
||||
"$ROOT/bin/omarchy" capture screenshot fullscreen save >/dev/null 2>"$screenshot_err"
|
||||
|
||||
screenshot=$(find "$screenshot_dir" -maxdepth 1 -type f -name '*.png' -print -quit)
|
||||
|
||||
[[ -n $screenshot && -f $screenshot ]] || fail_with_log "fullscreen screenshot was captured"
|
||||
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
test_dir=$(mktemp -d)
|
||||
trap 'rm -rf "$test_dir"' EXIT
|
||||
mkdir -p "$test_dir/test/shell.d"
|
||||
cp "$ROOT/test/"{shell,all} "$test_dir/test/"
|
||||
cp "$SHELL_TEST_DIR/base-test.sh" "$test_dir/test/shell.d/"
|
||||
|
||||
# Run the real runners in a tiny fixture tree, without recursing into this test.
|
||||
run_suite() {
|
||||
status=0
|
||||
output=$(bash "$test_dir/test/${1:-shell}" 2>&1) || status=$?
|
||||
}
|
||||
|
||||
run_suite
|
||||
(( status == 1 )) && [[ $output == *"No shell tests found"* ]] ||
|
||||
fail "an empty suite fails instead of counting the helper as a test" "$output"
|
||||
pass "an empty suite fails instead of counting the helper as a test"
|
||||
|
||||
cat >"$test_dir/test/shell.d/a-pass-test.sh" <<'SH'
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/base-test.sh"
|
||||
captured=$(skip "captured fixture output is not a skipped check")
|
||||
pass "the command skips an optional action"
|
||||
SH
|
||||
run_suite
|
||||
(( status == 0 )) && [[ $output == *"All 1 test files passed."* ]] ||
|
||||
fail "passing assertions and captured skip output do not mark a file skipped" "$output"
|
||||
pass "passing assertions and captured skip output do not mark a file skipped"
|
||||
|
||||
cat >"$test_dir/test/shell.d/b-partial-test.sh" <<'SH'
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/base-test.sh"
|
||||
pass "static check"
|
||||
skip "first unavailable runtime check"
|
||||
skip "second unavailable runtime check"
|
||||
pass "check after skips"
|
||||
SH
|
||||
cat >"$test_dir/test/shell.d/c-skipped-test.sh" <<'SH'
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/base-test.sh"
|
||||
unset WAYLAND_DISPLAY
|
||||
require_compositor "runtime fixture"
|
||||
fail "unreachable after compositor skip"
|
||||
SH
|
||||
cat >"$test_dir/test/shell.d/z-last-test.sh" <<'SH'
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/base-test.sh"
|
||||
pass "last file ran"
|
||||
SH
|
||||
run_suite
|
||||
(( status == 0 )) && [[ $output == *"4 test files completed without failures; 2 had skipped checks."* ]] ||
|
||||
fail "whole and partial skips are counted once per file and remain successful" "$output"
|
||||
[[ $output == *"ok - check after skips"* && $output == *"ok - last file ran"* ]] ||
|
||||
fail "skip returns normally and later files still run" "$output"
|
||||
[[ $output == *$'Skipped checks in 2 of 4 test files:\n test/shell.d/b-partial-test.sh\n test/shell.d/c-skipped-test.sh\n'* ]] ||
|
||||
fail "the summary identifies only files with skipped checks" "$output"
|
||||
pass "whole and partial skips are visible without failing or stopping the suite"
|
||||
|
||||
cat >"$test_dir/test/shell.d/d-failed-test.sh" <<'SH'
|
||||
set -euo pipefail
|
||||
source "$(dirname "$0")/base-test.sh"
|
||||
skip "unavailable check before failure"
|
||||
echo "failure detail" >&2
|
||||
false
|
||||
pass "unreachable after failure"
|
||||
SH
|
||||
run_suite
|
||||
(( status == 1 )) && [[ $output == *$'1 of 5 test files failed:\n test/shell.d/d-failed-test.sh'* ]] ||
|
||||
fail "a failure after a skip still fails the suite through the output pipe" "$output"
|
||||
[[ $output == *"Skipped checks in 3 of 5 test files:"* && $output == *"failure detail"* && $output == *"ok - last file ran"* && $output != *"ok - unreachable after failure"* ]] ||
|
||||
fail "failures preserve skip reporting, diagnostics, errexit, and later tests" "$output"
|
||||
pass "failures remain fatal while skips and later test results stay visible"
|
||||
|
||||
printf '#!/bin/bash\necho "CLI fixture passed"\n' >"$test_dir/test/cli"
|
||||
chmod +x "$test_dir/test/cli"
|
||||
run_suite all
|
||||
(( status == 1 )) && [[ $output == *"CLI fixture passed"* && $output == *"Skipped checks in 3 of 5 test files:"* && $output == *$'1 of 2 suites failed:\n test/shell'* ]] ||
|
||||
fail "the aggregate runner preserves shell failures and skip reporting" "$output"
|
||||
pass "the aggregate runner preserves shell failures and skip reporting"
|
||||
@@ -173,7 +173,7 @@ if locale -a 2>/dev/null | grep -qix 'en_US.utf-\?8'; then
|
||||
|
||||
pass "the accepted set does not move with the desktop's locale"
|
||||
else
|
||||
pass "no en_US.UTF-8 locale; skipping the locale-pinning check"
|
||||
skip "no en_US.UTF-8 locale; skipping the locale-pinning check"
|
||||
fi
|
||||
|
||||
# basename reads a leading dash as an option once the scp-style prefix is gone.
|
||||
|
||||
@@ -25,7 +25,7 @@ trap cleanup EXIT
|
||||
require_compositor "tray menu activation test"
|
||||
|
||||
if ! command -v quickshell >/dev/null 2>&1; then
|
||||
pass "quickshell not installed; skipping tray menu activation test"
|
||||
skip "quickshell not installed; skipping tray menu activation test"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -36,7 +36,7 @@ python - <<'PY' || {
|
||||
import dbus
|
||||
import gi
|
||||
PY
|
||||
pass "python DBus bindings unavailable; skipping tray menu activation test"
|
||||
skip "python DBus bindings unavailable; skipping tray menu activation test"
|
||||
exit 0
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,13 @@ allowed = {
|
||||
# them so the hook does not exist where it does not apply.
|
||||
"/usr/lib/systemd/system-sleep",
|
||||
# Written through a variable, so the scan below cannot see them at the point
|
||||
# they are written. Both drop configuration into another project's tree rather
|
||||
# than Omarchy's, which is why neither is a candidate for omarchy-settings.
|
||||
# they are written. These drop configuration into another project's tree
|
||||
# rather than Omarchy's and are not candidates for omarchy-settings.
|
||||
"/usr/share/chromium/extensions",
|
||||
"/usr/lib/firefox/distribution",
|
||||
# Claude's extension is registered only when the user selects Claude.
|
||||
"/usr/share/google-chrome/extensions",
|
||||
"/usr/share/microsoft-edge/extensions",
|
||||
# Static content that belongs in omarchy-settings. It cannot move there in the
|
||||
# same release that first ships omarchy-update-system-pkgs-when-conflicted: the
|
||||
# upgrade carrying the handler is the one that would hit the conflict, and the
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
|
||||
stub_bin="$test_tmp/bin"
|
||||
log_file="$test_tmp/keyring.log"
|
||||
mkdir -p "$stub_bin"
|
||||
|
||||
# Behavior is driven by env vars so each case can pick its failure point:
|
||||
# KEYRING_TEST_PKG_MISSING exit status of omarchy-pkg-missing (default 1: installed)
|
||||
# KEYRING_TEST_LIST_FAIL_ON which --list-keys call fails, counted per run (default: none)
|
||||
# KEYRING_TEST_RECV_STATUS exit status of --recv-keys (default 0)
|
||||
# KEYRING_TEST_REINSTALL_STATUS exit status of the archlinux-keyring reinstall (default 0)
|
||||
cat >"$stub_bin/sudo" <<'SH'
|
||||
#!/bin/bash
|
||||
|
||||
printf 'sudo' >>"$KEYRING_TEST_LOG"
|
||||
for arg in "$@"; do
|
||||
printf '\t%s' "$arg" >>"$KEYRING_TEST_LOG"
|
||||
done
|
||||
printf '\n' >>"$KEYRING_TEST_LOG"
|
||||
|
||||
if [[ $1 == "pacman-key" && $2 == "--list-keys" ]]; then
|
||||
calls_file="$KEYRING_TEST_DIR/list-calls"
|
||||
calls=$(( $(cat "$calls_file" 2>/dev/null || echo 0) + 1 ))
|
||||
echo "$calls" >"$calls_file"
|
||||
if [[ ${KEYRING_TEST_LIST_FAIL_ON:-} == "$calls" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ $1 == "pacman-key" && $2 == "--recv-keys" ]]; then
|
||||
exit "${KEYRING_TEST_RECV_STATUS:-0}"
|
||||
fi
|
||||
|
||||
if [[ $1 == "pacman-key" && $2 == "--lsign-key" ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ $1 == "pacman" && $* == *archlinux-keyring* ]]; then
|
||||
exit "${KEYRING_TEST_REINSTALL_STATUS:-0}"
|
||||
fi
|
||||
|
||||
exit 0
|
||||
SH
|
||||
chmod +x "$stub_bin/sudo"
|
||||
|
||||
cat >"$stub_bin/omarchy-pkg-missing" <<'SH'
|
||||
#!/bin/bash
|
||||
|
||||
exit "${KEYRING_TEST_PKG_MISSING:-1}"
|
||||
SH
|
||||
chmod +x "$stub_bin/omarchy-pkg-missing"
|
||||
|
||||
cat >"$stub_bin/omarchy-pkg-add" <<'SH'
|
||||
#!/bin/bash
|
||||
|
||||
printf 'pkg-add\t%s\n' "$1" >>"$KEYRING_TEST_LOG"
|
||||
exit 0
|
||||
SH
|
||||
chmod +x "$stub_bin/omarchy-pkg-add"
|
||||
|
||||
run_keyring() {
|
||||
KEYRING_TEST_LOG="$log_file" \
|
||||
KEYRING_TEST_DIR="$test_tmp" \
|
||||
PATH="$stub_bin:$PATH" \
|
||||
"$ROOT/bin/omarchy-update-keyring" "$@"
|
||||
}
|
||||
|
||||
# Everything healthy: the key and package are present, the reinstall works.
|
||||
: >"$log_file"
|
||||
rm -f "$test_tmp/list-calls"
|
||||
run_keyring >"$test_tmp/ok.out"
|
||||
|
||||
grep -F "Keys are correct" "$test_tmp/ok.out" >/dev/null ||
|
||||
fail "update-keyring reports success when the keyring is healthy" "$(cat "$test_tmp/ok.out")"
|
||||
pass "update-keyring reports success when the keyring is healthy"
|
||||
|
||||
grep -Eq $'^sudo\tpacman\t-Sy\t--noconfirm\tarchlinux-keyring$' "$log_file" ||
|
||||
fail "update-keyring still reinstalls archlinux-keyring" "$(cat "$log_file")"
|
||||
pass "update-keyring still reinstalls archlinux-keyring"
|
||||
|
||||
# Key and package missing: the full populate path runs and verifies at the end.
|
||||
: >"$log_file"
|
||||
rm -f "$test_tmp/list-calls"
|
||||
KEYRING_TEST_PKG_MISSING=0 run_keyring >"$test_tmp/populate.out"
|
||||
|
||||
grep -F "Keys are correct" "$test_tmp/populate.out" >/dev/null ||
|
||||
fail "update-keyring populates a missing keyring and reports success" "$(cat "$test_tmp/populate.out")"
|
||||
for expected in 'recv-keys' 'lsign-key' $'pkg-add\tomarchy-keyring'; do
|
||||
grep -Eq "$expected" "$log_file" ||
|
||||
fail "update-keyring populates a missing keyring and reports success" "$(cat "$log_file")"
|
||||
done
|
||||
pass "update-keyring populates a missing keyring and reports success"
|
||||
|
||||
# recv-keys failing must stop the script, not end in "Keys are correct".
|
||||
: >"$log_file"
|
||||
rm -f "$test_tmp/list-calls"
|
||||
if KEYRING_TEST_PKG_MISSING=0 KEYRING_TEST_RECV_STATUS=1 run_keyring >"$test_tmp/recv.out" 2>&1; then
|
||||
fail "update-keyring fails when recv-keys fails"
|
||||
fi
|
||||
if grep -F "Keys are correct" "$test_tmp/recv.out" >/dev/null; then
|
||||
fail "update-keyring fails when recv-keys fails" "$(cat "$test_tmp/recv.out")"
|
||||
fi
|
||||
if grep -q 'lsign-key' "$log_file"; then
|
||||
fail "update-keyring stops at the failed recv instead of signing anyway" "$(cat "$log_file")"
|
||||
fi
|
||||
pass "update-keyring fails when recv-keys fails"
|
||||
|
||||
# A failed archlinux-keyring reinstall must not end in success either.
|
||||
: >"$log_file"
|
||||
rm -f "$test_tmp/list-calls"
|
||||
if KEYRING_TEST_REINSTALL_STATUS=1 run_keyring >"$test_tmp/reinstall.out" 2>&1; then
|
||||
fail "update-keyring fails when the archlinux-keyring reinstall fails"
|
||||
fi
|
||||
if grep -F "Keys are correct" "$test_tmp/reinstall.out" >/dev/null; then
|
||||
fail "update-keyring fails when the archlinux-keyring reinstall fails" "$(cat "$test_tmp/reinstall.out")"
|
||||
fi
|
||||
pass "update-keyring fails when the archlinux-keyring reinstall fails"
|
||||
|
||||
# The closing check is what backs the success line: the first --list-keys
|
||||
# passes (key present, populate skipped), the verifying one fails.
|
||||
: >"$log_file"
|
||||
rm -f "$test_tmp/list-calls"
|
||||
if KEYRING_TEST_LIST_FAIL_ON=2 run_keyring >"$test_tmp/verify.out" 2>&1; then
|
||||
fail "update-keyring fails when the final key check fails"
|
||||
fi
|
||||
if grep -F "Keys are correct" "$test_tmp/verify.out" >/dev/null; then
|
||||
fail "update-keyring fails when the final key check fails" "$(cat "$test_tmp/verify.out")"
|
||||
fi
|
||||
pass "update-keyring fails when the final key check fails"
|
||||
@@ -200,7 +200,7 @@ run_with_lock_env "$ROOT/bin/omarchy-update-stay-awake" stop
|
||||
pass "stale update ownership preserves a newer Stay Awake choice"
|
||||
|
||||
# A stale PID is safe even if it has been reused by another process.
|
||||
sleep 30 &
|
||||
sleep 30 >/dev/null &
|
||||
unrelated_pid=$!
|
||||
unrelated_start_time=$(awk '{ print $22 }' "/proc/$unrelated_pid/stat")
|
||||
mkdir -p "$stay_awake_helper_state"
|
||||
|
||||
@@ -28,6 +28,8 @@ else
|
||||
expected_scope=""
|
||||
fi
|
||||
|
||||
# The baseline has no locale override; forwarding is tested below.
|
||||
unset LC_ALL
|
||||
run_helper -Syu --noconfirm
|
||||
[[ $(cat "$test_tmp/call") == "env OMARCHY_UPDATE_PACMAN=1 ${expected_scope}pacman -Syu --noconfirm" ]] ||
|
||||
fail "helper composes the guarded pacman invocation" "$(cat "$test_tmp/call")"
|
||||
|
||||
@@ -17,7 +17,7 @@ import tempfile
|
||||
repo = Path(sys.argv[1])
|
||||
source = (repo / 'bin/omarchy-system-factory-reset').read_text()
|
||||
functions = []
|
||||
for name in ['install_provisioning_units', 'sanitize_factory_baseline', 'stage_full_reset']:
|
||||
for name in ['install_provisioning_units', 'scrub_factory_accounts', 'sanitize_factory_baseline', 'stage_full_reset']:
|
||||
match = re.search(r'^' + name + r'\(\) \{\n.*?^\}', source, re.M | re.S)
|
||||
assert match, name
|
||||
functions.append(match.group())
|
||||
@@ -66,7 +66,11 @@ btrfs() {
|
||||
return 90
|
||||
fi
|
||||
}
|
||||
passwd() { :; }
|
||||
# Account cleanup has its own real-tool suite; keep this fixture focused on USB.
|
||||
usermod() {
|
||||
[[ $1 == --root && ( $2 == "$TOP_MNT/$NEXT_NAME" || $2 == "$TOP_MNT/@factory" ) &&
|
||||
$3 == --password && $4 == '!' && $5 == root ]]
|
||||
}
|
||||
systemd-id128() { echo new-machine-id; }
|
||||
encrypted_install() { return 1; }
|
||||
rebuild_next_boot() { :; }
|
||||
|
||||
@@ -9,15 +9,13 @@ const fs = require('fs')
|
||||
|
||||
const utilQml = fs.readFileSync(path.join(root, 'shell/Commons/Util.qml'), 'utf8')
|
||||
const mediaQml = fs.readFileSync(path.join(root, 'shell/Ui/BackgroundMedia.qml'), 'utf8')
|
||||
const videoQml = fs.readFileSync(path.join(root, 'shell/Ui/BackgroundVideo.qml'), 'utf8')
|
||||
const backgroundQml = fs.readFileSync(path.join(root, 'shell/plugins/background/Background.qml'), 'utf8')
|
||||
const lockQml = fs.readFileSync(path.join(root, 'shell/plugins/lock/LockView.qml'), 'utf8')
|
||||
const lockFeedQml = fs.readFileSync(path.join(root, 'shell/plugins/lock/LockFeedSurface.qml'), 'utf8')
|
||||
const themeSwitcher = fs.readFileSync(path.join(root, 'bin/omarchy-theme-switcher'), 'utf8')
|
||||
const quattroUpgrade = fs.readFileSync(path.join(root, 'bin/omarchy-upgrade-to-quattro'), 'utf8')
|
||||
const multimediaMigration = fs.readFileSync(path.join(root, 'migrations/1786609204.sh'), 'utf8')
|
||||
const barTextColor = fs.readFileSync(path.join(root, 'bin/omarchy-bar-text-color'), 'utf8')
|
||||
const menuImages = fs.readFileSync(path.join(root, 'bin/omarchy-menu-images'), 'utf8')
|
||||
const lockView = fs.readFileSync(path.join(root, 'shell/plugins/lock/LockView.qml'), 'utf8')
|
||||
const lockService = fs.readFileSync(path.join(root, 'shell/plugins/lock/Service.qml'), 'utf8')
|
||||
const batteryService = fs.readFileSync(path.join(root, 'shell/plugins/services/battery/Service.qml'), 'utf8')
|
||||
const themeSet = fs.readFileSync(path.join(root, 'bin/omarchy-theme-set'), 'utf8')
|
||||
@@ -29,54 +27,43 @@ assert(
|
||||
'shared media helper identifies video paths without truncating valid local names'
|
||||
)
|
||||
assert(
|
||||
videoQml.includes('loops: MediaPlayer.Infinite') &&
|
||||
videoQml.includes('autoPlay: root.playbackEnabled') &&
|
||||
videoQml.includes('fillMode: VideoOutput.PreserveAspectCrop') &&
|
||||
/imageUrl: path && !Util\.isVideoPath\(path\) \? Util\.fileUrl\(path\) \+ \(version \? "\?v=" \+ version : ""\) : ""/.test(mediaQml) &&
|
||||
/videoUrl: path && Util\.isVideoPath\(path\) \? Util\.fileUrl\(path\) : ""/.test(mediaQml),
|
||||
'background media plays aspect-cropped videos on a loop, and hands each loader only its own kind of file'
|
||||
!fs.existsSync(path.join(root, 'shell/Ui/BackgroundVideo.qml')),
|
||||
'the shell keeps no video player of its own'
|
||||
)
|
||||
assert(
|
||||
videoQml.includes('MediaPlayer.LoadedMedia') &&
|
||||
videoQml.includes('primePauseTimer') &&
|
||||
videoQml.includes('mediaGeneration') &&
|
||||
videoQml.includes('videoSink') &&
|
||||
videoQml.includes('onVideoFrameChanged') &&
|
||||
videoQml.includes('MediaPlayer.BufferedMedia') &&
|
||||
videoQml.includes('mediaStatus !== MediaPlayer.BufferedMedia') &&
|
||||
videoQml.includes('interval: 1000') &&
|
||||
videoQml.includes('interval: 50') &&
|
||||
videoQml.includes('frameReceived') &&
|
||||
videoQml.includes('output.clearOutput()') &&
|
||||
!videoQml.includes('KeepLastFrame') &&
|
||||
/onPlaybackEnabledChanged:[\s\S]*?if \(playbackEnabled\) player\.play\(\)[\s\S]*?else player\.pause\(\)/.test(videoQml) &&
|
||||
videoQml.includes('primingGeneration') &&
|
||||
videoQml.includes('player.play()') &&
|
||||
videoQml.includes('player.pause()'),
|
||||
'paused video sources are primed to display their first frame'
|
||||
/imageUrl: path && !video \? Util\.fileUrl\(path\) \+ \(version \? "\?v=" \+ version : ""\) : ""/.test(mediaQml) &&
|
||||
/active: root\.path !== "" && !root\.video/.test(mediaQml) &&
|
||||
!mediaQml.includes('videoUrl') &&
|
||||
!mediaQml.includes('videoLoader') &&
|
||||
!mediaQml.includes('videoEnabled'),
|
||||
'background media is image-only: it loads stills and leaves videos to OWE'
|
||||
)
|
||||
assert(
|
||||
!/^\s*import QtMultimedia/m.test(mediaQml) &&
|
||||
mediaQml.includes('source: "BackgroundVideo.qml"'),
|
||||
'the still-image path never imports QtMultimedia, so image-only sessions do not map it'
|
||||
!/^\s*import QtMultimedia/m.test(backgroundQml) &&
|
||||
!/^\s*import QtMultimedia/m.test(lockQml) &&
|
||||
!backgroundQml.includes('MediaPlayer') &&
|
||||
!lockQml.includes('MediaPlayer'),
|
||||
'no shell surface builds a Qt Multimedia pipeline'
|
||||
)
|
||||
assert(
|
||||
!/^\s*Video\s*\{/m.test(videoQml) &&
|
||||
/property bool audioEnabled: false/.test(videoQml) &&
|
||||
/property bool audioEnabled: false/.test(mediaQml) &&
|
||||
/active: root\.audioEnabled && player\.hasAudio/.test(videoQml) &&
|
||||
/audioOutput: audioLoader\.item/.test(videoQml) &&
|
||||
/muted: root\.priming \|\| !root\.playbackEnabled/.test(videoQml) &&
|
||||
/property: "audioEnabled"\s*\n\s*value: root\.audioEnabled/.test(mediaQml) &&
|
||||
/firstScreen: Quickshell\.screens\.length > 0\s*\n\s*&& String\(Quickshell\.screens\[0\]\.name/.test(backgroundQml) &&
|
||||
backgroundQml.includes('audioEnabled: panel.firstScreen') &&
|
||||
!lockQml.includes('audioEnabled'),
|
||||
'a sound track plays from the first monitor only, a silent file builds no audio output, and the lock stays quiet'
|
||||
/^import Owe\.LockFeed$/m.test(lockFeedQml) &&
|
||||
lockFeedQml.includes('LockFeed {') &&
|
||||
lockQml.includes('source: "LockFeedSurface.qml"') &&
|
||||
lockQml.includes('active: root.feedActive') &&
|
||||
/feedActive: root\.video && root\.loadBackground && !root\.displaysBlank && !root\.powerSaverActive/.test(lockQml),
|
||||
'the lock screen shows video through the OWE lock feed, loaded so a missing module costs only the video'
|
||||
)
|
||||
assert(
|
||||
/property: "mediaSource"[\s\S]*?when: videoLoader\.item !== null && Util\.isVideoPath\(root\.path\)\s*\n\s*restoreMode: Binding\.RestoreNone/.test(mediaQml) &&
|
||||
!videoQml.includes('Component.onDestruction'),
|
||||
'a player on its way out keeps its source, so nothing is left loading for its destructor to cancel'
|
||||
!/^import Owe\./m.test(lockQml),
|
||||
'the lock view itself carries no foreign import, so the lock still loads without the feed module'
|
||||
)
|
||||
assert(
|
||||
lockQml.includes('path: root.loadBackground ? (root.video ? root.videoPosterPath : root.backgroundPath) : ""') &&
|
||||
lockQml.indexOf('id: feedLoader') > lockQml.indexOf('MultiEffect {') &&
|
||||
lockQml.includes('visible: root.video') &&
|
||||
!lockQml.includes('wallpaper.video'),
|
||||
'the lock screen keeps its image effect for stills and shows the feed for videos'
|
||||
)
|
||||
assert(
|
||||
!mediaQml.includes('mipmap'),
|
||||
@@ -86,33 +73,21 @@ assert(
|
||||
/instant \|\| !displayedBackground \|\| isVideo\(path\) \|\| isVideo\(displayedBackground\)[\s\S]*displayedBackground = finalPath/.test(backgroundQml),
|
||||
'video switches bypass the image-only reveal stack and use the durable background path'
|
||||
)
|
||||
assert(backgroundQml.includes('BackgroundMedia {') && lockQml.includes('BackgroundMedia {'), 'desktop and lock screen share video-capable media rendering')
|
||||
assert(backgroundQml.includes('BackgroundMedia {') && lockQml.includes('BackgroundMedia {'), 'desktop and lock screen share still rendering')
|
||||
assert(
|
||||
lockQml.includes('source: wallpaper.video ? null : wallpaper') &&
|
||||
lockQml.includes('visible: !wallpaper.video') &&
|
||||
lockQml.includes('visible: wallpaper.video'),
|
||||
'lock screen bypasses its image effect for video output'
|
||||
!backgroundQml.includes('videoEnabled') &&
|
||||
!backgroundQml.includes('displayedReloads') &&
|
||||
!mediaQml.includes('displayedReloads'),
|
||||
'the desktop hands video backgrounds to OWE with no player switch to carry'
|
||||
)
|
||||
assert(
|
||||
/sessionObscured:\s*lockActive \|\| screensaverActive/.test(backgroundQml) &&
|
||||
backgroundQml.includes('playbackEnabled: !root.sessionObscured && !root.powerSaverActive && !panel.fullscreenHere') &&
|
||||
backgroundQml.includes('omarchy.lock') &&
|
||||
backgroundQml.includes('omarchy.idle') &&
|
||||
backgroundQml.includes('omarchy.battery'),
|
||||
'desktop playback stops while covered or on battery power-saver'
|
||||
)
|
||||
assert(
|
||||
backgroundQml.includes('Hyprland.monitorFor(modelData)') &&
|
||||
/fullscreenHere: visibleWorkspace \? visibleWorkspace\.hasFullscreen : false/.test(backgroundQml) &&
|
||||
!backgroundQml.includes('ToplevelManager.activeToplevel'),
|
||||
'a fullscreen window pauses only the output it covers, wherever focus is'
|
||||
)
|
||||
assert(
|
||||
/if \(displayedBackground === finalPath\) displayedReloads \+= 1/.test(backgroundQml) &&
|
||||
backgroundQml.includes('reloads: root.displayedReloads') &&
|
||||
/active: root\.path !== "" && root\.video && !root\.reloading/.test(mediaQml) &&
|
||||
/onReloadsChanged: \{[\s\S]*?reloading = true/.test(mediaQml),
|
||||
'a theme switch that keeps the video path still reopens the replaced file'
|
||||
!backgroundQml.includes('playbackEnabled') &&
|
||||
!backgroundQml.includes('sessionObscured') &&
|
||||
!backgroundQml.includes('fullscreenHere') &&
|
||||
!backgroundQml.includes('Hyprland.monitorFor') &&
|
||||
!backgroundQml.includes('omarchy.lock') &&
|
||||
!backgroundQml.includes('omarchy.battery'),
|
||||
'the desktop no longer carries the shell video pause policy'
|
||||
)
|
||||
assert(
|
||||
barTextColor.includes('magick "$background_path[0]"'),
|
||||
@@ -163,13 +138,12 @@ assert(
|
||||
'a locked video wallpaper follows what each panel actually did, not only what the lock asked for'
|
||||
)
|
||||
assert(
|
||||
lockView.includes('playbackEnabled: root.loadBackground && !root.displaysBlank') &&
|
||||
lockView.includes('&& !root.powerSaverActive') &&
|
||||
/feedActive: root\.video && root\.loadBackground && !root\.displaysBlank && !root\.powerSaverActive/.test(lockQml) &&
|
||||
/displaysBlank: root\.screenBlank\(/.test(lockService) &&
|
||||
/powerSaverActive: root\.powerSaverActive/.test(lockService) &&
|
||||
/function runBlank\(\) \{\s*\n\s*root\.displaysBlank = true/.test(lockService) &&
|
||||
/function runWake\(\) \{\s*\n\s*root\.displaysBlank = false/.test(lockService),
|
||||
'the lock screen stops playback once displays go dark or power-saver is active'
|
||||
'the lock feed stops once displays go dark or power-saver is active'
|
||||
)
|
||||
assert(
|
||||
batteryService.includes('property string activePowerProfile') &&
|
||||
@@ -186,10 +160,6 @@ assert(
|
||||
'theme switcher previews video-only themes, named preview files included'
|
||||
)
|
||||
assert(quattroUpgrade.includes("-iname '*.mp4'"), 'Quattro upgrade can seed a video-only theme background')
|
||||
assert(
|
||||
multimediaMigration.includes('omarchy-pkg-add qt6-multimedia qt6-multimedia-ffmpeg'),
|
||||
'existing Quattro installations receive video playback dependencies'
|
||||
)
|
||||
JS
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
@@ -303,8 +273,11 @@ timeout_rows=$(PATH="$test_tmp/bin:$PATH" XDG_CACHE_HOME="$timeout_cache" \
|
||||
timeout_marker=$(find "$timeout_cache/omarchy/image-selector" -maxdepth 1 -type f -name '*.failed' -print -quit)
|
||||
[[ -z $timeout_marker ]] || fail "a timed out video is left to retry rather than remembered as failed"
|
||||
|
||||
grep -qx 'qt6-multimedia' "$ROOT/install/omarchy-base.packages" || fail "Qt Multimedia runtime is a base package"
|
||||
grep -qx 'qt6-multimedia-ffmpeg' "$ROOT/install/omarchy-base.packages" || fail "Qt Multimedia FFmpeg backend is a base package"
|
||||
grep -qx 'owe' "$ROOT/install/omarchy-base.packages" || fail "OWE is a base package"
|
||||
grep -qx 'owe-lockfeed' "$ROOT/install/omarchy-base.packages" || fail "the OWE lock feed module is a base package"
|
||||
if grep -qx 'qt6-multimedia' "$ROOT/install/omarchy-base.packages"; then
|
||||
fail "Qt Multimedia is no longer needed by the shell"
|
||||
fi
|
||||
|
||||
pass "menu image generator creates thumbnails consumed by the picker"
|
||||
pass "direct picker generates and reuses still thumbnails"
|
||||
@@ -312,7 +285,7 @@ pass "direct picker omits videos whose thumbnails cannot be generated"
|
||||
pass "a rejected video is remembered so it costs nothing on the next open"
|
||||
pass "a timed out video is left to retry"
|
||||
pass "a locked video wallpaper follows the panels' real DPMS state"
|
||||
pass "Qt Multimedia playback dependencies are declared"
|
||||
pass "OWE and its lock feed module are declared"
|
||||
|
||||
source <(awk '
|
||||
/^(is_video_path|snapshot_background_path|background_transition_uses_snapshots|choose_theme_background|choose_staged_theme_background|set_theme_background)\(\) \{/ { copying=1 }
|
||||
|
||||
@@ -11,7 +11,7 @@ if [[ ${OMARCHY_WINDOWS_TEST_NAMESPACE:-0} != 1 ]]; then
|
||||
exec env OMARCHY_WINDOWS_TEST_NAMESPACE=1 \
|
||||
unshare --user --map-current-user --keep-caps --mount --propagation private bash "$0"
|
||||
fi
|
||||
pass "unprivileged mount namespaces unavailable; skipping Windows VM mount runtime tests"
|
||||
skip "unprivileged mount namespaces unavailable; skipping Windows VM mount runtime tests"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -241,7 +241,7 @@ race_swaps="$TMPDIR/concurrent-race-swaps"
|
||||
sleep 0.005
|
||||
fi
|
||||
done
|
||||
) &
|
||||
) >/dev/null &
|
||||
racer_pid=$!
|
||||
concurrent_dc_calls=0
|
||||
dc() {
|
||||
|
||||
@@ -8,7 +8,7 @@ if ((EUID != 0)); then
|
||||
if unshare --user --map-auto --map-root-user --mount true 2>/dev/null; then
|
||||
exec unshare --user --map-auto --map-root-user --mount --propagation private bash "$0"
|
||||
fi
|
||||
pass "automatic subordinate-id namespace unavailable; skipping root Windows VM boundary probe"
|
||||
skip "automatic subordinate-id namespace unavailable; skipping root Windows VM boundary probe"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
Reference in new issue
Block a user