Wren Linux

wren-linux / tools / test-claude.sh

#!/bin/bash
# Checks the claude command (bin/claude): which Claude Code it starts, what it asks, and how the
# claude command is taken away with the last way to start it (the sandboxed and as-you ways are
# retired, but a computer that still has one is offered it). Run by test.sh, or:
#   bash tools/test-claude.sh
# shellcheck disable=SC2016  # the inner shells expand their own variables
HERE=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)

T=$(mktemp -d) || { echo "  FAIL  couldn't make a temp folder (is /tmp full?)"; exit 1; }
trap 'rm -rf "$T"' EXIT
fails=0
check() { # DESCRIPTION CONDITION-COMMAND...
    local what=$1
    shift
    if "$@" >/dev/null 2>&1; then printf '  ok    %s\n' "$what"; else printf '  FAIL  %s\n' "$what"; fails=$((fails + 1)); fi
}

mkdir -p "$T/fake" "$T/home/projects/app" "$T/home/projects-work/app" "$T/home/other"
# a pretend sudo: "-l" says whether the rule for the user after -u exists
# ($T/rule-USER), else it writes down the call
cat >"$T/fake/sudo" <<EOF
#!/bin/sh
case "\$*" in *" -l "*) [ -e "$T/rule-\$3" ] ;; *) echo "sudo \$*" >>"$T/calls" ;; esac
EOF
chmod +x "$T/fake/sudo"
export PATH="$T/fake:$PATH" HOME="$T/home"

# ---- the claude command: what is set up, which is asked, Enter takes the most walled off
C=$T/ch
mkdir -p "$C/home/.local/bin" "$C/home/projects/app" "$C/home/projects-work" "$C/elsewhere"
for c in claude-vm claude-sandbox claude-regular; do
    printf '#!/bin/sh\necho "%s $* in $(pwd)" >"%s/ran"\n' "$c" "$C" >"$C/$c"
    chmod +x "$C/$c"
done
: >"$C/passwd"
have() { rm -f "$C/home/.local/bin/"claude-*; for c; do cp "$C/$c" "$C/home/.local/bin/$c"; done; }
# ch ANSWER ARGS...: in a terminal (script gives one), answering ANSWER (no-terminal: none)
ch() {
    local a=$1; shift; rm -f "$C/ran"
    if [ "$a" = no-terminal ]; then
        (cd "$C/elsewhere" && env -u WAYLAND_DISPLAY HOME="$C/home" WREN_PASSWD="$C/passwd" bash "$HERE/bin/claude" "$@" </dev/null >"$C/out" 2>&1)
    else
        printf '%s\n' "$a" | (cd "$C/elsewhere" && env -u WAYLAND_DISPLAY HOME="$C/home" WREN_PASSWD="$C/passwd" script -qec "bash $HERE/bin/claude $*" /dev/null >"$C/out" 2>&1)
    fi
}
ran() { cat "$C/ran" 2>/dev/null; }
# From a launcher (no terminal, a sway session): it opens a window and runs in
# that; from a terminal, with its output piped, it stays where it is
mkdir -p "$C/fake"
printf '#!/bin/sh\necho "foot $*" >"%s/foot-ran"\n' "$C" >"$C/fake/foot"
chmod +x "$C/fake/foot"
cp "$C/claude-regular" "$C/home/.local/bin/"
rm -f "$C/foot-ran"
# (setsid: a launcher's programs have no controlling terminal, as sway's exec does)
(cd "$C/elsewhere" && env -u CLAUDE_CHOOSER_WINDOW WAYLAND_DISPLAY=wayland-9 PATH="$C/fake:$PATH" HOME="$C/home" WREN_PASSWD="$C/passwd" \
    setsid -w bash "$HERE/bin/claude" </dev/null >/dev/null 2>&1)
check "claude from a launcher (Super+d): it opens a foot window and runs in it" \
    bash -c 'grep -q "^foot --title=Claude env CLAUDE_CHOOSER_WINDOW=1 .*/bin/claude" "$1"' _ "$C/foot-ran"
rm -f "$C/foot-ran"
# sway started on tty1 hands that console to what it starts: stdin a terminal, but
# no controlling one
(cd "$C/elsewhere" && env -u CLAUDE_CHOOSER_WINDOW WAYLAND_DISPLAY=wayland-9 PATH="$C/fake:$PATH" HOME="$C/home" WREN_PASSWD="$C/passwd" \
    script -qec "setsid -w bash $HERE/bin/claude" /dev/null </dev/null >/dev/null 2>&1)
check "...also when sway was started on tty1 (stdin that console, out of sight)" \
    bash -c 'grep -q "^foot --title=Claude env CLAUDE_CHOOSER_WINDOW=1 .*/bin/claude" "$1"' _ "$C/foot-ran"
rm -f "$C/foot-ran" "$C/ran"
(cd "$C/elsewhere" && env -u CLAUDE_CHOOSER_WINDOW WAYLAND_DISPLAY=wayland-9 PATH="$C/fake:$PATH" HOME="$C/home" WREN_PASSWD="$C/passwd" \
    script -qec "bash $HERE/bin/claude --help | cat" /dev/null >/dev/null 2>&1)
check "...but claude --help | cat in a terminal stays there, no window" bash -c '[ ! -e "$1/foot-ran" ] && [[ $(cat "$1/ran") == "claude-regular --help in "* ]]' _ "$C"
have
ch no-terminal
check "claude with nothing set up: it says how" grep -q 'wrenctl install claude-vm' "$C/out"
have claude-regular
ch no-terminal
check "one way set up: that one, without asking" bash -c '[[ $1 == "claude-regular  in "* ]]' _ "$(ran)"
have claude-sandbox
ch no-terminal
check "the sandboxed one starts in ~/projects when you are elsewhere" bash -c '[[ $1 == "claude-sandbox  in "*/home/projects ]]' _ "$(ran)"
have claude-vm claude-sandbox claude-regular
ch ''
check "several: it asks, and lists them most walled off first" bash -c '
    grep -q "1) in its own virtual machine" "$1" && grep -q "2) as its own user" "$1" && grep -q "3) as you" "$1"' _ "$C/out"
check "...Enter takes the VM" bash -c '[[ $1 == "claude-vm "* ]]' _ "$(ran)"
ch 3
check "...3 takes the third" bash -c '[[ $1 == claude-regular* ]]' _ "$(ran)"
ch no-terminal
check "...no terminal to ask on: the most walled off" bash -c '[[ $1 == "claude-vm "* ]]' _ "$(ran)"
ch '' --resume foo
check "...options of Claude's: the VM takes none, so it isn't offered" bash -c '[[ $1 == "claude-sandbox --resume foo in "* ]]' _ "$(ran)"
ch no-terminal --version
check "...--version asks nothing, and starts no VM" bash -c '[[ $1 == "claude-sandbox --version in "*elsewhere ]]' _ "$(ran)"
have claude-sandbox
printf '%s\n' 'claude-agent:x:1:1::/x:/bin/sh' 'claude-agent-work:x:2:2::/x:/bin/sh' 'claude-agent-Bad:x:3:3::/x:/bin/sh' >"$C/passwd"
ch ''
check "a second account: it asks, not offering names that aren't one" bash -c 'grep -q "account work" "$1" && ! grep -q Bad "$1"' _ "$C/out"
ch 2
check "...2 takes that account, in its own folder" bash -c '[[ $1 == "claude-sandbox --account work in "*/home/projects-work ]]' _ "$(ran)"
ch no-terminal --account work
check "--account: that account, without asking" bash -c '[[ $1 == "claude-sandbox --account work "* ]]' _ "$(ran)"
ch no-terminal --account ../x
check "...a name that isn't one starts nothing" test ! -e "$C/ran"
: >"$C/passwd"; mkdir -p "$C/home/.claude-home"; : >"$C/home/.claude-home/settings.json"
have claude-regular
ch 2
check "claude-regular's second account (~/.claude-NAME) is offered too" bash -c '[[ $1 == "claude-regular --account home "* ]]' _ "$(ran)"
check "extras/claude-vm.sh installs the claude command and takes it out with the last one" bash -c '
    grep -qx "install_bin claude" "$1" && grep -q "remove_claude_chooser" "$1"' _ "$HERE/extras/claude-vm.sh"
for x in claude-code claude-regular; do
    check "the retired extras/$x.sh still takes the claude command out with the last one" grep -q "remove_claude_chooser" "$HERE/extras/$x.sh"
done
# remove_claude_chooser, on a temp folder
rc() { rm -rf "$C/rc"; mkdir -p "$C/rc"; printf '#!/bin/sh\n# written by Wren Linux\n' >"$C/rc/claude"; [ -z "$1" ] || : >"$C/rc/$1"
    bash -c 'BIN=$1; eval "$(sed -n "/^remove_bin() {/,/^}/p" "$2/lib/core.sh")"; . "$2/lib/claude-accounts.sh"; remove_claude_chooser' _ "$C/rc" "$HERE"; [ -e "$C/rc/claude" ]; }
check "...it stays while another is set up" rc claude-regular
nochooser() { ! rc ""; }
check "...and goes with the last" nochooser

# ---- Claude Code's theme: Auto (it follows the terminal), seeded in the VM
check "the VM seeds the theme as Auto" grep -qF '"theme": "auto"' "$HERE/data/claude-vm-provision.sh"

[ "$fails" -eq 0 ]