Something went wrong. Try again.
🏠 sweet ~/
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930#!/bin/bash## script/test## Functional tests for the scripts that have non-trivial logic:# stow-package (conflict parsing + backup moves), setup-git-config# (Git-native rendering, migration, and drift detection), and install-mise# (platform-aware mainline installation), and apply-macos-preferences# (platform gating, Dock/screenshot defaults, and LaunchAgent reloads). Each test runs# inside its own mktemp sandbox with a stubbed $HOME; nothing touches the real# system.## The runner does NOT use `set -e` globally so that a failing assertion# in one test reports and continues instead of aborting the whole suite.# Individual test functions set `set -e` locally so a bare `[ ... ]` or# command failure aborts just that test with a nonzero exit code, which# run_test translates into a FAIL.
set -uo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"cd "$REPO_ROOT"
if [ -t 1 ]; then RED='\033[0;31m' GREEN='\033[0;32m' NC='\033[0m'else RED='' GREEN='' NC=''fi
passed=0failed=0
run_test() { local name=$1 local fn=$2 local sandbox sandbox=$(mktemp -d) local output if output=$("$fn" "$sandbox" 2>&1); then printf " ${GREEN}PASS${NC} %s\n" "$name" passed=$((passed + 1)) else printf " ${RED}FAIL${NC} %s\n" "$name" if [ -n "$output" ]; then printf '%s\n' "$output" | sed 's/^/ /' fi failed=$((failed + 1)) fi rm -rf "$sandbox"}
# ------- stow-package -------## The sandbox layout mirrors the real repo just enough for stow-package's# `$(dirname "$0")/..` resolution: we copy the script into# $sandbox/repo/script/ and build a fake package next to it. Invoking# $sandbox/repo/script/stow-package makes REPO_ROOT resolve to# $sandbox/repo, so `pkg` is looked up inside the sandbox, not the real# dotfiles checkout.
make_stow_sandbox_repo() { local sandbox=$1 mkdir -p "$sandbox/repo/script" cp "$REPO_ROOT/script/stow-package" "$sandbox/repo/script/"}
test_stow_clean() { local sandbox=$1 set -e make_stow_sandbox_repo "$sandbox" mkdir -p "$sandbox/repo/pkg/.config" "$sandbox/home" echo "repo-content" > "$sandbox/repo/pkg/.config/foo.conf"
HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null
[ -L "$sandbox/home/.config/foo.conf" ] || { echo "expected symlink"; exit 1; } [ ! -d "$sandbox/home/.dotfiles-backup" ] || { echo "backup dir should not exist"; exit 1; }}
test_stow_real_file_conflict() { local sandbox=$1 set -e make_stow_sandbox_repo "$sandbox" mkdir -p "$sandbox/repo/pkg/.config" "$sandbox/home/.config" echo "repo-content" > "$sandbox/repo/pkg/.config/foo.conf" echo "original-home-content" > "$sandbox/home/.config/foo.conf"
HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null
[ -L "$sandbox/home/.config/foo.conf" ] || { echo "expected symlink after stow"; exit 1; }
local backup backup=$(find "$sandbox/home/.dotfiles-backup" -name foo.conf -print -quit 2>/dev/null) [ -n "$backup" ] || { echo "no backup file found"; exit 1; } [ "$(cat "$backup")" = "original-home-content" ] \ || { echo "backup contents not preserved"; exit 1; }}
test_stow_foreign_symlink_conflict() { local sandbox=$1 set -e make_stow_sandbox_repo "$sandbox" mkdir -p "$sandbox/repo/pkg/.config" "$sandbox/home/.config" "$sandbox/other" echo "repo-content" > "$sandbox/repo/pkg/.config/foo.conf" echo "foreign" > "$sandbox/other/foreign-target" ln -s "$sandbox/other/foreign-target" "$sandbox/home/.config/foo.conf"
HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null
[ -L "$sandbox/home/.config/foo.conf" ] || { echo "expected symlink"; exit 1; } local target target=$(readlink "$sandbox/home/.config/foo.conf") case "$target" in */repo/pkg/.config/foo.conf) ;; *) echo "symlink points to wrong target: $target"; exit 1 ;; esac find "$sandbox/home/.dotfiles-backup" -name foo.conf 2>/dev/null | grep -q . \ || { echo "foreign symlink not backed up"; exit 1; }}
test_stow_foreign_directory_symlink_conflict() { local sandbox=$1 set -e make_stow_sandbox_repo "$sandbox" mkdir -p "$sandbox/repo/pkg/.config/demo" \ "$sandbox/home" \ "$sandbox/other/config/demo" echo "repo-content" > "$sandbox/repo/pkg/.config/demo/foo.conf" echo "foreign-content" > "$sandbox/other/config/demo/foreign.conf" ln -s "$sandbox/other/config" "$sandbox/home/.config"
HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null
if ! { [ -d "$sandbox/home/.config" ] && [ ! -L "$sandbox/home/.config" ]; }; then echo "foreign directory symlink was not replaced"; exit 1 fi [ -L "$sandbox/home/.config/demo/foo.conf" ] \ || { echo "package file was not stowed"; exit 1; } [ -f "$sandbox/other/config/demo/foreign.conf" ] \ || { echo "foreign directory contents were changed"; exit 1; } find "$sandbox/home/.dotfiles-backup" -type l -name .config 2>/dev/null | grep -q . \ || { echo "foreign directory symlink not backed up"; exit 1; }}
test_stow_mixed_conflicts_and_clean() { local sandbox=$1 set -e make_stow_sandbox_repo "$sandbox" mkdir -p "$sandbox/repo/pkg/.config/nested" "$sandbox/home/.config/nested" echo "a" > "$sandbox/repo/pkg/.config/a.conf" echo "b" > "$sandbox/repo/pkg/.config/nested/b.conf" echo "c" > "$sandbox/repo/pkg/.config/nested/c.conf" echo "home-b" > "$sandbox/home/.config/nested/b.conf"
HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null
[ -L "$sandbox/home/.config/a.conf" ] || { echo "a not symlinked"; exit 1; } [ -L "$sandbox/home/.config/nested/b.conf" ] || { echo "b not symlinked"; exit 1; } [ -L "$sandbox/home/.config/nested/c.conf" ] || { echo "c not symlinked"; exit 1; }
find "$sandbox/home/.dotfiles-backup" -name b.conf 2>/dev/null | grep -q . \ || { echo "b.conf should be in backup"; exit 1; } if find "$sandbox/home/.dotfiles-backup" -name a.conf 2>/dev/null | grep -q .; then echo "a.conf should not be in backup"; exit 1 fi if find "$sandbox/home/.dotfiles-backup" -name c.conf 2>/dev/null | grep -q .; then echo "c.conf should not be in backup"; exit 1 fi}
test_stow_restow_is_idempotent() { local sandbox=$1 set -e make_stow_sandbox_repo "$sandbox" mkdir -p "$sandbox/repo/pkg/.config" "$sandbox/home" echo "repo-content" > "$sandbox/repo/pkg/.config/foo.conf"
HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null # Second run: the target is already the symlink we would create, so it # must not be treated as a conflict and moved into the backup dir. HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null
[ -L "$sandbox/home/.config/foo.conf" ] || { echo "expected symlink"; exit 1; } [ "$(cat "$sandbox/home/.config/foo.conf")" = "repo-content" ] \ || { echo "symlink no longer resolves to package content"; exit 1; } [ ! -d "$sandbox/home/.dotfiles-backup" ] \ || { echo "re-stow should not back anything up"; exit 1; }}
test_stow_restow_prunes_obsolete_link() { local sandbox=$1 set -e make_stow_sandbox_repo "$sandbox" mkdir -p "$sandbox/repo/pkg/.config" "$sandbox/home" echo "keep" > "$sandbox/repo/pkg/.config/keep.conf" echo "remove" > "$sandbox/repo/pkg/.config/remove.conf"
HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null rm "$sandbox/repo/pkg/.config/remove.conf" HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null
[ -L "$sandbox/home/.config/keep.conf" ] || { echo "current link missing"; exit 1; } if [ -e "$sandbox/home/.config/remove.conf" ] || [ -L "$sandbox/home/.config/remove.conf" ]; then echo "obsolete link was not pruned"; exit 1 fi}
test_stow_restow_unfolds_owned_directory() { local sandbox=$1 set -e make_stow_sandbox_repo "$sandbox" mkdir -p "$sandbox/repo/pkg/.config/demo" "$sandbox/home" echo "repo-content" > "$sandbox/repo/pkg/.config/demo/foo.conf"
( cd "$sandbox/repo" stow -t "$sandbox/home" pkg ) [ -L "$sandbox/home/.config" ] || { echo "fixture did not fold directory"; exit 1; }
HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null
if ! { [ -d "$sandbox/home/.config" ] && [ ! -L "$sandbox/home/.config" ]; }; then echo "owned directory symlink was not unfolded"; exit 1 fi [ -L "$sandbox/home/.config/demo/foo.conf" ] \ || { echo "package file was not restowed"; exit 1; } [ ! -d "$sandbox/home/.dotfiles-backup" ] \ || { echo "owned directory symlink should not be backed up"; exit 1; }}
test_stow_local_ignore_runtime_state() { local sandbox=$1 set -e make_stow_sandbox_repo "$sandbox" mkdir -p "$sandbox/repo/pkg/.config/nvim" "$sandbox/home/.config/nvim" cp "$REPO_ROOT/common/.stow-local-ignore" "$sandbox/repo/pkg/" echo "pkg/.config/nvim/lazy-lock.json" > "$sandbox/repo/.gitignore" ( cd "$sandbox/repo" git init -q ) echo "tracked" > "$sandbox/repo/pkg/.config/nvim/init.lua" echo "source-runtime" > "$sandbox/repo/pkg/.config/nvim/lazy-lock.json" echo "host-runtime" > "$sandbox/home/.config/nvim/lazy-lock.json"
HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null
[ -L "$sandbox/home/.config/nvim/init.lua" ] || { echo "tracked file not stowed"; exit 1; } [ ! -L "$sandbox/home/.config/nvim/lazy-lock.json" ] \ || { echo "runtime file should not be linked"; exit 1; } [ "$(cat "$sandbox/home/.config/nvim/lazy-lock.json")" = "host-runtime" ] \ || { echo "host runtime file was not preserved"; exit 1; } [ ! -d "$sandbox/home/.dotfiles-backup" ] \ || { echo "ignored runtime file should not be backed up"; exit 1; }}
test_stow_dangling_symlink_conflict() { local sandbox=$1 set -e make_stow_sandbox_repo "$sandbox" mkdir -p "$sandbox/repo/pkg/.config" "$sandbox/home/.config" echo "repo-content" > "$sandbox/repo/pkg/.config/foo.conf" # A link to a target that does not exist: `test -e` is false for this, # so it is only detectable via -L. ln -s "$sandbox/nowhere/gone.conf" "$sandbox/home/.config/foo.conf"
HOME="$sandbox/home" "$sandbox/repo/script/stow-package" pkg > /dev/null
[ "$(cat "$sandbox/home/.config/foo.conf")" = "repo-content" ] \ || { echo "dangling link was not replaced by the package symlink"; exit 1; } find "$sandbox/home/.dotfiles-backup" -name foo.conf 2>/dev/null | grep -q . \ || { echo "dangling symlink not backed up"; exit 1; }}
test_stow_missing_arg() { local sandbox=$1 if "$REPO_ROOT/script/stow-package" 2>/dev/null; then echo "expected nonzero exit for missing arg" exit 1 fi}
test_stow_nonexistent_package() { local sandbox=$1 set -e make_stow_sandbox_repo "$sandbox" if HOME="$sandbox/home" "$sandbox/repo/script/stow-package" nonexistent 2>/dev/null; then echo "expected nonzero exit for missing package" exit 1 fi}
# ------- setup-git-config -------## setup-git-config hardcodes its template path relative to $REPO_ROOT# (via $(dirname "$0")/..), so we invoke the real script from the real# repo but stub $HOME so the identity and rendered config live in the# sandbox. This tests the real template rendering end-to-end.
write_test_identity() { local home=$1 email=$2 key=$3 local identity="$home/.config/local/git/identity.gitconfig" mkdir -p "$(dirname "$identity")" git config --file "$identity" user.email "$email" git config --file "$identity" user.signingkey "$key"}
test_git_config_basic_render() { local sandbox=$1 set -e local key='ssh-ed25519 AAAATESTBASICKEY' write_test_identity "$sandbox/home" "test@example.com" "$key"
HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" > /dev/null
local config="$sandbox/home/.config/git/config" [ -f "$config" ] || { echo "config not rendered"; exit 1; } [ "$(git config --file "$config" --get user.email)" = "test@example.com" ] \ || { echo "email not injected"; exit 1; } [ "$(git config --file "$config" --get user.signingkey)" = "$key" ] \ || { echo "signing key not injected"; exit 1; }}
test_git_config_special_values() { local sandbox=$1 set -e local email='weird&chars|here\test@example.com' local key='ssh-ed25519 AAAATESTKEY weird&chars|comment\withbackslash' write_test_identity "$sandbox/home" "$email" "$key"
HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" > /dev/null
local config="$sandbox/home/.config/git/config" [ "$(git config --file "$config" --get user.email)" = "$email" ] \ || { echo "email value did not round trip"; exit 1; } [ "$(git config --file "$config" --get user.signingkey)" = "$key" ] \ || { echo "signing key value did not round trip"; exit 1; }}
test_git_config_codespaces_skip() { local sandbox=$1 set -e mkdir -p "$sandbox/home" CODESPACES=1 HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" > /dev/null [ -f "$sandbox/home/.config/git/config" ] \ || { echo "codespaces path should render config"; exit 1; } local config="$sandbox/home/.config/git/config" if git config --file "$config" --get-regexp '^user\.' >/dev/null; then echo "[user] section should be removed" exit 1 fi if git config --file "$config" --get-regexp '^include\.' >/dev/null; then echo "[include] section should be removed" exit 1 fi [ "$(git config --file "$config" --get commit.gpgsign)" = "false" ] \ || { echo "gpgsign should be false in codespaces mode"; exit 1; } git config --file "$config" --get alias.co >/dev/null \ || { echo "aliases should be present in codespaces config"; exit 1; }}
test_git_config_missing_email() { local sandbox=$1 set -e mkdir -p "$sandbox/home/.config/local/git" git config --file "$sandbox/home/.config/local/git/identity.gitconfig" \ user.signingkey "ssh-ed25519 AAAATESTKEY" if HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" 2>/dev/null; then echo "expected nonzero exit when user.email missing" exit 1 fi}
test_git_config_missing_signingkey() { local sandbox=$1 set -e mkdir -p "$sandbox/home/.config/local/git" git config --file "$sandbox/home/.config/local/git/identity.gitconfig" user.email x@y.z if HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" 2>/dev/null; then echo "expected nonzero exit when user.signingkey missing" exit 1 fi}
test_git_config_rejects_non_ssh_signingkey() { local sandbox=$1 set -e write_test_identity "$sandbox/home" "test@example.com" "ABCD1234DEADBEEF" if HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" 2>/dev/null; then echo "expected nonzero exit for a non-SSH signing key" exit 1 fi}
test_git_config_legacy_identity_migration() { local sandbox=$1 set -e mkdir -p "$sandbox/home/.config/dotfiles" cat > "$sandbox/home/.config/dotfiles/identity.env" <<'EOF'EMAIL=legacy@example.comSIGNINGKEY=ssh-ed25519 AAAALEGACYKEYEOF
HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" > /dev/null
local identity="$sandbox/home/.config/local/git/identity.gitconfig" [ -f "$identity" ] || { echo "legacy identity was not migrated"; exit 1; } [ "$(git config --file "$identity" --get user.email)" = "legacy@example.com" ] \ || { echo "migrated email is incorrect"; exit 1; } [ "$(git config --file "$identity" --get user.signingkey)" = "ssh-ed25519 AAAALEGACYKEY" ] \ || { echo "migrated signing key is incorrect"; exit 1; } [ -f "$sandbox/home/.config/local/git/identity.env.migrated" ] \ || { echo "legacy identity should be archived"; exit 1; } [ ! -e "$sandbox/home/.config/dotfiles/identity.env" ] \ || { echo "legacy identity should leave the old directory"; exit 1; }}
test_git_config_prior_git_identity_migration() { local sandbox=$1 set -e local old_identity="$sandbox/home/.config/dotfiles/identity.gitconfig" mkdir -p "$(dirname "$old_identity")" git config --file "$old_identity" user.email prior@example.com git config --file "$old_identity" user.signingkey "ssh-ed25519 AAAAPRIORKEY"
HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" > /dev/null
local identity="$sandbox/home/.config/local/git/identity.gitconfig" [ "$(git config --file "$identity" --get user.email)" = "prior@example.com" ] \ || { echo "prior Git identity email is incorrect"; exit 1; } [ "$(git config --file "$identity" --get user.signingkey)" = "ssh-ed25519 AAAAPRIORKEY" ] \ || { echo "prior Git identity signing key is incorrect"; exit 1; } [ ! -e "$old_identity" ] || { echo "prior Git identity should be moved"; exit 1; }}
test_git_config_check_detects_drift() { local sandbox=$1 set -e write_test_identity "$sandbox/home" "test@example.com" "ssh-ed25519 AAAATESTKEY" HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" > /dev/null HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" --check
git config --file "$sandbox/home/.config/git/config" alias.drift true if HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" --check; then echo "expected drift check to fail" exit 1 fi}
TEST_SSH_KEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAITESTKEY"
test_git_config_allowed_signers_render() { local sandbox=$1 set -e write_test_identity "$sandbox/home" "test@example.com" "$TEST_SSH_KEY"
HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" > /dev/null
local signers="$sandbox/home/.config/local/git/allowed_signers" [ -f "$signers" ] || { echo "allowed_signers not rendered"; exit 1; } [ "$(cat "$signers")" = "test@example.com $TEST_SSH_KEY" ] \ || { echo "allowed_signers entry incorrect"; exit 1; }}
test_git_config_allowed_signers_extra() { local sandbox=$1 set -e write_test_identity "$sandbox/home" "test@example.com" "$TEST_SSH_KEY" echo "teammate@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOTHER" \ > "$sandbox/home/.config/local/git/allowed_signers.extra"
HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" > /dev/null
local signers="$sandbox/home/.config/local/git/allowed_signers" grep -q '^test@example.com ' "$signers" \ || { echo "own identity missing from allowed_signers"; exit 1; } grep -q '^teammate@example.com ' "$signers" \ || { echo "extra signers not appended"; exit 1; }}
test_git_config_allowed_signers_codespaces() { local sandbox=$1 set -e mkdir -p "$sandbox/home"
CODESPACES=1 HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" > /dev/null
[ ! -e "$sandbox/home/.config/local/git/allowed_signers" ] \ || { echo "Codespaces should not render allowed_signers"; exit 1; }}
test_git_config_check_detects_signers_drift() { local sandbox=$1 set -e write_test_identity "$sandbox/home" "test@example.com" "$TEST_SSH_KEY" HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" > /dev/null HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" --check
echo "stranger@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAISTRANGER" \ >> "$sandbox/home/.config/local/git/allowed_signers" if HOME="$sandbox/home" "$REPO_ROOT/script/setup-git-config" --check; then echo "expected allowed_signers drift to fail the check" exit 1 fi}
# ------- install-mise -------
make_mise_test_path() { local sandbox=$1 os=$2 mkdir -p "$sandbox/bin" cat > "$sandbox/bin/uname" <<EOF#!/bin/shprintf '%s\n' '$os'EOF chmod +x "$sandbox/bin/uname" printf '%s:%s' "$sandbox/bin" "$PATH"}
write_fake_mise() { local path=$1 log=$2 marker=$3 mkdir -p "$(dirname "$path")" cat > "$path" <<EOF#!/bin/shprintf '%s\n' '$marker' >> '$log'printf '%s\n' "\$*" >> '$log'EOF chmod +x "$path"}
test_mise_installs_mainline_on_darwin() { local sandbox=$1 set -e local path log path=$(make_mise_test_path "$sandbox" Darwin) log="$sandbox/mise.log" cat > "$sandbox/bin/curl" <<EOF#!/bin/shcat <<'INSTALLER'mkdir -p "\$(dirname "\$MISE_INSTALL_PATH")"cat > "\$MISE_INSTALL_PATH" <<'MISE'#!/bin/shprintf '%s\n' installed >> '$log'printf '%s\n' "\$*" >> '$log'MISEchmod +x "\$MISE_INSTALL_PATH"INSTALLEREOF chmod +x "$sandbox/bin/curl"
HOME="$sandbox/home" PATH="$path" "$REPO_ROOT/script/install-mise"
[ -x "$sandbox/home/.local/bin/mise" ] \ || { echo "mainline mise binary was not installed"; exit 1; } [ "$(cat "$log")" = "$(printf 'installed\ntrust\ninstalled\ninstall')" ] \ || { echo "installed mise did not configure runtimes"; exit 1; }}
test_mise_skips_installer_when_mainline_exists() { local sandbox=$1 set -e local path log path=$(make_mise_test_path "$sandbox" Darwin) log="$sandbox/mise.log" write_fake_mise "$sandbox/home/.local/bin/mise" "$log" existing cat > "$sandbox/bin/curl" <<'EOF'#!/bin/shecho "curl should not run" >&2exit 99EOF chmod +x "$sandbox/bin/curl"
HOME="$sandbox/home" PATH="$path" "$REPO_ROOT/script/install-mise"
[ "$(cat "$log")" = "$(printf 'existing\ntrust\nexisting\ninstall')" ] \ || { echo "existing mainline mise was not used"; exit 1; }}
test_mise_errors_when_installer_produces_no_binary() { local sandbox=$1 set -e local path path=$(make_mise_test_path "$sandbox" Darwin) cat > "$sandbox/bin/curl" <<'EOF'#!/bin/shprintf '%s\n' 'exit 0'EOF chmod +x "$sandbox/bin/curl"
if HOME="$sandbox/home" PATH="$path" "$REPO_ROOT/script/install-mise" 2>/dev/null; then echo "expected failure when installer produced no executable" exit 1 fi}
test_mise_does_not_install_on_linux() { local sandbox=$1 set -e local path path=$(make_mise_test_path "$sandbox" Linux) cat > "$sandbox/bin/curl" <<'EOF'#!/bin/shecho "curl should not run" >&2exit 99EOF chmod +x "$sandbox/bin/curl"
HOME="$sandbox/home" PATH="$sandbox/bin" "$REPO_ROOT/script/install-mise" > "$sandbox/output"
[ ! -e "$sandbox/home/.local/bin/mise" ] \ || { echo "Linux bootstrap should not install mise"; exit 1; } grep -q "mise not on PATH; skipping" "$sandbox/output" \ || { echo "missing skip message"; exit 1; }}
# ------- apply-macos-preferences -------
make_macos_preferences_test_path() { local sandbox=$1 os=$2 print_status=$3 mkdir -p "$sandbox/bin" cat > "$sandbox/bin/uname" <<EOF#!/bin/shprintf '%s\n' '$os'EOF cat > "$sandbox/bin/id" <<'EOF'#!/bin/shprintf '%s\n' 501EOF cat > "$sandbox/bin/launchctl" <<EOF#!/bin/shif [ "\$1" = print ]; then exit $print_statusfiif [ "\$1" = list ]; then printf '%s\t0\tcom.apple.SystemUIServer.agent\n' "\${SYSTEM_UI_TEST_PID:--}" exit 0fiprintf '%s\n' "\$*" >> '$sandbox/launchctl.log'EOF cat > "$sandbox/bin/defaults" <<EOF#!/bin/shprintf '%s\n' "\$*" >> '$sandbox/defaults.log'EOF chmod +x "$sandbox/bin/uname" "$sandbox/bin/id" \ "$sandbox/bin/launchctl" "$sandbox/bin/defaults" printf '%s:%s' "$sandbox/bin" "$PATH"}
make_keyboard_launch_agent() { local home=$1 mkdir -p "$home/Library/LaunchAgents" touch "$home/Library/LaunchAgents/com.tarebyte.keyboard-remapping.plist"}
assert_macos_preferences_applied() { local sandbox=$1 expected expected=$(printf '%s\n%s\n%s\n%s' \ "write com.apple.dock wvous-br-corner -int 13" \ "write com.apple.dock wvous-br-modifier -int 0" \ "write com.apple.screencapture location -string $sandbox/home/Pictures/Screenshots" \ "write com.apple.screencapture location-screenshot -string $sandbox/home/Pictures/Screenshots") [ "$(cat "$sandbox/defaults.log")" = "$expected" ] \ || { echo "hot corner and screenshot preferences were not configured"; exit 1; } [ -d "$sandbox/home/Pictures/Screenshots" ] \ || { echo "screenshot directory was not created"; exit 1; }}
test_macos_preferences_bootstraps_agent() { local sandbox=$1 set -e local path path=$(make_macos_preferences_test_path "$sandbox" Darwin 1) make_keyboard_launch_agent "$sandbox/home"
HOME="$sandbox/home" PATH="$path" "$REPO_ROOT/script/apply-macos-preferences" > /dev/null
local expected expected=$(printf '%s\n%s' \ "bootstrap gui/501 $sandbox/home/Library/LaunchAgents/com.tarebyte.keyboard-remapping.plist" \ "kickstart -k gui/501/com.apple.Dock.agent") [ "$(cat "$sandbox/launchctl.log")" = "$expected" ] \ || { echo "LaunchAgent was not bootstrapped"; exit 1; } assert_macos_preferences_applied "$sandbox"}
test_macos_preferences_reloads_agent() { local sandbox=$1 set -e local path path=$(make_macos_preferences_test_path "$sandbox" Darwin 0) make_keyboard_launch_agent "$sandbox/home"
HOME="$sandbox/home" PATH="$path" "$REPO_ROOT/script/apply-macos-preferences" > /dev/null
local expected expected=$(printf '%s\n%s\n%s' \ "bootout gui/501/com.tarebyte.keyboard-remapping" \ "bootstrap gui/501 $sandbox/home/Library/LaunchAgents/com.tarebyte.keyboard-remapping.plist" \ "kickstart -k gui/501/com.apple.Dock.agent") [ "$(cat "$sandbox/launchctl.log")" = "$expected" ] \ || { echo "loaded LaunchAgent was not reloaded"; exit 1; } assert_macos_preferences_applied "$sandbox"}
test_macos_preferences_requires_agent() { local sandbox=$1 set -e local path path=$(make_macos_preferences_test_path "$sandbox" Darwin 1)
if HOME="$sandbox/home" PATH="$path" \ "$REPO_ROOT/script/apply-macos-preferences" 2>/dev/null; then echo "expected failure when LaunchAgent is missing" exit 1 fi [ ! -e "$sandbox/defaults.log" ] \ || { echo "hot corner should not change without the LaunchAgent"; exit 1; } [ ! -e "$sandbox/launchctl.log" ] \ || { echo "launchctl should not run without the LaunchAgent"; exit 1; }}
test_macos_preferences_preserves_screenshots() { local sandbox=$1 set -e local path path=$(make_macos_preferences_test_path "$sandbox" Darwin 0) make_keyboard_launch_agent "$sandbox/home" mkdir -p "$sandbox/home/Pictures/Screenshots" printf '%s\n' existing > "$sandbox/home/Pictures/Screenshots/example.png"
HOME="$sandbox/home" PATH="$path" "$REPO_ROOT/script/apply-macos-preferences" > /dev/null HOME="$sandbox/home" PATH="$path" "$REPO_ROOT/script/apply-macos-preferences" > /dev/null
[ "$(cat "$sandbox/home/Pictures/Screenshots/example.png")" = existing ] \ || { echo "existing screenshot was changed"; exit 1; }}
test_macos_preferences_refreshes_screenshots() { local sandbox=$1 set -e local path pid path=$(make_macos_preferences_test_path "$sandbox" Darwin 0) make_keyboard_launch_agent "$sandbox/home" sleep 30 & pid=$! trap 'kill "$pid" 2>/dev/null || true' EXIT
SYSTEM_UI_TEST_PID="$pid" HOME="$sandbox/home" PATH="$path" \ "$REPO_ROOT/script/apply-macos-preferences" > /dev/null
if wait "$pid" 2>/dev/null; then echo "screenshot preference process was not restarted" exit 1 fi trap - EXIT}
test_macos_preferences_skips_linux() { local sandbox=$1 set -e local path path=$(make_macos_preferences_test_path "$sandbox" Linux 1)
HOME="$sandbox/home" PATH="$path" \ "$REPO_ROOT/script/apply-macos-preferences" > "$sandbox/output"
[ ! -e "$sandbox/launchctl.log" ] \ || { echo "launchctl should not run on Linux"; exit 1; } [ ! -e "$sandbox/defaults.log" ] \ || { echo "defaults should not run on Linux"; exit 1; } [ ! -e "$sandbox/home/Pictures/Screenshots" ] \ || { echo "screenshot directory should not be created on Linux"; exit 1; } grep -q "not macOS; skipping preferences" "$sandbox/output" \ || { echo "missing non-macOS skip message"; exit 1; }}
# ------- tool_ready -------## tool_ready lives in install-codespace-tools. With a version-flag probe it# must return true only when a tool is both on PATH and actually executes —# a binary present but broken (e.g. glibc mismatch, exits nonzero) must be# treated as not runnable so the installer rebuilds it. With the "none"# probe it checks presence only (tools without a version flag).
extract_tool_ready_fn() { local sandbox=$1 sed -n '/^tool_ready()/,/^}/p' "$REPO_ROOT/script/install-codespace-tools" \ > "$sandbox/tool_ready_fn.sh"}
test_tool_ready_working() { local sandbox=$1 set -e extract_tool_ready_fn "$sandbox" mkdir -p "$sandbox/bin" cat > "$sandbox/bin/faketool" <<'STUB'#!/bin/bashecho "faketool 1.2.3"STUB chmod +x "$sandbox/bin/faketool" # shellcheck disable=SC1091,SC2030,SC2031 ( PATH="$sandbox/bin:$PATH"; source "$sandbox/tool_ready_fn.sh"; tool_ready faketool --version )}
test_tool_ready_broken() { local sandbox=$1 set -e extract_tool_ready_fn "$sandbox" mkdir -p "$sandbox/bin" # Present on PATH but crashes at runtime, like a glibc mismatch. cat > "$sandbox/bin/faketool" <<'STUB'#!/bin/bashecho "faketool: version \`GLIBC_2.35' not found" >&2exit 1STUB chmod +x "$sandbox/bin/faketool" # shellcheck disable=SC1091,SC2030,SC2031 if ( PATH="$sandbox/bin:$PATH"; source "$sandbox/tool_ready_fn.sh"; tool_ready faketool --version ); then echo "a broken binary should not be reported as ready" exit 1 fi}
test_tool_ready_absent() { local sandbox=$1 set -e extract_tool_ready_fn "$sandbox" # Empty PATH dir: the tool is not present at all. mkdir -p "$sandbox/bin" # shellcheck disable=SC1091,SC2030,SC2031 if ( PATH="$sandbox/bin"; source "$sandbox/tool_ready_fn.sh"; tool_ready faketool --version ); then echo "an absent tool should not be reported as ready" exit 1 fi}
test_tool_ready_none_present() { local sandbox=$1 set -e extract_tool_ready_fn "$sandbox" mkdir -p "$sandbox/bin" # A tool with no version flag: "none" probe checks presence only, so a # binary that errors on --version is still considered ready. cat > "$sandbox/bin/faketool" <<'STUB'#!/bin/bashecho "usage: faketool ..." >&2exit 1STUB chmod +x "$sandbox/bin/faketool" # shellcheck disable=SC1091,SC2030,SC2031 ( PATH="$sandbox/bin:$PATH"; source "$sandbox/tool_ready_fn.sh"; tool_ready faketool none )}
test_tool_ready_none_absent() { local sandbox=$1 set -e extract_tool_ready_fn "$sandbox" mkdir -p "$sandbox/bin" # shellcheck disable=SC1091,SC2030,SC2031 if ( PATH="$sandbox/bin"; source "$sandbox/tool_ready_fn.sh"; tool_ready faketool none ); then echo "an absent tool should not be reported as ready (none probe)" exit 1 fi}
# ------- runner -------
echo "=== stow-package ==="run_test "clean stow, no conflicts" test_stow_cleanrun_test "real file conflict" test_stow_real_file_conflictrun_test "foreign symlink conflict" test_stow_foreign_symlink_conflictrun_test "foreign directory symlink" test_stow_foreign_directory_symlink_conflictrun_test "mixed conflicts and clean files" test_stow_mixed_conflicts_and_cleanrun_test "re-stow is idempotent" test_stow_restow_is_idempotentrun_test "re-stow prunes obsolete link" test_stow_restow_prunes_obsolete_linkrun_test "re-stow unfolds owned directory" test_stow_restow_unfolds_owned_directoryrun_test "runtime state is ignored" test_stow_local_ignore_runtime_staterun_test "dangling symlink conflict" test_stow_dangling_symlink_conflictrun_test "missing arg errors" test_stow_missing_argrun_test "nonexistent package errors" test_stow_nonexistent_package
echoecho "=== setup-git-config ==="run_test "basic template render" test_git_config_basic_renderrun_test "special values round trip" test_git_config_special_valuesrun_test "codespaces render" test_git_config_codespaces_skiprun_test "missing user.email errors" test_git_config_missing_emailrun_test "missing signing key errors" test_git_config_missing_signingkeyrun_test "non-SSH signing key errors" test_git_config_rejects_non_ssh_signingkeyrun_test "legacy identity migration" test_git_config_legacy_identity_migrationrun_test "prior Git identity migration" test_git_config_prior_git_identity_migrationrun_test "drift check" test_git_config_check_detects_driftrun_test "allowed_signers render" test_git_config_allowed_signers_renderrun_test "allowed_signers extra appended" test_git_config_allowed_signers_extrarun_test "allowed_signers skipped in cs" test_git_config_allowed_signers_codespacesrun_test "allowed_signers drift check" test_git_config_check_detects_signers_drift
echoecho "=== install-mise ==="run_test "installs mainline on Darwin" test_mise_installs_mainline_on_darwinrun_test "skips existing mainline binary" test_mise_skips_installer_when_mainline_existsrun_test "errors without installed binary" test_mise_errors_when_installer_produces_no_binaryrun_test "does not install on Linux" test_mise_does_not_install_on_linux
echoecho "=== apply-macos-preferences ==="run_test "applies preferences and agent" test_macos_preferences_bootstraps_agentrun_test "applies preferences and reloads" test_macos_preferences_reloads_agentrun_test "requires stowed keyboard agent" test_macos_preferences_requires_agentrun_test "preserves screenshots on reruns" test_macos_preferences_preserves_screenshotsrun_test "refreshes screenshot preferences" test_macos_preferences_refreshes_screenshotsrun_test "does not apply on Linux" test_macos_preferences_skips_linux
echoecho "=== tool_ready ==="run_test "working tool ready" test_tool_ready_workingrun_test "broken tool (glibc) not ready" test_tool_ready_brokenrun_test "absent tool not ready" test_tool_ready_absentrun_test "none probe: present is ready" test_tool_ready_none_presentrun_test "none probe: absent not ready" test_tool_ready_none_absent
echoprintf "${GREEN}passed: %d${NC} ${RED}failed: %d${NC}\n" "$passed" "$failed"[ "$failed" -eq 0 ]