Add opt-in root access, persistent containers and per-project packages
This commit is contained in:
132
test.sh
Executable file
132
test.sh
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
# test.sh — end-to-end tests for claudio, run on the host with the real podman.
|
||||
#
|
||||
# Usage: ./test.sh build the image (cached if unchanged) and test
|
||||
# ./test.sh --update also test `claudio update` (slow: full rebuild)
|
||||
#
|
||||
# It works in throwaway directories under /tmp and removes the containers and
|
||||
# PACKAGES images it creates. The shared claudio-home volume is not touched.
|
||||
set -uo pipefail
|
||||
|
||||
CLAUDIO="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")/claudio.sh"
|
||||
proj="$(mktemp -d /tmp/claudio-test.XXXXXX)"
|
||||
extra="$(mktemp -d /tmp/claudio-test-mount.XXXXXX)"
|
||||
# Same formula as claudio.sh, to find this project's PERSIST container.
|
||||
name="claudio-$(printf %s "${proj##*/}" | tr -c 'a-zA-Z0-9_.-' -)-$(printf %s "$proj" | sha256sum | cut -c1-8)"
|
||||
pkg_images() { podman images -q --filter label=claudio.packages | sort -u; }
|
||||
pkg_before="$(pkg_images)"
|
||||
|
||||
cleanup() {
|
||||
(cd "$proj" && "$CLAUDIO" reset)
|
||||
comm -13 <(echo "$pkg_before") <(pkg_images) | xargs -r podman rmi -f >/dev/null
|
||||
rm -rf "$proj" "$extra"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
pass=0 fail=0
|
||||
check() { # check "description" command...
|
||||
if "${@:2}"; then echo " ok $1"; ((pass++))
|
||||
else echo " FAIL $1"; ((fail++)); fi
|
||||
}
|
||||
not() { ! "$@"; }
|
||||
cfg() { printf '%s\n' "$@" > "$proj/.claudio"; } # write the project's .claudio
|
||||
claudio() { (cd "$proj" && "$CLAUDIO" "$@" </dev/null); }
|
||||
cin() { claudio shell -c "$1"; } # run a command in the container
|
||||
exits_with() { local want="$1"; shift; "$@"; [[ $? == "$want" ]]; }
|
||||
wait_for() { for _ in {1..30}; do "$@" && return; sleep 1; done; return 1; }
|
||||
running() { [[ "$(podman container inspect -f '{{.State.Running}}' "$name" 2>/dev/null)" == true ]]; }
|
||||
image_of() { podman container inspect -f '{{.ImageName}}' "$name"; }
|
||||
|
||||
from_home() { (cd ~ && "$CLAUDIO" shell -c true </dev/null 2>/dev/null); }
|
||||
owned_by_you() { cin 'touch owned' && [[ "$(stat -c %u "$proj/owned")" == "$(id -u)" ]]; }
|
||||
pkg_image_labeled() { [[ -n "$(podman images -q --filter label=claudio.packages \
|
||||
--filter reference='localhost/claudio:pkgs-*')" ]]; }
|
||||
claude_runs() { claudio --version >/dev/null; }
|
||||
bg_session_up() { podman exec "$name" pgrep -fx 'sleep 8' >/dev/null 2>&1; }
|
||||
|
||||
echo "== build"
|
||||
"$CLAUDIO" build || { echo "build failed"; exit 1; }
|
||||
|
||||
echo "== basics"
|
||||
cfg ''
|
||||
check "refuses to run from \$HOME" not from_home
|
||||
check "runs as uid 1000 in the project dir" cin "[[ \$(id -u) == 1000 && \$PWD == '$proj' ]]"
|
||||
check "claude is installed" claude_runs
|
||||
check "files in the project are yours" owned_by_you
|
||||
check "exit status is passed through" exits_with 3 cin 'exit 3'
|
||||
check "home is the claudio-home volume" cin 'mountpoint -q /home/claude'
|
||||
|
||||
echo "== MOUNTS"
|
||||
cfg "MOUNTS=('$extra')"
|
||||
check "bare path is mounted read-write" cin "touch '$extra/rw'"
|
||||
cfg "MOUNTS=('$extra:/mnt/x:ro')"
|
||||
check "':ro' mount is read-only" cin 'test -f /mnt/x/rw && ! touch /mnt/x/no 2>/dev/null'
|
||||
|
||||
echo "== ROOT"
|
||||
cfg ''
|
||||
check "no sudo without ROOT=1" cin '! sudo -n true 2>/dev/null'
|
||||
cfg 'ROOT=1'
|
||||
check "passwordless sudo with ROOT=1" cin '[[ $(sudo -n id -u) == 0 ]]'
|
||||
check "root can change the system" cin 'sudo -n touch /opt/marker'
|
||||
check "...but it is gone next run" cin '! test -e /opt/marker'
|
||||
|
||||
echo "== PACKAGES"
|
||||
cfg 'PACKAGES=(hello)'
|
||||
check "package is installed" cin 'hello >/dev/null'
|
||||
check "image is labeled claudio.packages" pkg_image_labeled
|
||||
cfg ''
|
||||
check "not installed without PACKAGES" cin '! command -v hello >/dev/null'
|
||||
|
||||
echo "== PERSIST"
|
||||
persist=('ROOT=1' 'PERSIST=1' 'PACKAGES=(hello)')
|
||||
cfg "${persist[@]}"
|
||||
check "system changes are kept" cin 'sudo -n touch /opt/marker'
|
||||
check "...and seen by the next run" cin 'test -e /opt/marker'
|
||||
check "PACKAGES and sudo work too" cin 'hello >/dev/null && sudo -n true'
|
||||
check "stopped after the run" not running
|
||||
check "exit status is passed through" exits_with 3 cin 'exit 3'
|
||||
check "claudio (not shell) runs claude" claude_runs
|
||||
|
||||
cin 'sleep 8' & bg=$!
|
||||
check "background session started" wait_for bg_session_up
|
||||
cin true
|
||||
check "still running while a session is" running
|
||||
cfg "${persist[@]}" "MOUNTS=('$extra')" # must change the podman options, not just the file
|
||||
check "refuses to recreate while in use" not cin true 2>/dev/null
|
||||
cfg "${persist[@]}"
|
||||
wait "$bg"
|
||||
check "stopped after the last session" not running
|
||||
|
||||
cfg "${persist[@]}" "MOUNTS=('$extra')"
|
||||
check "settings change recreates it" cin '! test -e /opt/marker'
|
||||
claudio reset
|
||||
check "reset removes the container" not podman container exists "$name"
|
||||
cfg "${persist[@]}"
|
||||
cin 'sudo -n touch /opt/marker'
|
||||
cfg "${persist[@]/#PERSIST=1/PERSIST=0}"
|
||||
check "PERSIST=0 ignores the container" cin '! test -e /opt/marker'
|
||||
|
||||
echo "== GUI / AUDIO (skipped if your session has no such socket)"
|
||||
host_run="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
||||
wl="${WAYLAND_DISPLAY:-wayland-0}"
|
||||
if [[ -S "$host_run/$wl" ]]; then
|
||||
cfg 'GUI=1'
|
||||
check "Wayland socket is shared" cin "test -S /run/user/1000/$wl && [[ \$WAYLAND_DISPLAY == '$wl' ]]"
|
||||
else echo " skip GUI (no $host_run/$wl)"; fi
|
||||
if [[ -S "$host_run/pulse/native" ]]; then
|
||||
cfg 'AUDIO=1'
|
||||
check "PulseAudio socket is shared" cin 'test -S /run/user/1000/pulse/native'
|
||||
else echo " skip AUDIO (no $host_run/pulse/native)"; fi
|
||||
|
||||
if [[ "${1:-}" == --update ]]; then
|
||||
echo "== update"
|
||||
cfg "${persist[@]}"
|
||||
cin 'sudo -n touch /opt/marker'
|
||||
old="$(image_of)"
|
||||
"$CLAUDIO" update
|
||||
check "update recreates the container" cin '! test -e /opt/marker && hello >/dev/null'
|
||||
check "...on a new PACKAGES image" [ "$(image_of)" != "$old" ]
|
||||
fi
|
||||
|
||||
echo "== $pass passed, $fail failed"
|
||||
(( fail == 0 ))
|
||||
Reference in New Issue
Block a user