#!/bin/bash # Test script for slim - boots containers and verifies CMD inference/override. # Requires: podman, qemu-system-x86_64, KVM, curl, and a kernel at # $XDG_DATA_HOME/slim-rs/registry/vmlinuz. set -u SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" TARGET_DIR="${CARGO_TARGET_DIR:-$SCRIPT_DIR/../target}" SLIM_BIN="$TARGET_DIR/debug/slim" TIMEOUT=120 pass=0 fail=0 report() { if [ "$1" = "pass" ]; then echo " PASS: $2" pass=$((pass + 1)) else echo " FAIL: $2" fail=$((fail + 1)) fi } check_output() { output="$1" marker="$2" label="$3" if echo "$output" | grep -q "$marker"; then report pass "$label" else report fail "$label (expected marker: $marker)" fi } cleanup() { "$SLIM_BIN" image rm "$1" >/dev/null 2>&1 || true podman image rm "$2" >/dev/null 2>&1 || true } test_distro() { distro="$1" from="$2" extra_setup="$3" echo "=== Testing $distro ===" work="$(mktemp -d)" # === Test 1: CMD-inferred === echo "-- Test 1: CMD-inferred" cat > "$work/Containerfile.infer" </dev/null && echo CONN_OK; wget -q -O /dev/null http://example.org 2>/dev/null && echo DNS_OK; poweroff -f"] EOF img="slim-test-$distro-infer" if podman build -t "$img" -f "$work/Containerfile.infer" >/dev/null 2>&1; then "$SLIM_BIN" build qcow2 "$img" >/dev/null 2>&1 output=$(timeout "$TIMEOUT" "$SLIM_BIN" run "$img" 2>&1 || true) check_output "$output" "CMD_INFERRED_OK" "$distro CMD-inferred" check_output "$output" "CONN_OK" "$distro connect to 1.1.1.1" check_output "$output" "DNS_OK" "$distro DNS lookup" else report fail "$distro CMD-inferred (podman build failed)" fi cleanup "$img" "$img" # === Test 2: slim run --cmd override === echo "-- Test 2: slim run --cmd override" cat > "$work/Containerfile.run" </dev/null 2>&1; then "$SLIM_BIN" build qcow2 "$img" >/dev/null 2>&1 output=$(timeout "$TIMEOUT" "$SLIM_BIN" run "$img" --cmd 'echo RUN_OVERRIDE_OK; poweroff -f' 2>&1 || true) check_output "$output" "RUN_OVERRIDE_OK" "$distro run --cmd override" else report fail "$distro run --cmd override (podman build failed)" fi cleanup "$img" "$img" # === Test 3: slim build --cmd override === echo "-- Test 3: slim build --cmd override" cat > "$work/Containerfile.build" </dev/null 2>&1; then "$SLIM_BIN" build qcow2 "$img" --cmd 'echo BUILD_OVERRIDE_OK; poweroff -f' >/dev/null 2>&1 output=$(timeout "$TIMEOUT" "$SLIM_BIN" run "$img" 2>&1 || true) check_output "$output" "BUILD_OVERRIDE_OK" "$distro build --cmd override" else report fail "$distro build --cmd override (podman build failed)" fi cleanup "$img" "$img" rm -rf "$work" } test_user() { echo "=== Testing USER directive ===" work="$(mktemp -d)" img="slim-test-user" cat > "$work/Containerfile" <<'EOF' FROM alpine:latest RUN adduser -D -u 1500 testuser USER testuser CMD ["/bin/sh", "-c", "echo USER_BUILD_OK:$(id -u):$(whoami); poweroff -f"] EOF echo "-- Building container image with USER directive..." if ! podman build --network=none -t "$img" -f "$work/Containerfile" >/dev/null 2>&1; then report fail "USER directive (podman build failed)" rm -rf "$work" return fi echo "-- Test 1: build-time CMD runs as USER" "$SLIM_BIN" build qcow2 "$img" >/dev/null 2>&1 output=$(timeout "$TIMEOUT" "$SLIM_BIN" run "$img" 2>&1 || true) check_output "$output" "USER_BUILD_OK:1500:testuser" "USER build-time CMD runs as testuser" echo "-- Test 2: run --cmd override runs as USER" output=$(timeout "$TIMEOUT" "$SLIM_BIN" run "$img" --cmd 'echo USER_RUN_OK:$(id -u):$(whoami); poweroff -f' 2>&1 || true) check_output "$output" "USER_RUN_OK:1500:testuser" "USER run --cmd override runs as testuser" cleanup "$img" "$img" rm -rf "$work" } test_mount() { echo "=== Testing --mount (9p shares) ===" work="$(mktemp -d)" # --- Image 1: no WORKINGDIR, test host:guest and multi-mount --- img="slim-test-mount" share_dir="$work/share" share_dir2="$work/share2" guest_dir="/mnt/guest" guest_dir2="/mnt/guest2" mkdir -p "$share_dir" "$share_dir2" echo "hello from host" > "$share_dir/testfile.txt" echo "second share" > "$share_dir2/testfile2.txt" cat > "$work/Containerfile" <<'EOF' FROM alpine:latest CMD ["/bin/sh", "-c", "poweroff -f"] EOF echo "-- Building container image (no WORKINGDIR)..." if ! podman build --network=none -t "$img" -f "$work/Containerfile" >/dev/null 2>&1; then report fail "--mount (podman build failed)" rm -rf "$work" return fi echo "-- Building slim VM..." if ! "$SLIM_BIN" build qcow2 "$img" >/dev/null 2>&1; then report fail "--mount (slim build failed)" cleanup "$img" "$img" rm -rf "$work" return fi share_canon=$(readlink -f "$share_dir") share_canon2=$(readlink -f "$share_dir2") echo "-- Test 1: --mount host:guest (absolute guest path, multi-mount)..." output=$(timeout "$TIMEOUT" "$SLIM_BIN" run "$img" \ --mount "${share_dir}:${guest_dir}" \ --mount "${share_dir2}:${guest_dir2}" \ --cmd "for f in /sys/bus/virtio/drivers/9pnet_virtio/virtio*/mount_tag; do [ -f \"\$f\" ] && echo \"\$f: \$(tr -d '\\0' < \"\$f\")\"; done; mount -v; cat ${guest_dir}/testfile.txt 2>/dev/null || echo NO_FILE; cat ${guest_dir2}/testfile2.txt 2>/dev/null || echo NO_FILE2; echo MOUNT_VERIFY_DONE; poweroff -f" \ 2>&1 || true) check_output "$output" "mount_tag: slim0" "--mount 9p share detected" check_output "$output" "mount_tag: slim1" "--mount second 9p share detected" check_output "$output" "type 9p" "--mount 9p filesystem in mount list" check_output "$output" "hello from host" "--mount file accessible at guest path" check_output "$output" "second share" "--mount second file accessible at guest path" check_output "$output" "MOUNT_VERIFY_DONE" "--mount VM ran to completion" cleanup "$img" "$img" # --- Image 2: with WORKINGDIR, test relative guest path --- img="slim-test-mount-wd" share_dir3="$work/share3" mkdir -p "$share_dir3" echo "relative share" > "$share_dir3/relfile.txt" cat > "$work/Containerfile.wd" <<'EOF' FROM alpine:latest WORKDIR /app CMD ["/bin/sh", "-c", "poweroff -f"] EOF echo "-- Building container image (WORKINGDIR /app)..." if ! podman build --network=none -t "$img" -f "$work/Containerfile.wd" >/dev/null 2>&1; then report fail "--mount relative (podman build failed)" rm -rf "$work" return fi echo "-- Building slim VM..." if ! "$SLIM_BIN" build qcow2 "$img" >/dev/null 2>&1; then report fail "--mount relative (slim build failed)" cleanup "$img" "$img" rm -rf "$work" return fi echo "-- Test 2: --mount host:relative-guest (resolved against WORKINGDIR)..." output=$(timeout "$TIMEOUT" "$SLIM_BIN" run "$img" \ --mount "${share_dir3}:data" \ --cmd "mount -v; cat /app/data/relfile.txt 2>/dev/null || echo NO_REL_FILE; echo REL_VERIFY_DONE; poweroff -f" \ 2>&1 || true) check_output "$output" "type 9p" "--mount relative 9p filesystem in mount list" check_output "$output" "relative share" "--mount relative file accessible at WORKINGDIR/data" check_output "$output" "REL_VERIFY_DONE" "--mount relative VM ran to completion" cleanup "$img" "$img" rm -rf "$work" } test_service() { echo "=== Testing nextcloud service ===" img="slim-test-nextcloud" host_port=18080 work="$(mktemp -d)" cat > "$work/Containerfile" <<'EOF' FROM docker.io/library/nextcloud:32-apache RUN apt-get update && apt-get install -y --no-install-recommends iproute2 && rm -rf /var/lib/apt/lists/* EOF echo "-- Building nextcloud container image..." if ! podman build -t "$img" -f "$work/Containerfile" >/dev/null 2>&1; then report fail "nextcloud service (podman build failed)" rm -rf "$work" return fi echo "-- Building slim VM..." if ! "$SLIM_BIN" build qcow2 "$img" >/dev/null 2>&1; then report fail "nextcloud service (slim build failed)" cleanup "$img" "$img" rm -rf "$work" return fi echo "-- Booting VM with port forward (host :${host_port} -> guest :80)..." setsid timeout 300 "$SLIM_BIN" run "$img" \ --forward "tcp:0.0.0.0:${host_port}-:80" \ --memory 2048M >/dev/null 2>&1 & vm_pid=$! echo "-- Waiting for Nextcloud to start..." up=0 for _ in $(seq 1 60); do if curl -s -o /dev/null -m 2 "http://localhost:${host_port}/" 2>/dev/null; then up=1 break fi sleep 3 done if [ "$up" = "1" ]; then response=$(curl -s -L -m 10 "http://localhost:${host_port}/" 2>/dev/null || true) if echo "$response" | grep -qi "nextcloud"; then report pass "nextcloud service (port forward + content)" else report fail "nextcloud service (port reachable but no 'nextcloud' in response)" fi else report fail "nextcloud service (port not reachable within timeout)" fi pkill -f "qemu-system-x86_64.*$img" 2>/dev/null || true kill "$vm_pid" 2>/dev/null || true wait "$vm_pid" 2>/dev/null || true cleanup "$img" "$img" rm -rf "$work" } echo "Building slim..." cargo build 2>&1 test_distro "alpine" "alpine:latest" "" test_distro "archlinux" "archlinux:latest" "RUN pacman -Sy --noconfirm iproute2 wget; pacman -Sc --noconfirm" test_user test_mount test_service echo "" echo "=== Results: $pass passed, $fail failed ===" [ "$fail" -eq 0 ]