The first real `deploy.sh` run on blankpad printed:
SKIP live-sync.service (ExecStart '%h/.omp/scripts/live-sync.sh' absent on this host)
while `/home/blank/.omp/scripts/live-sync.sh` sits right there. `%h` is expanded by systemd,
never by the shell, so the literal string was handed to `command -v`, failed, and the unit was
declared to belong to another machine — through the same code path whose whole purpose is to
keep a *foreign* unit inert, and silently. The victim was this repo's own heartbeat, and the
verdict was the same on every box that ever deployed it, because `%h` is exactly where the
checkout lives.
Nothing broke tonight only by luck of install state: `live-sync.service` is `linked`, driven
by its timer, so the heartbeat kept running while the deploy lied about it. A unit whose only
install route was this loop would simply never have been enabled anywhere.
`%h`, `%u` and `%U` are now expanded before resolution; an unrecognised specifier is left
alone so it fails loudly rather than being guessed at. Pinned by a test that writes a
`%h/.local/bin/helper` unit into the scratch home — red before (`SKIP`), green after
(`already linked; not started`), which also keeps the #45 rule intact: resolving a unit is not
licence to start it. Refs #44.
Verification: `bats tests/` 71/71, `kb-lint.sh omp-docs` 0 violations, `kb-coverage.py` 0
issues, `bash -n` clean.
155 lines
6.3 KiB
Bash
Executable File
155 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env bats
|
|
# tests/deploy.bats — black-box tests for scripts/deploy.sh (Refs #42)
|
|
#
|
|
# Contract (see script header): the repo checkout IS the deployment; sync adopts
|
|
# identicals, links new files, quarantines-and-replaces divergents, and stows the
|
|
# zz-$(hostname) package last. One thing it must NEVER do is deploy generated theme
|
|
# output: noctalia rewrites `noctalia.*` / `themes/noctalia*` in place, so a tracked
|
|
# copy is a stale snapshot, and a sync would revert a live palette to whatever colour
|
|
# scheme was current on the day it was committed.
|
|
#
|
|
# Nothing is observed except exit status, stdout, the file/symlink shape under $HOME,
|
|
# and the quarantine directory.
|
|
|
|
setup() {
|
|
TEST_TMP="$(mktemp -d)"
|
|
REPO="$TEST_TMP/repo"
|
|
DEPLOY="$BATS_TEST_DIRNAME/../scripts/deploy.sh"
|
|
mkdir -p "$REPO/scripts" "$REPO/dotfiles"
|
|
# A git repo, because the deployment checkout IS one and deploy.sh arms hooksPath.
|
|
git init -q -b main "$REPO"
|
|
cp "$DEPLOY" "$REPO/scripts/deploy.sh"
|
|
chmod +x "$REPO/scripts/deploy.sh"
|
|
export HOME="$TEST_TMP/home"
|
|
mkdir -p "$HOME"
|
|
export DEPLOY_REPO="$REPO" DEPLOY_HOST=box
|
|
|
|
# systemctl stub: records every call and answers is-enabled from a file. The real
|
|
# systemctl must never run here — on this box it would start a screen lock.
|
|
mkdir -p "$TEST_TMP/bin"
|
|
export STUB_LOG="$TEST_TMP/systemctl.log" UNIT_STATE="$TEST_TMP/unit-state"
|
|
: > "$STUB_LOG"
|
|
cat > "$TEST_TMP/bin/systemctl" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
echo "systemctl $*" >> "$STUB_LOG"
|
|
if [ "$2" = is-enabled ]; then
|
|
state="$(cat "$UNIT_STATE" 2>/dev/null || echo disabled)"
|
|
printf '%s\n' "$state"
|
|
case "$state" in disabled | "") exit 1 ;; *) exit 0 ;; esac
|
|
fi
|
|
exit 0
|
|
STUB
|
|
chmod +x "$TEST_TMP/bin/systemctl"
|
|
export PATH="$TEST_TMP/bin:$PATH"
|
|
}
|
|
|
|
teardown() { rm -rf "$TEST_TMP"; }
|
|
|
|
# repo_file <pkg-relative path> <content>
|
|
repo_file() { mkdir -p "$(dirname "$REPO/dotfiles/$1")"; printf '%s\n' "$2" > "$REPO/dotfiles/$1"; }
|
|
# live_file <absolute path under $HOME> <content>
|
|
live_file() { mkdir -p "$(dirname "$1")"; printf '%s\n' "$2" > "$1"; }
|
|
|
|
# systemd_unit <name> <ExecStart> — a unit in the systemd-user package
|
|
systemd_unit() {
|
|
mkdir -p "$REPO/dotfiles/systemd-user/.config/systemd/user"
|
|
printf '[Unit]\nDescription=%s\n[Service]\nExecStart=%s\n[Install]\nWantedBy=default.target\n' "$1" "$2" \
|
|
> "$REPO/dotfiles/systemd-user/.config/systemd/user/$1"
|
|
}
|
|
|
|
@test "generated theme output is never adopted into a symlink" {
|
|
repo_file "ghostty/.config/ghostty/themes/noctalia" 'background = "#f43841"'
|
|
live_file "$HOME/.config/ghostty/themes/noctalia" 'background = "#f43841"'
|
|
run "$REPO/scripts/deploy.sh"
|
|
[ "$status" -eq 0 ]
|
|
[[ "$output" == *"SKIP"*".config/ghostty/themes/noctalia"* ]]
|
|
[ -f "$HOME/.config/ghostty/themes/noctalia" ]
|
|
[ ! -L "$HOME/.config/ghostty/themes/noctalia" ]
|
|
}
|
|
|
|
@test "a diverged live palette is never quarantined and reverted to a stale copy" {
|
|
# The dangerous shape: the tracked file matches today's palette, the theme changes
|
|
# tomorrow, and sync would quarantine the live file and restore the stale one.
|
|
repo_file "niri/.config/niri/noctalia.kdl" 'active-color "#e01b24"'
|
|
live_file "$HOME/.config/niri/noctalia.kdl" 'active-color "#ff8c00"'
|
|
run "$REPO/scripts/deploy.sh"
|
|
[ "$status" -eq 0 ]
|
|
grep -q 'active-color "#ff8c00"' "$HOME/.config/niri/noctalia.kdl"
|
|
[ ! -L "$HOME/.config/niri/noctalia.kdl" ]
|
|
[ ! -d "$REPO/.quarantine" ]
|
|
}
|
|
|
|
@test "an ordinary identical config is still adopted" {
|
|
# Control against an exclusion so broad the tool stops deploying anything.
|
|
repo_file "ghostty/.config/ghostty/config.ghostty" 'theme = "gruber"'
|
|
live_file "$HOME/.config/ghostty/config.ghostty" 'theme = "gruber"'
|
|
run "$REPO/scripts/deploy.sh"
|
|
[ "$status" -eq 0 ]
|
|
[ -L "$HOME/.config/ghostty/config.ghostty" ]
|
|
[ "$(realpath "$HOME/.config/ghostty/config.ghostty")" = "$(realpath "$REPO/dotfiles/ghostty/.config/ghostty/config.ghostty")" ]
|
|
}
|
|
|
|
@test "--check reports and writes nothing" {
|
|
repo_file "ghostty/.config/ghostty/config.ghostty" 'theme = "gruber"'
|
|
live_file "$HOME/.config/ghostty/config.ghostty" 'theme = "tokyo-night"'
|
|
run "$REPO/scripts/deploy.sh" --check
|
|
[ "$status" -eq 0 ]
|
|
[[ "$output" == *"DRIFT"* || "$output" == *"QUAR"* ]]
|
|
[ ! -L "$HOME/.config/ghostty/config.ghostty" ]
|
|
grep -q 'theme = "tokyo-night"' "$HOME/.config/ghostty/config.ghostty"
|
|
[ ! -d "$REPO/.quarantine" ]
|
|
}
|
|
|
|
@test "a unit already enabled is enabled but not started" {
|
|
systemd_unit lock.service /bin/sh
|
|
printf 'enabled\n' > "$UNIT_STATE"
|
|
run "$REPO/scripts/deploy.sh"
|
|
[ "$status" -eq 0 ]
|
|
[[ "$output" == *"not started"* ]]
|
|
grep -q 'systemctl --user enable lock.service' "$STUB_LOG"
|
|
! grep -q -- '--now' "$STUB_LOG"
|
|
}
|
|
|
|
@test "a linked unit is not started either" {
|
|
# `is-enabled` exits 1 for a linked unit, exactly as it does for a disabled one, so the
|
|
# printed state is the signal — exit status cannot tell them apart.
|
|
systemd_unit timer-driven.service /bin/sh
|
|
printf 'linked\n' > "$UNIT_STATE"
|
|
run "$REPO/scripts/deploy.sh"
|
|
[ "$status" -eq 0 ]
|
|
! grep -q -- '--now' "$STUB_LOG"
|
|
}
|
|
|
|
@test "a disabled unit is enabled and started, so a fresh box still comes up" {
|
|
systemd_unit fresh.service /bin/sh
|
|
printf 'disabled\n' > "$UNIT_STATE"
|
|
run "$REPO/scripts/deploy.sh"
|
|
[ "$status" -eq 0 ]
|
|
grep -q 'systemctl --user enable --now fresh.service' "$STUB_LOG"
|
|
}
|
|
|
|
@test "a unit whose ExecStart is absent here stays inert" {
|
|
systemd_unit otherbox.service /nonexistent/daemon
|
|
printf 'disabled\n' > "$UNIT_STATE"
|
|
run "$REPO/scripts/deploy.sh"
|
|
[ "$status" -eq 0 ]
|
|
[[ "$output" == *"SKIP"*otherbox.service* ]]
|
|
! grep -q enable "$STUB_LOG"
|
|
}
|
|
|
|
@test "a unit whose ExecStart uses a systemd specifier still resolves here" {
|
|
# `%h` is expanded by systemd, not by the shell, so matching the literal string makes a
|
|
# unit belonging to THIS box look foreign and skip it in silence. The victim is this repo's
|
|
# own heartbeat: `ExecStart=%h/.omp/scripts/live-sync.sh`, skipped on every box. Refs #44.
|
|
mkdir -p "$HOME/.local/bin"
|
|
printf '#!/bin/sh\ntrue\n' > "$HOME/.local/bin/helper"
|
|
chmod +x "$HOME/.local/bin/helper"
|
|
systemd_unit heartbeat.service '%h/.local/bin/helper'
|
|
printf 'linked\n' > "$UNIT_STATE"
|
|
run "$REPO/scripts/deploy.sh"
|
|
[ "$status" -eq 0 ]
|
|
[[ "$output" == *"heartbeat.service (already linked"* ]]
|
|
grep -q 'systemctl --user enable heartbeat.service' "$STUB_LOG"
|
|
! grep -q -- '--now' "$STUB_LOG"
|
|
}
|