Merge branch 'omacom:quattro' into omarchycn
This commit is contained in:
43 files changed
+1162
-158
No files matched your search
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
migration="$ROOT/migrations/1791376887.sh"
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
|
||||
home="$test_tmp/home"
|
||||
config_dir="$home/.config"
|
||||
mkdir -p "$config_dir" "$test_tmp/targets"
|
||||
|
||||
printf '%s\n' '--enable-features=TouchpadOverscrollHistoryNavigation' >"$config_dir/chromium-flags.conf"
|
||||
printf '%s\n' '--enable-features=' >"$config_dir/chrome-flags.conf"
|
||||
printf '%s' '--ozone-platform=wayland' >"$config_dir/microsoft-edge-stable-flags.conf"
|
||||
printf '%s\n' '--enable-features=BraveFeature' >"$test_tmp/targets/brave-flags.conf"
|
||||
ln -s "$test_tmp/targets/brave-flags.conf" "$config_dir/brave-flags.conf"
|
||||
|
||||
run_migration() {
|
||||
HOME="$home" bash -euo pipefail "$migration" >/dev/null
|
||||
}
|
||||
|
||||
run_migration
|
||||
run_migration
|
||||
|
||||
grep -Fxq -- '--enable-features=TouchpadOverscrollHistoryNavigation,OverlayScrollbar' "$config_dir/chromium-flags.conf" ||
|
||||
fail "the migration preserves existing Chromium features"
|
||||
overlay_count=$(grep -o 'OverlayScrollbar' "$config_dir/chromium-flags.conf" | wc -l)
|
||||
(( overlay_count == 1 )) || fail "the migration does not duplicate the Chromium feature"
|
||||
pass "the migration preserves existing features and can run twice"
|
||||
|
||||
[[ $(cat "$config_dir/chrome-flags.conf") == '--enable-features=OverlayScrollbar' ]] ||
|
||||
fail "the migration fills an empty feature list"
|
||||
[[ $(cat "$config_dir/microsoft-edge-stable-flags.conf") == $'--ozone-platform=wayland\n--enable-features=OverlayScrollbar' ]] ||
|
||||
fail "the migration adds a feature list with a missing trailing newline"
|
||||
pass "the migration handles empty and missing feature lists"
|
||||
|
||||
[[ -L $config_dir/brave-flags.conf ]] || fail "the migration preserves symlinked browser flags"
|
||||
grep -Fxq -- '--enable-features=BraveFeature,OverlayScrollbar' "$config_dir/brave-flags.conf" ||
|
||||
fail "the migration updates the target of symlinked browser flags"
|
||||
pass "the migration follows symlinked browser flags"
|
||||
|
||||
[[ ! -e $config_dir/brave-origin-flags.conf ]] || fail "the migration leaves missing browser flags absent"
|
||||
pass "the migration skips missing browser flags"
|
||||
@@ -0,0 +1,39 @@
|
||||
#!/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
|
||||
mkdir -p "$test_tmp/bin" "$test_tmp/home/.local/share/applications"
|
||||
|
||||
for helper in omarchy-pkg-add omarchy-pkg-drop update-desktop-database; do
|
||||
cat > "$test_tmp/bin/$helper" <<'SH'
|
||||
#!/bin/bash
|
||||
printf '%s %s\n' "${0##*/}" "$*" >> "$TEST_LOG"
|
||||
SH
|
||||
chmod +x "$test_tmp/bin/$helper"
|
||||
done
|
||||
|
||||
launcher="$test_tmp/home/.local/share/applications/Disk Usage.desktop"
|
||||
printf '[Desktop Entry]\nExec=dua i /\n' > "$launcher"
|
||||
|
||||
for (( attempt = 0; attempt < 2; attempt++ )); do
|
||||
env HOME="$test_tmp/home" OMARCHY_PATH="$ROOT" TEST_LOG="$test_tmp/log" PATH="$test_tmp/bin:$PATH" \
|
||||
bash -euo pipefail "$ROOT/migrations/1791345484.sh"
|
||||
[[ ! -e $launcher ]] || fail "the migration removes the old Disk Usage launcher"
|
||||
done
|
||||
pass "the Disktree migration removes the old launcher and can run twice"
|
||||
|
||||
expected="omarchy-pkg-add disktree-bin
|
||||
omarchy-pkg-drop dua-cli
|
||||
update-desktop-database $test_tmp/home/.local/share/applications"
|
||||
[[ $(cat "$test_tmp/log") == "$expected"$'\n'"$expected" ]] || fail "the migration installs Disktree before removing dua and refreshing launchers"
|
||||
pass "the migration installs Disktree before removing dua and refreshing launchers"
|
||||
|
||||
rm -r "$test_tmp/home/.local/share/applications"
|
||||
env HOME="$test_tmp/home" OMARCHY_PATH="$ROOT" TEST_LOG="$test_tmp/log" PATH="$test_tmp/bin:$PATH" \
|
||||
bash -euo pipefail "$ROOT/migrations/1791345484.sh"
|
||||
[[ ! -d $test_tmp/home/.local/share/applications ]] || fail "the migration relies on the packaged Disktree launcher"
|
||||
pass "the migration relies on the packaged Disktree launcher when no local launchers exist"
|
||||
@@ -52,7 +52,9 @@ case "$name" in
|
||||
;;
|
||||
mv)
|
||||
[[ ${TEST_PUBLISH_FAIL:-0} != 1 ]] || exit 1
|
||||
[[ ${TEST_REQUIRE_EXISTING_TARGET:-0} != 1 || -f ${@: -1} ]] || exit 1
|
||||
/usr/bin/mv "$@"
|
||||
[[ ${TEST_KILL_AFTER_PUBLISH:-0} != 1 ]] || /usr/bin/kill -KILL "$PPID"
|
||||
[[ ${TEST_POST_PUBLISH_FAIL:-0} != 1 ]] || : >"$TEST_GRANT_ROOT/run/omarchy-sudo-passwordless-package-removing"
|
||||
;;
|
||||
systemd-run)
|
||||
@@ -69,7 +71,15 @@ case "$name" in
|
||||
sudo)
|
||||
if [[ ${1:-} == -h ]]; then echo 'usage: sudo [-N] command'; exit 0; fi
|
||||
if [[ ${1:-} == -k ]]; then exit 0; fi
|
||||
if [[ ${1:-} == -N ]]; then shift; fi
|
||||
if [[ ${1:-} == -n && ${3:-} == -l ]]; then
|
||||
[[ ${TEST_POLICY_FAILURE:-0} != 1 ]] || exit 1
|
||||
default_policy=' Options: authenticate'
|
||||
[[ ${TEST_STATUS:-3} == 3 ]] || default_policy=' Options: !authenticate'
|
||||
printf '%s\n' "${TEST_POLICY:-$default_policy}"
|
||||
exit 0
|
||||
fi
|
||||
if [[ ${1:-} == -n ]]; then shift; fi
|
||||
if [[ ${1:-} == -N || ${1:-} == -kn ]]; then shift; fi
|
||||
if [[ ${1:-} == -- ]]; then shift; fi
|
||||
if [[ ${TEST_MIGRATION:-0} == 1 ]]; then
|
||||
[[ ${TEST_NO_SUDO:-0} != 1 ]] || exit 1
|
||||
@@ -78,7 +88,14 @@ case "$name" in
|
||||
[[ ${2:-} != __status ]] || exit "${TEST_STATUS:-3}"
|
||||
fi
|
||||
;;
|
||||
gum) exit 1 ;;
|
||||
gum)
|
||||
if [[ $1 == choose ]]; then
|
||||
[[ ${TEST_CHOICE_CANCEL:-0} != 1 ]] || exit 130
|
||||
printf '%s\n' "${TEST_CHOICE:-15 minutes}"
|
||||
else
|
||||
exit "${TEST_CONFIRM_STATUS:-1}"
|
||||
fi
|
||||
;;
|
||||
*) exit 99 ;;
|
||||
esac
|
||||
STUB
|
||||
@@ -120,6 +137,6 @@ assert_status() {
|
||||
(( actual == expected )) || fail "expected status $expected, got $actual from $*"
|
||||
}
|
||||
reset_grant() {
|
||||
rm -f "$test_tmp/etc/sudoers.d/99-omarchy-nopasswd-1000" "$test_tmp/run/omarchy-sudo-passwordless-package-removing"
|
||||
rm -f "$test_tmp/etc/sudoers.d/99-omarchy-permanent-nopasswd-1000" "$test_tmp/etc/sudoers.d/99-omarchy-nopasswd-1000" "$test_tmp/run/omarchy-sudo-passwordless-package-removing"
|
||||
: >"$test_tmp/commands"
|
||||
}
|
||||
@@ -147,7 +147,7 @@ assert(
|
||||
)
|
||||
assert(
|
||||
/OverlayWindow \{\s*id: panel\s*shown: root\.opened\s*shownKeyboardFocus: root\.imagesLoaded \? WlrKeyboardFocus\.Exclusive : WlrKeyboardFocus\.None/.test(imagePickerQml),
|
||||
'image picker parks on OverlayWindow and takes the keyboard once images load'
|
||||
'image picker uses OverlayWindow and takes the keyboard once images load'
|
||||
)
|
||||
assert(
|
||||
/model: visibleImages/.test(imagePickerQml) &&
|
||||
|
||||
@@ -32,7 +32,7 @@ for status in 1 2 3; do
|
||||
else
|
||||
(( result != 0 )) && ! grep -q '^gum ' "$test_tmp/commands" || fail "inspection errors must not offer enablement"
|
||||
fi
|
||||
grep -q '^sudo -N -- .* __status ' "$test_tmp/commands" || fail "status must not publish reusable authorization"
|
||||
grep -q '^sudo -n -N -l -l -- .* __status ' "$test_tmp/commands" || fail "status must inspect policy without authentication"
|
||||
[[ $(tail -1 "$test_tmp/commands") == 'sudo -k' ]] || fail "public exit must revoke its authorization"
|
||||
done
|
||||
pass "public status distinguishes inactive from errors and revokes authorization on exit"
|
||||
@@ -164,3 +164,214 @@ cp "$ROOT/default/libalpm/hooks/05-omarchy-passwordless-revoke.hook" "$test_tmp/
|
||||
[[ ! -e $(rule_file 1000) ]]
|
||||
)
|
||||
pass "trailing-dollar accounts publish valid native policy and alias-shaped names never publish"
|
||||
|
||||
reset_grant
|
||||
(
|
||||
source "$library"
|
||||
valid_duration permanent
|
||||
! valid_duration forever || fail "invalid duration was accepted"
|
||||
enable_locked 1000 permanent
|
||||
read_grant 1000
|
||||
[[ $GRANT_NAME == audituser && -z $GRANT_DEADLINE ]]
|
||||
[[ $(stat -c '%a' "$(rule_file 1000 permanent)") == 440 ]]
|
||||
/usr/sbin/visudo -cf "$(rule_file 1000 permanent)" >/dev/null
|
||||
! grep -q '^systemd-run ' "$test_tmp/commands" || fail "permanent grant started a timer"
|
||||
TEST_EXPIRED=1 status_locked 1000
|
||||
TEST_EXPIRED=1 expire_locked 1000
|
||||
cleanup_all_locked
|
||||
/usr/bin/systemd-tmpfiles --root="$test_tmp" --remove --boot --inline 'r! /etc/sudoers.d/99-omarchy-nopasswd-*'
|
||||
[[ -f $(rule_file 1000 permanent) ]]
|
||||
cleanup_uid_locked 1000
|
||||
assert_status 3 status_locked 1000
|
||||
)
|
||||
pass "permanent access has no timer, survives expiry and temporary cleanup, and can be disabled"
|
||||
|
||||
reset_grant
|
||||
(
|
||||
source "$library"
|
||||
enable_locked 1000 15
|
||||
enable_locked 1000 permanent
|
||||
[[ ! -e $(rule_file 1000) && -e $(rule_file 1000 permanent) ]]
|
||||
TEST_EXPIRED=1 expire_locked 1000
|
||||
[[ -e $(rule_file 1000 permanent) ]]
|
||||
enable_locked 1000 60
|
||||
[[ -e $(rule_file 1000) && ! -e $(rule_file 1000 permanent) ]]
|
||||
TEST_EXPIRED=1 expire_locked 1000
|
||||
assert_status 3 status_locked 1000
|
||||
TEST_PUBLISH_FAIL=1 assert_status 1 enable_locked 1000 permanent
|
||||
[[ ! -e $(rule_file 1000 permanent) ]]
|
||||
)
|
||||
pass "switching duration replaces the prior policy and old callbacks preserve permanent access"
|
||||
|
||||
reset_grant
|
||||
(
|
||||
source "$library"
|
||||
permanent=$(rule_file 1000 permanent)
|
||||
printf 'audituser ALL=(ALL) NOPASSWD: /usr/bin/true\n' >"$permanent"
|
||||
assert_status 2 status_locked 1000
|
||||
assert_status 1 enable_locked 1000 permanent
|
||||
assert_status 1 cleanup_uid_locked 1000
|
||||
[[ -e $permanent ]]
|
||||
rm "$permanent"
|
||||
enable_locked 1000 permanent
|
||||
TEST_BAD_PATH="$permanent" assert_status 2 status_locked 1000
|
||||
)
|
||||
pass "permanent policy rejects unsafe ownership and preserves administrator edits"
|
||||
|
||||
for choice in '15 minutes' '1 Hour' '1 Day' Permanently; do
|
||||
case "$choice" in
|
||||
'15 minutes') expected=15 ;;
|
||||
'1 Hour') expected=60 ;;
|
||||
'1 Day') expected=1440 ;;
|
||||
Permanently) expected=permanent ;;
|
||||
esac
|
||||
: >"$test_tmp/commands"
|
||||
TEST_CHOICE="$choice" TEST_CONFIRM_STATUS=0 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" >"$test_tmp/public.log" 2>&1
|
||||
grep -q '^gum choose .*15 minutes 1 Hour 1 Day Permanently$' "$test_tmp/commands"
|
||||
grep -q "^sudo -N -- .* __enable .* $expected$" "$test_tmp/commands"
|
||||
[[ $(tail -1 "$test_tmp/commands") == 'sudo -k' ]]
|
||||
if [[ $expected == permanent ]]; then
|
||||
grep -q 'remain enabled across reboots until you disable it' "$test_tmp/public.log"
|
||||
! grep -q 'automatically disable' "$test_tmp/public.log" || fail "permanent grant claims automatic expiry"
|
||||
fi
|
||||
done
|
||||
pass "each duration choice dispatches its exact grant duration with permanent-specific copy"
|
||||
|
||||
: >"$test_tmp/commands"
|
||||
assert_status 130 env TEST_CHOICE_CANCEL=1 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" >"$test_tmp/public.log" 2>&1
|
||||
! grep -q '__enable\|^gum confirm ' "$test_tmp/commands" || fail "cancelled picker continued enablement"
|
||||
[[ $(tail -1 "$test_tmp/commands") == 'sudo -k' ]]
|
||||
: >"$test_tmp/commands"
|
||||
TEST_STATUS=0 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" >"$test_tmp/public.log" 2>&1
|
||||
! grep -q '^gum ' "$test_tmp/commands" || fail "disabling access offered a picker"
|
||||
grep -q '__disable ' "$test_tmp/commands"
|
||||
pass "cancelling the picker grants nothing and active access still toggles off"
|
||||
|
||||
for confirm_status in 0 1; do
|
||||
: >"$test_tmp/commands"
|
||||
TEST_STATUS=0 TEST_CONFIRM_STATUS=$confirm_status /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" permanent >"$test_tmp/public.log" 2>&1
|
||||
grep -q '^gum confirm Enable passwordless sudo permanently?' "$test_tmp/commands"
|
||||
if (( confirm_status == 0 )); then
|
||||
grep -q '__enable .* permanent$' "$test_tmp/commands"
|
||||
else
|
||||
! grep -q '__enable ' "$test_tmp/commands" || fail "declining permanent access still enabled it"
|
||||
fi
|
||||
done
|
||||
pass "changing active access to permanent always requires confirmation"
|
||||
|
||||
reset_grant
|
||||
(
|
||||
source "$library"
|
||||
enable_locked 1000 15
|
||||
TEST_REQUIRE_EXISTING_TARGET=1 enable_locked 1000 60
|
||||
cleanup_uid_locked 1000
|
||||
enable_locked 01000 permanent
|
||||
status_locked 1000
|
||||
[[ -e $(rule_file 1000 permanent) ]]
|
||||
[[ ! -e $test_tmp/etc/sudoers.d/99-omarchy-permanent-nopasswd-01000 ]]
|
||||
cleanup_uid_locked 1000
|
||||
assert_status 3 status_locked 01000
|
||||
)
|
||||
pass "renewal replaces the existing rule atomically and zero-padded UIDs share one policy"
|
||||
|
||||
|
||||
for policy in ' Options: !authenticate' ' Options: authenticate'; do
|
||||
: >"$test_tmp/commands"
|
||||
expected=1
|
||||
[[ $policy != *'!authenticate'* ]] || expected=0
|
||||
assert_status "$expected" env TEST_POLICY="$policy" /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" --active
|
||||
[[ $(wc -l <"$test_tmp/commands") == 1 ]]
|
||||
grep -q '^sudo -n -N -l -l -- .* __status ' "$test_tmp/commands"
|
||||
done
|
||||
assert_status 1 env TEST_POLICY_FAILURE=1 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" --active
|
||||
pass "menu probe reads policy tags without executing a privileged action or changing the timestamp"
|
||||
|
||||
for status in 0 3; do
|
||||
: >"$test_tmp/commands"
|
||||
TEST_STATUS=$status /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" --disable >"$test_tmp/public.log" 2>&1
|
||||
! grep -q '^gum\|__enable ' "$test_tmp/commands" || fail "explicit disable offered enablement"
|
||||
if (( status == 0 )); then
|
||||
grep -q '__disable ' "$test_tmp/commands"
|
||||
fi
|
||||
done
|
||||
pass "explicit disable never enables access even when a displayed grant has expired"
|
||||
|
||||
: >"$test_tmp/commands"
|
||||
TEST_STATUS=3 TEST_CHOICE='15 minutes' TEST_CONFIRM_STATUS=0 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" >"$test_tmp/public.log" 2>&1
|
||||
[[ $(grep -c '^sudo -N -- ' "$test_tmp/commands") == 1 ]] || fail "enable must authenticate exactly one sudo call"
|
||||
grep -q '^sudo -N -- .* __enable .* 15$' "$test_tmp/commands"
|
||||
! grep -q '^sudo -n -N -- .* __status ' "$test_tmp/commands" || fail "inactive flow attempted root status"
|
||||
[[ $(tail -1 "$test_tmp/commands") == 'sudo -k' ]]
|
||||
pass "enabling from inactive policy has exactly one password-capable sudo invocation"
|
||||
|
||||
: >"$test_tmp/commands"
|
||||
TEST_STATUS=0 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" --disable >"$test_tmp/public.log" 2>&1
|
||||
grep -q '^sudo -n -N -- .* __disable ' "$test_tmp/commands" || fail "disable must refuse interactive authentication"
|
||||
! grep -q '^sudo -N -- ' "$test_tmp/commands" || fail "disable attempted an interactive sudo command"
|
||||
pass "disabling active access is fully noninteractive"
|
||||
|
||||
for duration in permanent 15; do
|
||||
reset_grant
|
||||
(
|
||||
source "$library"
|
||||
if [[ $duration == permanent ]]; then
|
||||
enable_locked 1000 15
|
||||
else
|
||||
enable_locked 1000 permanent
|
||||
fi
|
||||
assert_status 137 env TEST_KILL_AFTER_PUBLISH=1 TEST_EUID=0 SUDO_UID=1000 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" __enable 1000 "$duration"
|
||||
[[ -f $(rule_file 1000) && -f $(rule_file 1000 permanent) ]]
|
||||
status_locked 1000
|
||||
TEST_EXPIRED=1 expire_locked 1000
|
||||
[[ -f $(rule_file 1000 permanent) ]]
|
||||
cleanup_uid_locked 1000
|
||||
assert_status 3 status_locked 1000
|
||||
)
|
||||
done
|
||||
pass "SIGKILL during either duration switch leaves access inspectable and revocable without expiring permanent policy"
|
||||
|
||||
reset_grant
|
||||
(
|
||||
source "$library"
|
||||
enable_locked 1000 permanent
|
||||
timed=$(rule_file 1000)
|
||||
printf 'otheruser ALL=(ALL) NOTAFTER=99991231235959Z NOPASSWD: ALL\n' >"$timed"
|
||||
assert_status 2 read_grant 1000
|
||||
assert_status 1 enable_locked 1000 15
|
||||
printf 'audituser ALL=(ALL) NOTAFTER=99991231235959Z NOPASSWD: ALL\n' >"$timed"
|
||||
TEST_BAD_PATH="$timed" assert_status 2 read_grant 1000
|
||||
rm "$timed"
|
||||
ln -s "$(rule_file 1000 permanent)" "$timed"
|
||||
assert_status 2 read_grant 1000
|
||||
rm "$timed"
|
||||
printf 'audituser ALL=(ALL) NOTAFTER=99991231235959Z NOPASSWD: ALL\n' >"$timed"
|
||||
enable_locked 1000 15
|
||||
[[ -f $timed && ! -e $(rule_file 1000 permanent) ]]
|
||||
)
|
||||
pass "paired policy recovery rejects mismatched or unsafe rules and allows a complete duration change"
|
||||
|
||||
: >"$test_tmp/commands"
|
||||
TEST_POLICY_FAILURE=1 TEST_STATUS=0 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" --disable >"$test_tmp/public.log" 2>&1
|
||||
grep -q '^sudo -n -N -- .* __disable ' "$test_tmp/commands"
|
||||
! grep -q '^gum\|^sudo -N -- ' "$test_tmp/commands" || fail "listpw=always disable prompted"
|
||||
TEST_POLICY_FAILURE=1 TEST_STATUS=0 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" --active
|
||||
assert_status 1 env TEST_POLICY_FAILURE=1 TEST_STATUS=1 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" --active
|
||||
: >"$test_tmp/commands"
|
||||
TEST_POLICY_FAILURE=1 TEST_STATUS=1 TEST_CONFIRM_STATUS=0 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" 15 >"$test_tmp/public.log" 2>&1
|
||||
[[ $(grep -c '^sudo -N -- ' "$test_tmp/commands") == 1 ]] || fail "listing failure must leave one enable authentication"
|
||||
grep -q '^sudo -N -- .* __enable .* 15$' "$test_tmp/commands"
|
||||
: >"$test_tmp/commands"
|
||||
assert_status 1 env TEST_POLICY_FAILURE=1 TEST_STATUS=2 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" 15 >"$test_tmp/public.log" 2>&1
|
||||
! grep -q '^gum\|__enable ' "$test_tmp/commands" || fail "unsafe grant was offered enablement"
|
||||
pass "password-required listings do not block enabling, disabling, or active detection"
|
||||
|
||||
: >"$test_tmp/commands"
|
||||
TEST_POLICY_FAILURE=1 TEST_STATUS=2 /usr/bin/bash -p "$test_tmp/omarchy-sudo-passwordless" --active
|
||||
grep -q '^sudo -kn -- .* __status ' "$test_tmp/commands" || fail "fallback probe must ignore cached credentials"
|
||||
(
|
||||
source "$library"
|
||||
with_root_lock() { return 1; }
|
||||
verify_sudo_caller() { return 0; }
|
||||
assert_status 2 root_dispatch __status 1000
|
||||
)
|
||||
pass "status lock failures remain inspection errors and an authenticated unsafe grant keeps its warning"
|
||||
@@ -9,31 +9,122 @@ const fs = require('fs')
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8')
|
||||
const overlay = read('shell/Ui/OverlayWindow.qml')
|
||||
|
||||
// Closed, the surface parks 1x1, input-less and without keyboard below
|
||||
// windows: off the overlay layer, so it never blocks direct scanout.
|
||||
// Closing must unmap, not leave a surface on an output that may disappear.
|
||||
assert(
|
||||
overlay.includes('visible: true') &&
|
||||
overlay.includes('anchors { top: true; left: true; bottom: shown; right: shown }') &&
|
||||
overlay.includes('implicitWidth: 1') &&
|
||||
overlay.includes('implicitHeight: 1') &&
|
||||
overlay.includes('mask: shown ? null : emptyRegion') &&
|
||||
overlay.includes('WlrLayershell.layer: shown ? shownLayer : WlrLayer.Bottom') &&
|
||||
overlay.includes('WlrLayershell.keyboardFocus: shown ? shownKeyboardFocus : WlrKeyboardFocus.None'),
|
||||
'overlay window parks a 1x1 input-less surface below windows while hidden'
|
||||
overlay.includes('visible: shown && !!targetScreen && !!targetMonitor') &&
|
||||
overlay.includes('anchors { top: true; left: true; bottom: true; right: true }') &&
|
||||
!overlay.includes('implicitWidth: 1') && !overlay.includes('WlrLayer.Bottom'),
|
||||
'closed overlays unmap instead of parking a surface'
|
||||
)
|
||||
|
||||
// Content stays hidden until the surface has grown, so no frame drawn at 1x1
|
||||
// is stretched across the screen.
|
||||
const readyExpression = /readonly property bool contentReady: ([^\n]*(?:\n &&[^\n]*)*)/.exec(overlay)[1]
|
||||
const ready = new Function('shown', 'backingWindowVisible', 'width', 'height', 'targetScreen', 'targetMonitor', 'devicePixelRatio', `return ${readyExpression}`)
|
||||
const screen = { name: 'eDP-1', width: 1200, height: 750 }
|
||||
const monitor = { name: 'eDP-1', scale: 1.6 }
|
||||
for (const scale of [2, 1, 1.6]) {
|
||||
assertEqual(ready(true, true, 1200, 750, screen, monitor, scale), scale === 1.6,
|
||||
`opening frames at ${scale}x wait for the monitor's 1.6x scale`)
|
||||
}
|
||||
for (const scale of [1, 1.25, 1.5, 2]) {
|
||||
assert(ready(true, true, 1200, 750, screen, { scale }, scale),
|
||||
`overlay reveals at the correct ${scale}x scale`)
|
||||
}
|
||||
assert(ready(true, true, 1200, 750, screen, { scale: 1.333333 }, 160 / 120),
|
||||
'scale comparison tolerates compositor floating point rounding')
|
||||
assert(!ready(true, true, 500, 500, screen, monitor, 1.6),
|
||||
'initial window geometry never stretches content across the screen')
|
||||
assert(!ready(true, false, 1200, 750, screen, monitor, 1.6),
|
||||
'content waits for a mapped backing window')
|
||||
assert(!ready(false, true, 1200, 750, screen, monitor, 1.6),
|
||||
'closed content stays transparent even at the correct scale')
|
||||
assert(!ready(true, true, 1200, 750, null, monitor, 1.6) &&
|
||||
!ready(true, true, 1200, 750, screen, null, 1.6),
|
||||
'content waits for a connected screen and its monitor information')
|
||||
assert(
|
||||
/target: window\.contentItem\s*property: "visible"\s*value: window\.shown && window\.width > 1 && window\.height > 1/.test(overlay),
|
||||
'overlay window draws nothing until the surface has grown'
|
||||
/target: window\.contentItem\s*property: "opacity"\s*value: window\.contentRevealed \? 1 : 0/.test(overlay),
|
||||
'transparent opening frames preserve immediate keyboard handling'
|
||||
)
|
||||
|
||||
assert(
|
||||
/onShownChanged: if \(shown\) targetScreen = focusedScreen\(\) \|\| targetScreen/.test(overlay) &&
|
||||
overlay.includes('screen: targetScreen'),
|
||||
'overlay window follows the focused monitor each time it is shown'
|
||||
)
|
||||
const revealBody = /onContentReadyChanged: \{([\s\S]*?)\n \}/.exec(overlay)[1]
|
||||
const reveal = new Function('window', 'Qt', `with (window) { ${revealBody} }`)
|
||||
const reveals = []
|
||||
const revealQt = { callLater(callback) { reveals.push(callback) } }
|
||||
const content = { contentReady: true, contentRevealed: false }
|
||||
reveal(content, revealQt)
|
||||
assert(!content.contentRevealed, 'scale and resize notifications finish before content reveals')
|
||||
reveals.shift()()
|
||||
assert(content.contentRevealed, 'settled content reveals on the next event-loop turn')
|
||||
content.contentReady = false
|
||||
reveal(content, revealQt)
|
||||
assert(!content.contentRevealed, 'losing readiness immediately hides content')
|
||||
content.contentReady = true
|
||||
reveal(content, revealQt)
|
||||
content.contentReady = false
|
||||
reveal(content, revealQt)
|
||||
reveals.shift()()
|
||||
assert(!content.contentRevealed, 'a close or screen loss cancels a pending reveal')
|
||||
|
||||
const focusedScreenBody = /function focusedScreen\(\) \{([\s\S]*?)\n \}/.exec(overlay)[1]
|
||||
const focusedScreen = new Function('Hyprland', 'Quickshell', focusedScreenBody)
|
||||
const external = { name: 'DP-1', width: 1920, height: 1080 }
|
||||
assertEqual(focusedScreen({ focusedMonitor: { name: 'DP-1' } }, { screens: [screen, external] }), external,
|
||||
'overlay selects the focused output')
|
||||
assertEqual(focusedScreen({ focusedMonitor: { name: 'DP-1' } }, { screens: [screen] }), screen,
|
||||
'disconnecting the focused output falls back to a connected screen')
|
||||
assertEqual(focusedScreen({ focusedMonitor: null }, { screens: [] }), null,
|
||||
'no outputs leaves the overlay unmapped')
|
||||
|
||||
const shownHandler = /onShownChanged: \{([\s\S]*?)\n \}/.exec(overlay)[1]
|
||||
let refreshed = 0
|
||||
const Hyprland = { refreshMonitors() { refreshed++ } }
|
||||
const changeShown = new Function('shown', 'focusedScreen', 'Hyprland', `let targetScreen = null; ${shownHandler}; return targetScreen`)
|
||||
assertEqual(changeShown(false, () => external, Hyprland), null, 'closing drops the previous screen reference')
|
||||
assertEqual(refreshed, 0, 'closing does not refresh monitor data')
|
||||
assertEqual(changeShown(true, () => screen, Hyprland), screen, 'reopening selects a current screen')
|
||||
assertEqual(refreshed, 1, 'opening refreshes monitor scales after a live change while closed')
|
||||
const scaleHandler = /onDevicePixelRatioChanged: ([^\n]+)/.exec(overlay)[1]
|
||||
const changeScale = new Function('shown', 'Hyprland', scaleHandler)
|
||||
changeScale(false, Hyprland)
|
||||
assertEqual(refreshed, 1, 'scale changes on hidden windows do not refresh monitor data')
|
||||
const changedScreen = { name: 'eDP-1', width: 960, height: 600 }
|
||||
assert(!ready(true, true, 960, 600, changedScreen, monitor, 2),
|
||||
'a stale monitor scale keeps content transparent after a live scale change')
|
||||
Hyprland.refreshMonitors = () => { refreshed++; monitor.scale = 2 }
|
||||
changeScale(true, Hyprland)
|
||||
assertEqual(refreshed, 2, 'a showing window refreshes monitor scales when Qt receives a live change')
|
||||
assert(ready(true, true, 960, 600, changedScreen, monitor, 2),
|
||||
'fresh monitor data lets content reveal at the new scale')
|
||||
|
||||
const screensChangedBody = /function onScreensChanged\(\) \{([\s\S]*?)\n \}/.exec(overlay)[1]
|
||||
const screensChanged = new Function('window', 'Quickshell', 'Qt', screensChangedBody)
|
||||
const pending = []
|
||||
const Qt = { callLater(callback) { pending.push(callback) } }
|
||||
const window = { shown: true, targetScreen: external, focusedScreen: () => screen }
|
||||
screensChanged(window, { screens: [screen] }, Qt)
|
||||
assertEqual(window.targetScreen, null, 'screen removal unmaps before Qt finishes destroying the old window')
|
||||
pending.shift()()
|
||||
assertEqual(window.targetScreen, screen, 'a showing overlay moves off a disconnected screen')
|
||||
window.targetScreen = screen
|
||||
window.focusedScreen = () => external
|
||||
screensChanged(window, { screens: [screen, external] }, Qt)
|
||||
assertEqual(window.targetScreen, screen, 'connecting another screen does not move a showing overlay')
|
||||
window.shown = false
|
||||
window.targetScreen = null
|
||||
screensChanged(window, { screens: [screen, external] }, Qt)
|
||||
assertEqual(window.targetScreen, null, 'screen changes leave a closed overlay unmapped')
|
||||
window.shown = true
|
||||
window.targetScreen = external
|
||||
screensChanged(window, { screens: [screen] }, Qt)
|
||||
window.shown = false
|
||||
pending.shift()()
|
||||
assertEqual(window.targetScreen, null, 'closing before a deferred remap prevents a stale reopen')
|
||||
window.shown = true
|
||||
window.targetScreen = external
|
||||
screensChanged(window, { screens: [screen] }, Qt)
|
||||
window.targetScreen = screen
|
||||
window.focusedScreen = () => external
|
||||
pending.shift()()
|
||||
assertEqual(window.targetScreen, screen, 'a deferred remap preserves the screen chosen by a newer open')
|
||||
|
||||
const overlays = {
|
||||
'shell/plugins/menu/Menu.qml': 'shown: root.opened && root.rowsLoaded',
|
||||
@@ -48,11 +139,11 @@ for (const [file, shown] of Object.entries(overlays)) {
|
||||
const qml = read(file)
|
||||
assert(
|
||||
qml.includes('OverlayWindow {') && qml.includes(shown) && !/PanelWindow \{\s*(id: panel\s*)?visible: root\.opened/.test(qml),
|
||||
`${file} keeps its surface through OverlayWindow`
|
||||
`${file} uses the shared overlay scale and lifecycle handling`
|
||||
)
|
||||
}
|
||||
|
||||
// Window visibility never changes now, so close-time resets key off shown.
|
||||
// Layout resets on a requested close, independent of mapping readiness.
|
||||
const menuQml = read('shell/plugins/menu/Menu.qml')
|
||||
assert(
|
||||
menuQml.includes('onShownChanged: if (!shown) { cardTop = -1; maxRowsHeight = -1 }') &&
|
||||
@@ -60,9 +151,15 @@ assert(
|
||||
!/onVisibleChanged: if \(!visible\) \{ cardTop/.test(menuQml),
|
||||
'the menu unfreezes its layout when the overlay hides'
|
||||
)
|
||||
const screenHandler = /onTargetScreenChanged: \{([^\n]*)\}/.exec(menuQml)[1]
|
||||
const changeScreen = new Function('panel', `with (panel) { ${screenHandler} }`)
|
||||
const menuPanel = { shown: true, cardTop: 945, maxRowsHeight: 510 }
|
||||
changeScreen(menuPanel)
|
||||
assertEqual(menuPanel.cardTop, -1, 'moving an open menu off a disconnected output unfreezes its old top edge')
|
||||
assertEqual(menuPanel.maxRowsHeight, -1, 'moving an open menu drops the old output row-height limit')
|
||||
assert(menuPanel.shown, 'resetting the layout keeps the menu open')
|
||||
|
||||
// A kept surface grows from its parked 1x1 when shown, which Hyprland animates
|
||||
// as a slide in from the corner unless its layer rule turns animation off.
|
||||
// Fullscreen overlays open without an additional compositor animation.
|
||||
const shellRules = read('default/hypr/apps/omarchy-shell.lua')
|
||||
const noAnim = /namespace = "\^\(([^)]*)\)\$" \}, no_anim = true/.exec(shellRules)
|
||||
assert(noAnim, 'the shell overlays share one no-animation layer rule')
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
run_node_test <<'JS'
|
||||
const fs = require('fs')
|
||||
const read = name => fs.readFileSync(path.join(root, name), 'utf8')
|
||||
const indicator = read('shell/plugins/bar/indicators/PasswordlessSudo.qml')
|
||||
const widget = read('shell/plugins/bar/widgets/Indicators.qml')
|
||||
const manifest = JSON.parse(read('shell/plugins/bar/widgets/Indicators.manifest.json'))
|
||||
assert(widget.match(/defaultIndicatorEntries: \[ "PasswordlessSudo", "Dictation"/), 'passwordless sudo is included in the default indicator tray')
|
||||
assert(manifest.barWidget.schema.find(field => field.key === 'items').options.some(option => option.value === 'PasswordlessSudo'), 'passwordless sudo is configurable alongside Night Light')
|
||||
assert(indicator.includes('useActiveColor: true') && indicator.includes('activeColor: Color.urgent'), 'active passwordless sudo uses the theme danger color')
|
||||
assertEqual(indicator.match(/activeText: "([^"]+)"/)[1], indicator.match(/inactiveText: "([^"]+)"/)[1], 'sudo keeps the same icon in both states')
|
||||
assert(!widget.includes('sudoHorizontal') && !widget.includes('sudoVertical'), 'sudo participates in the normal indicator blocks')
|
||||
assert(!indicator.includes('visible:'), 'sudo uses the shared indicator visibility and hover behavior')
|
||||
assert(indicator.includes('command: ["omarchy-sudo-passwordless", "--active"]'), 'indicator uses the noninteractive grant probe')
|
||||
assert(indicator.includes('root.granted = exitCode === 0 && exitStatus === 0'), 'failed or interrupted probes do not claim an active grant')
|
||||
assert(indicator.includes('interval: 5000') && indicator.includes('onTriggered: root.refresh()'), 'indicator refreshes after activation, revocation, and expiry')
|
||||
assert(indicator.includes('command: ["omarchy-sudo-passwordless", "--disable"]'), 'active indicator revokes access through a background process')
|
||||
assert(indicator.includes('if (root.granted) disableProc.running = true'), 'active click bypasses the terminal launcher')
|
||||
assert(indicator.includes('root.indicatorHost.refresh()'), 'disabling access refreshes all indicator instances immediately')
|
||||
const press = new Function('root', 'disableProc', indicator.match(/onPressed: function\(\) \{([\s\S]*?)\n \}/)[1])
|
||||
const launched = []
|
||||
const button = { granted: true, bar: { run: command => launched.push(command) } }
|
||||
const revoke = { running: false }
|
||||
press(button, revoke)
|
||||
assert(revoke.running && launched.length === 0, 'clicking active sudo starts background revocation without a terminal')
|
||||
button.granted = false
|
||||
press(button, revoke)
|
||||
assert(launched.length === 0, 'repeat clicks during revocation cannot open the enable flow')
|
||||
revoke.running = false
|
||||
press(button, revoke)
|
||||
assertDeepEqual(launched, ['omarchy-launch-floating-terminal-with-presentation omarchy-sudo-passwordless'], 'inactive click still opens interactive setup')
|
||||
JS
|
||||
@@ -23,7 +23,7 @@ SH
|
||||
}
|
||||
|
||||
run_orphan_checker() {
|
||||
HOME="$test_home" PATH="$stub_bin:$PATH" "$ROOT/bin/omarchy-update-orphan-pkgs"
|
||||
HOME="$test_home" PATH="$stub_bin:$ROOT/bin:$PATH" "$ROOT/bin/omarchy-update-orphan-pkgs" "$@"
|
||||
}
|
||||
|
||||
write_stub pacman 'if [[ $1 == "-Qtdq" ]]; then printf "old-lib\nunused-tool\n"; exit 0; fi; exit 1'
|
||||
@@ -35,7 +35,29 @@ grep -q '^ old-lib$' "$test_tmp/noninteractive.out" || fail "orphan checker lis
|
||||
grep -q 'Re-run omarchy-update-orphan-pkgs in a terminal' "$test_tmp/noninteractive.out" || fail "orphan checker does not remove packages non-interactively"
|
||||
pass "orphan checker only reports orphans non-interactively"
|
||||
|
||||
export REMOVAL_LOG="$test_tmp/removal.log"
|
||||
write_stub pacman 'case $1 in
|
||||
-Qtdq|-Qq) printf "old-lib\nunused-tool\n" ;;
|
||||
-Rns) printf "%s\n" "$@" >"$REMOVAL_LOG" ;;
|
||||
*) exit 1 ;;
|
||||
esac'
|
||||
write_stub sudo '"$@"'
|
||||
run_orphan_checker -y >"$test_tmp/approved.out" 2>"$test_tmp/approved.err"
|
||||
diff <(printf '%s\n' -Rns --noconfirm old-lib unused-tool) "$REMOVAL_LOG" ||
|
||||
fail "approved cleanup removes the orphan packages without pacman confirmation"
|
||||
pass "approved cleanup removes orphans without Omarchy or pacman confirmation"
|
||||
|
||||
write_stub sudo 'exit 42'
|
||||
if run_orphan_checker -y >"$test_tmp/failure.out" 2>"$test_tmp/failure.err"; then
|
||||
fail "approved cleanup reports removal failures"
|
||||
fi
|
||||
pass "approved cleanup reports removal failures"
|
||||
|
||||
rm -f "$REMOVAL_LOG"
|
||||
write_stub pacman 'if [[ $1 == "-Qtdq" ]]; then exit 0; fi; exit 1'
|
||||
run_orphan_checker >"$test_tmp/none.out" 2>"$test_tmp/none.err"
|
||||
[[ ! -s $test_tmp/none.out ]] || fail "orphan checker stays quiet when no orphans exist"
|
||||
for mode in "" -y; do
|
||||
run_orphan_checker $mode >"$test_tmp/none.out" 2>"$test_tmp/none.err"
|
||||
[[ ! -s $test_tmp/none.out ]] || fail "orphan checker stays quiet when no orphans exist"
|
||||
done
|
||||
[[ ! -e $REMOVAL_LOG ]] || fail "orphan checker does not remove packages when no orphans exist"
|
||||
pass "orphan checker stays quiet without orphans"
|
||||
@@ -35,6 +35,9 @@ for step in "${steps[@]}"; do
|
||||
cat >"$stub_bin/$step" <<'STUB'
|
||||
#!/bin/bash
|
||||
printf '%s unattended=%s\n' "${0##*/}" "${OMARCHY_UPDATE_UNATTENDED:-}" >>"$STEP_LOG"
|
||||
if [[ ${0##*/} == "omarchy-update-orphan-pkgs" ]]; then
|
||||
printf '%s\n' "$@" >"$STEP_LOG.orphan-args"
|
||||
fi
|
||||
[[ ${FAILING_STEP:-} != "${0##*/}" ]] || exit 1
|
||||
STUB
|
||||
chmod +x "$stub_bin/$step"
|
||||
@@ -84,6 +87,8 @@ run_update -y || fail "an update where everything works reports a failure"
|
||||
diff <(expected_steps) <(steps_run) >"$test_tmp/order" ||
|
||||
fail "an update where everything works does not run every step in order" "$(cat "$test_tmp/order")"
|
||||
pass "an update where every step works runs all of them, in order"
|
||||
[[ $(cat "$test_tmp/steps.orphan-args") == "-y" ]] || fail "an unattended update approves orphan removal"
|
||||
pass "an unattended update approves orphan removal"
|
||||
|
||||
grep -q '^omarchy-update-system-pkgs unattended=1$' "$test_tmp/steps" ||
|
||||
fail "-y does not mark the update unattended"
|
||||
@@ -93,6 +98,8 @@ diff <(expected_steps confirmed) <(steps_run) >"$test_tmp/order" ||
|
||||
grep -q '^omarchy-update-system-pkgs unattended=$' "$test_tmp/steps" ||
|
||||
fail "an update a person confirmed is treated as unattended"
|
||||
pass "-y is what marks an update unattended, not the update itself"
|
||||
[[ $(cat "$test_tmp/steps.orphan-args") == "-y" ]] || fail "a confirmed update approves orphan removal"
|
||||
pass "a confirmed update approves orphan removal"
|
||||
|
||||
# Migrations ship with the packages the upgrade installs and are written against
|
||||
# them. Running them against what is still on disk is the failure this ordering
|
||||
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source "$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)/base-test.sh"
|
||||
|
||||
leaf="$ROOT/install/hardware/dell-xps13-ptl-speaker-firmware.sh"
|
||||
migration="$ROOT/migrations/1791265613.sh"
|
||||
|
||||
grep -q 'run_logged .*hardware/dell-xps13-ptl-speaker-firmware.sh' "$ROOT/install/hardware/all.sh" ||
|
||||
fail "hardware setup installs the Panther Lake XPS 13 firmware"
|
||||
pass "hardware setup installs the Panther Lake XPS 13 firmware"
|
||||
|
||||
test_tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$test_tmp"' EXIT
|
||||
mkdir -p "$test_tmp/bin"
|
||||
|
||||
cat >"$test_tmp/bin/omarchy-hw-match" <<'SH'
|
||||
#!/bin/bash
|
||||
[[ $TEST_PRODUCT_NAME == *"$1"* ]]
|
||||
SH
|
||||
cat >"$test_tmp/bin/omarchy-hw-intel-ptl" <<'SH'
|
||||
#!/bin/bash
|
||||
[[ $TEST_INTEL_PTL == "1" ]]
|
||||
SH
|
||||
cat >"$test_tmp/bin/sudo" <<'SH'
|
||||
#!/bin/bash
|
||||
if [[ $1 == "pacman" ]]; then
|
||||
exec "$@"
|
||||
fi
|
||||
printf 'Unexpected privileged command: %s\n' "$*" >&2
|
||||
exit 1
|
||||
SH
|
||||
cat >"$test_tmp/bin/pacman" <<'SH'
|
||||
#!/bin/bash
|
||||
if [[ $1 == "-Q" ]]; then
|
||||
if [[ ${@: -1} == "linux-firmware-cirrus-dx13260" ]]; then
|
||||
[[ -e $TEST_ALIAS_FILE ]] || exit 1
|
||||
printf 'linux-firmware-cirrus-dx13260 20260810-1\n'
|
||||
else
|
||||
[[ -s $TEST_VERSION_FILE ]] || exit 1
|
||||
printf 'linux-firmware-cirrus %s\n' "$(<"$TEST_VERSION_FILE")"
|
||||
fi
|
||||
else
|
||||
printf 'pacman %s\n' "$*" >>"$TEST_LOG"
|
||||
[[ $* == "-S --noconfirm --needed -- linux-firmware-cirrus-dx13260" ]] || exit 1
|
||||
[[ ${TEST_INSTALL_FAILURE:-0} == "0" ]] || exit 1
|
||||
touch "$TEST_ALIAS_FILE"
|
||||
fi
|
||||
SH
|
||||
cat >"$test_tmp/bin/omarchy-state" <<'SH'
|
||||
#!/bin/bash
|
||||
printf 'state %s\n' "$*" >>"$TEST_LOG"
|
||||
SH
|
||||
chmod +x "$test_tmp/bin/"*
|
||||
|
||||
export PATH="$test_tmp/bin:$ROOT/bin:$PATH"
|
||||
export TEST_LOG="$test_tmp/calls" TEST_VERSION_FILE="$test_tmp/version" TEST_ALIAS_FILE="$test_tmp/alias-installed"
|
||||
export TEST_PRODUCT_NAME="XPS 13 DX13260" TEST_INTEL_PTL=1
|
||||
install_call="pacman -S --noconfirm --needed -- linux-firmware-cirrus-dx13260"
|
||||
export OMARCHY_PATH="$ROOT"
|
||||
|
||||
reset_fixture() {
|
||||
: >"$TEST_LOG"
|
||||
printf '%s' "$1" >"$TEST_VERSION_FILE"
|
||||
rm -f "$TEST_ALIAS_FILE"
|
||||
}
|
||||
|
||||
run_leaf() {
|
||||
bash -eE -c 'source "$1"' bash "$leaf"
|
||||
}
|
||||
|
||||
run_migration() {
|
||||
bash -euo pipefail "$migration" >/dev/null
|
||||
}
|
||||
|
||||
for model in "XPS 9350" "XPS 13 DX13261"; do
|
||||
reset_fixture "20260810-2"
|
||||
TEST_PRODUCT_NAME="$model" run_leaf
|
||||
TEST_PRODUCT_NAME="$model" run_migration
|
||||
[[ ! -s $TEST_LOG ]] || fail "other models receive no firmware repair"
|
||||
done
|
||||
reset_fixture "20260810-2"
|
||||
TEST_INTEL_PTL=0 run_leaf
|
||||
TEST_INTEL_PTL=0 run_migration
|
||||
[[ ! -s $TEST_LOG ]] || fail "the Wildcat Lake variant receives no firmware repair"
|
||||
pass "other models and the Wildcat Lake variant receive no firmware repair"
|
||||
|
||||
for version in "" "20260810-2" "20260810-3" "20260810-4" "20260910-2" "20260916-1" "1:20260810-2"; do
|
||||
reset_fixture "$version"
|
||||
run_leaf || fail "the alias package is installed during hardware setup"
|
||||
[[ $(<"$TEST_LOG") == "$install_call" && $(<"$TEST_VERSION_FILE") == "$version" && -e $TEST_ALIAS_FILE ]] ||
|
||||
fail "hardware setup installs the separate aliases without replacing stock firmware"
|
||||
done
|
||||
pass "hardware setup installs the package identity without replacing any stock firmware"
|
||||
pass "hardware setup leaves reboot bookkeeping to the migration"
|
||||
|
||||
reset_fixture "20260810-2"
|
||||
touch "$TEST_ALIAS_FILE"
|
||||
run_leaf
|
||||
[[ ! -s $TEST_LOG ]] || fail "installed aliases are idempotent on stock firmware"
|
||||
pass "installed aliases are idempotent on stock firmware"
|
||||
|
||||
run_migration
|
||||
[[ $(<"$TEST_LOG") == "state set reboot-required" ]] ||
|
||||
fail "the migration requests a reboot even if hardware setup already installed the aliases"
|
||||
pass "the migration requests a reboot even if hardware setup already installed the aliases"
|
||||
|
||||
reset_fixture "20260810-2"
|
||||
if TEST_INSTALL_FAILURE=1 run_migration; then
|
||||
fail "a failed firmware installation fails the migration"
|
||||
fi
|
||||
[[ $(<"$TEST_LOG") == "$install_call" && $(<"$TEST_VERSION_FILE") == "20260810-2" ]] ||
|
||||
fail "a failed installation leaves the migration retryable without claiming success"
|
||||
pass "a failed firmware installation fails the migration and remains retryable"
|
||||
|
||||
: >"$TEST_LOG"
|
||||
run_migration
|
||||
[[ $(<"$TEST_LOG") == "$install_call"$'\nstate set reboot-required' ]] ||
|
||||
fail "retrying the migration installs the firmware and requests a reboot"
|
||||
pass "retrying the migration installs the firmware and requests a reboot"
|
||||
|
||||
: >"$TEST_LOG"
|
||||
run_migration
|
||||
[[ $(<"$TEST_LOG") == "state set reboot-required" ]] ||
|
||||
fail "a second user before reboot is prompted without reinstalling"
|
||||
pass "a second user before reboot is prompted without reinstalling"
|
||||
Reference in new issue
Block a user