#!/bin/bash # # Copyright (C) 2026 Danilo M. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as # published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # The one runnable check for userctl. busctl, kwriteconfig6, git and gpg are # stubs on PATH that log their arguments, so nothing here reaches the real # AccountsService, emaildefaults or git config. jq is the real one. # # Usage: ./test-userctl.sh (exit 0 = all passed) set -u here="$(cd "$(dirname "$0")" && pwd)" ctl="$here/userctl" tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT mkdir "$tmp/bin" cat > "$tmp/bin/stub" <<'EOF' #!/bin/bash n="${0##*/}" { printf '%s' "$n"; printf ' [%s]' "$@"; echo; } >> "$LOG" [[ "${FAIL:-}" == "$n" ]] && { echo boom >&2; exit 1; } case "$n" in busctl) [[ " $* " == *" get-property "* ]] && printf '{"type":"s","data":"%s"}\n' "${@: -1}" ;; git) [[ " $* " == *" --get "* ]] && { [[ -n "${GIT_VALUE:-}" ]] && echo "$GIT_VALUE" || exit 1; } ;; gpg) cat "$GPG_FIXTURE" ;; magick) : > "${@: -1}" ;; esac exit 0 EOF chmod +x "$tmp/bin/stub" for n in busctl kwriteconfig6 git gpg magick; do ln -s stub "$tmp/bin/$n"; done export PATH="$tmp/bin:$PATH" HOME="$tmp" LOG="$tmp/log" obj="/org/freedesktop/Accounts/User$(id -u)" acc="busctl [--system] [call] [--] [org.freedesktop.Accounts] [$obj] [org.freedesktop.Accounts.User]" pass=0 fail=0 check() { local label="$1" want="$2" got="$3" if [[ "$want" == "$got" ]]; then printf 'ok %s\n' "$label"; pass=$((pass + 1)) else printf 'FAIL %s:\n want %q\n got %q\n' "$label" "$want" "$got"; fail=$((fail + 1)) fi } reset() { : > "$LOG"; unset FAIL GIT_VALUE; } # get: one line per field, AccountsService through --json, the key from git. reset export GIT_VALUE=ABCDEF check "get" "name=RealName email=Email avatar=IconFile language=Language location=Location signingkey=ABCDEF" "$("$ctl" get)" # The desktop's avatar file, once it exists, wins over AccountsService's. : > "$HOME/.user-icon.jpg" check "get prefers ~/.user-icon.jpg" "avatar=$HOME/.user-icon.jpg" "$("$ctl" get | grep '^avatar=')" rm "$HOME/.user-icon.jpg" # keys: the primary fingerprint and first uid of each secret key; subkey # fingerprints and later uids are skipped. reset cat > "$tmp/gpg.txt" <<'EOF' sec:u:255:22:AAAA1111:1700000000:::u:::scESC:::+:::23::0: fpr:::::::::1111222233334444555566667777AAAA1111: grp:::::::::0000: uid:r::::1700000000::HASH::Old Name ::::::::::0: uid:u::::1700000000::HASH::Jane Doèr ::::::::::0: uid:u::::1700000000::HASH::Jane D. ::::::::::0: ssb:u:255:18:SUB1:1700000000::::::e:::+:::23: fpr:::::::::9999888877776666555544443333SUB1: sec:r:255:22:DEAD0000:1700000000:::u:::scESC:::#:::23::0: fpr:::::::::DEAD0000DEAD0000DEAD0000DEAD00000000: uid:r::::1700000000::HASH::Revoked Key ::::::::::0: sec:u:255:22:BBBB2222:1700000000:::u:::scESC:::#:::23::0: fpr:::::::::AAAABBBBCCCCDDDDEEEEFFFF0000BBBB2222: uid:u::::1700000000::HASH::Work Key ::::::::::0: EOF export GPG_FIXTURE="$tmp/gpg.txt" check "keys" "1111222233334444555566667777AAAA1111 Jane Doèr AAAABBBBCCCCDDDDEEEEFFFF0000BBBB2222 Work Key " "$("$ctl" keys)" # set name: AccountsService, emaildefaults, git with the surname shortened. reset out="$("$ctl" set name "Jane Doèr")" check "set name prints nothing" "" "$out" check "set name writes" "$acc [SetRealName] [s] [Jane Doèr] kwriteconfig6 [--file] [emaildefaults] [--group] [PROFILE_Default] [--key] [FullName] [--] [Jane Doèr] git [config] [--global] [user.name] [Jane D.]" "$(cat "$LOG")" gitname() { reset; "$ctl" set name "$1" >/dev/null; grep '^git' "$LOG"; } check "three words" "git [config] [--global] [user.name] [Anna Maria R.]" "$(gitname "Anna Maria Rossi")" check "one word" "git [config] [--global] [user.name] [Cher]" "$(gitname "Cher")" check "accented initial" "git [config] [--global] [user.name] [Ève À.]" "$(gitname "Ève Àlba")" check "extra spaces" "git [config] [--global] [user.name] [Jane D.]" "$(gitname " Jane Doèr ")" # set email: the same three stores. reset "$ctl" set email jane@example.org >/dev/null check "set email writes" "$acc [SetEmail] [s] [jane@example.org] kwriteconfig6 [--file] [emaildefaults] [--group] [PROFILE_Default] [--key] [EmailAddress] [--] [jane@example.org] git [config] [--global] [user.email] [jane@example.org]" "$(cat "$LOG")" # AccountsService only. for pair in language:SetLanguage location:SetLocation; do reset "$ctl" set "${pair%%:*}" v >/dev/null check "set ${pair%%:*} writes" "$acc [${pair#*:}] [s] [v]" "$(cat "$LOG")" done # avatar: converted to ~/.user-icon.jpg through a temp file, then handed to # AccountsService, so the greeter, hyprlock and the login screen agree. reset "$ctl" set avatar /pics/me.png >/dev/null check "set avatar writes" "magick [/pics/me.png] [-auto-orient] [-strip] [$HOME/.user-icon.jpg.tmp.jpg] $acc [SetIconFile] [s] [$HOME/.user-icon.jpg]" "$(cat "$LOG")" check "set avatar leaves the file" "yes" "$([[ -e "$HOME/.user-icon.jpg" && ! -e "$HOME/.user-icon.jpg.tmp.jpg" ]] && echo yes)" rm -f "$HOME/.user-icon.jpg" # signingkey: a fingerprint is written; empty unsets, but only if one is set. reset "$ctl" set signingkey ABCD >/dev/null check "set signingkey" "git [config] [--global] [user.signingkey] [ABCD]" "$(cat "$LOG")" reset export GIT_VALUE=OLD "$ctl" set signingkey "" >/dev/null check "unset signingkey" "git [config] [--global] [--get] [user.signingkey] git [config] [--global] [--unset-all] [user.signingkey]" "$(cat "$LOG")" reset "$ctl" set signingkey "" >/dev/null check "unset when none is set" "git [config] [--global] [--get] [user.signingkey]" "$(cat "$LOG")" # A leading dash is a value, not an option. reset "$ctl" set location -x >/dev/null check "dash value" "$acc [SetLocation] [s] [-x]" "$(cat "$LOG")" # A failing read: one error line, exit 1, no key=value lines. reset export FAIL=busctl out="$("$ctl" get)"; rc=$? check "get failure exit" "1" "$rc" check "get failure output" "error=boom" "$out" # A failing write stops there, says why on stdout, and exits 1. reset export FAIL=busctl out="$("$ctl" set name "Jane Doèr")"; rc=$? check "failure exit" "1" "$rc" check "failure message" "busctl: boom" "$out" check "failure stops" "1" "$(wc -l < "$LOG" | tr -d ' ')" # An unknown field is refused. reset "$ctl" set shoesize 42 >/dev/null 2>&1; rc=$? check "unknown field" "2" "$rc" printf '\n%d passed, %d failed\n' "$pass" "$fail" [[ $fail -eq 0 ]]