#!/usr/bin/env bash # tofu smoketest — full e2e: install → search → upgrade → remove # against a LOCAL mock ZUUR. No network access, no root required. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" TOFU_BIN="$REPO_ROOT/tofu" RED='\033[31m' GREEN='\033[32m' NC='\033[0m' PASS_COUNT=0 FAIL_COUNT=0 pass() { echo -e "${GREEN}PASS${NC}"; PASS_COUNT=$((PASS_COUNT + 1)); } fail() { echo -e "${RED}FAIL${NC}"; FAIL_COUNT=$((FAIL_COUNT + 1)); exit 1; } # ── Step 0: Build tofu binary ──────────────────────────────────────── echo "=== tofu smoketest ===" echo "" echo -n "step 0: build tofu binary... " if [ -f "$TOFU_BIN" ]; then pass # pre-built else (cd "$REPO_ROOT" && dub build 2>&1 >/dev/null) || fail pass fi # ── Step 1: Create temp dir + hello "binary" ───────────────────────── TMP="$(mktemp -d)" trap 'kill $SERVER_PID 2>/dev/null; rm -rf "$TMP"' EXIT echo -n "step 1: create temp workspace and hello binary... " mkdir -p "$TMP/bin" # Use a shell script as the "binary" — no compiler dependency cat > "$TMP/hello" <<'HELLOEOF' #!/bin/sh echo "hello from tofu" HELLOEOF chmod +x "$TMP/hello" mkdir -p "$TMP/source" cp "$TMP/hello" "$TMP/source/hello" pass # ── Step 2: Create hello.recipe ────────────────────────────────────── # NOTE: deps = {} is REQUIRED — a D bug in info.d's scanDepsArray() # stores indexOf() return in size_t, causing an ArrayIndexError when # "deps" is absent. See .omo/notepads/tofu-core/problems.md. echo -n "step 2: create hello.recipe... " cat > "$TMP/hello.recipe" <<'RECIPEEOF' name = "hello" version = "1.0" summary = "A tiny demo package" build_system = "make" deps = {} test = "test -f ${DESTDIR}/usr/bin/hello" RECIPEEOF pass # ── Step 3: Create package.lua ─────────────────────────────────────── echo -n "step 3: create package.lua... " cat > "$TMP/package.lua" <<'PKGLUAEOF' return { name = "hello", version = "1.0", url = "local", sha256 = "any", archive = { strip = 1 }, } PKGLUAEOF pass # ── Step 4: Create fake zeta-makepkg and fake zeta ─────────────────── echo -n "step 4: create fake zeta-makepkg and zeta... " # Fake zeta-makepkg: simulates building a package. # Args: --output -j --no-index --repo cat > "$TMP/bin/zeta-makepkg" <<'MKPKGEOF' #!/usr/bin/env bash # $1 = recipe path, $3 = output dir (after --output) recipe_path="$1" output_dir="" i=1 while [[ $i -le $# ]]; do if [[ "${!i}" == "--output" ]]; then n=$((i + 1)) output_dir="${!n}" break fi i=$((i + 1)) done if [[ -z "$output_dir" ]]; then echo "error: --output not specified" >&2 exit 1 fi # Extract package name from recipe path pkg_name=$(basename "$(dirname "$recipe_path")") pkg_dir="$output_dir/packages/$pkg_name" mkdir -p "$pkg_dir" # Create the "built" binary inside a tarball tmpd=$(mktemp -d) cat > "$tmpd/hello" <<'BINEOF' #!/bin/sh echo "hello from tofu" BINEOF chmod +x "$tmpd/hello" tar -czf "$pkg_dir/${pkg_name}-1.0.tar.gz" -C "$tmpd" hello rm -rf "$tmpd" # Copy package.lua from recipe cache directory recipe_dir="$(dirname "$recipe_path")" if [[ -f "$recipe_dir/package.lua" ]]; then cp "$recipe_dir/package.lua" "$pkg_dir/package.lua" else echo "error: package.lua not found in $recipe_dir" >&2 exit 1 fi exit 0 MKPKGEOF chmod +x "$TMP/bin/zeta-makepkg" # Fake zeta: simulates -LocalProvide and -Remove. # Args: -LocalProvide --pass OR -Remove --pass [--force] cat > "$TMP/bin/zeta" <<'ZETAEOF' #!/usr/bin/env bash cmd="$1" pkg="$2" ZETA_ROOT="${ZETA_ROOT:-}" if [[ "$cmd" == "-LocalProvide" ]]; then echo "installing $pkg-1.0" if [[ -n "$ZETA_ROOT" ]]; then mkdir -p "$ZETA_ROOT/usr/bin" # Extract from tarball if available cache_dir="${TOFU_CACHE_DIR:-}" tarball="$cache_dir/built/packages/$pkg/${pkg}-1.0.tar.gz" if [[ -f "$tarball" ]]; then tar -xzf "$tarball" -C "$ZETA_ROOT/usr/bin/" hello 2>/dev/null || true else cat > "$ZETA_ROOT/usr/bin/$pkg" <<'HEOF' #!/bin/sh echo "hello from tofu" HEOF chmod +x "$ZETA_ROOT/usr/bin/$pkg" fi fi exit 0 elif [[ "$cmd" == "-Remove" ]]; then echo "removing $pkg" if [[ -n "$ZETA_ROOT" ]]; then rm -f "$ZETA_ROOT/usr/bin/$pkg" fi exit 0 else echo "unknown zeta command: $cmd" >&2 exit 1 fi ZETAEOF chmod +x "$TMP/bin/zeta" export PATH="$TMP/bin:$PATH" pass # ── Step 5: Set up mock ZUUR layout ────────────────────────────────── echo -n "step 5: create mock ZUUR layout... " MOCK_ZUUR="$TMP/zuur" mkdir -p "$MOCK_ZUUR/recipes/hello" mkdir -p "$MOCK_ZUUR/binary/packages" # index.lua — single package entry cat > "$MOCK_ZUUR/index.lua" <<'INDEXEOF' return { { name = "hello", ver = "1.0", summary = "A tiny demo", pool = "recipes" } } INDEXEOF # Recipe in ZUUR cp "$TMP/hello.recipe" "$MOCK_ZUUR/recipes/hello/hello.recipe" # package.lua in ZUUR cp "$TMP/package.lua" "$MOCK_ZUUR/recipes/hello/package.lua" pass # ── Step 6: Start http.server on ephemeral port ────────────────────── echo -n "step 6: start mock ZUUR http server... " # Pick a random high port and retry if busy PORT=0 for attempt in $(seq 1 20); do PORT=$((20000 + RANDOM % 10000)) if ! (echo >/dev/tcp/127.0.0.1/$PORT) 2>/dev/null; then break fi done (cd "$MOCK_ZUUR" && python3 -m http.server "$PORT" --bind 127.0.0.1 > /dev/null 2>&1) & SERVER_PID=$! # Wait for server to be ready ZUUR_URL="http://127.0.0.1:$PORT" for i in $(seq 1 20); do if curl -s "$ZUUR_URL/index.lua" > /dev/null 2>&1; then break fi sleep 0.2 done # Verify server is up if ! curl -s "$ZUUR_URL/index.lua" > /dev/null 2>&1; then echo "ERROR: http server failed to start" >&2 exit 1 fi pass # ── Step 6b: Export env vars ───────────────────────────────────────── export TOFU_ZUUR_URL="$ZUUR_URL" export TOFU_CACHE_DIR="$TMP/cache" export TOFU_ZETA_TOOLCHAIN_PATH="$TMP/bin/zeta-makepkg" export TOFU_ZETA_PATH="$TMP/bin/zeta" export ZETA_ROOT="$TMP/root" mkdir -p "$TOFU_CACHE_DIR" "$ZETA_ROOT" echo " ZUUR_URL=$TOFU_ZUUR_URL" echo " CACHE_DIR=$TOFU_CACHE_DIR" echo "" # ── Step 7: Search (-Ss) ───────────────────────────────────────────── # Capture output then grep — avoids pipefail false-negatives if tofu # exits non-zero but still produces valid stdout. echo -n "step 7: tofu -Ss hello (search)... " SEARCH_OUT=$("$TOFU_BIN" -Ss hello 2>/dev/null) || true if echo "$SEARCH_OUT" | grep -q "hello"; then pass else fail fi # ── Step 8: Install (-S hello --noconfirm) ─────────────────────────── echo -n "step 8: tofu -S hello --noconfirm (install)... " if "$TOFU_BIN" -S hello --noconfirm 2>&1; then : else fail fi # Verify: installed.json contains hello if [ -f "$TOFU_CACHE_DIR/installed.json" ] && grep -q '"hello"' "$TOFU_CACHE_DIR/installed.json"; then echo -n " installed.json check... " pass else echo -n " installed.json check... " fail fi # Verify: $ZETA_ROOT/usr/bin/hello exists (fake zeta installed it) if [ -x "$ZETA_ROOT/usr/bin/hello" ]; then echo -n " binary in root check... " pass else echo -n " binary in root check... " fail fi # ── Step 9: Upgrade (-Syu --noconfirm) ─────────────────────────────── echo -n "step 9: tofu -Syu --noconfirm (upgrade — nothing to do)... " UPGRADE_OUT=$("$TOFU_BIN" -Syu --noconfirm 2>&1) || true if echo "$UPGRADE_OUT" | grep -qi "nothing to do"; then pass else fail fi # ── Step 10: Info (-Si hello) ──────────────────────────────────────── echo -n "step 10: tofu -Si hello (info)... " INFO_OUT=$("$TOFU_BIN" -Si hello 2>/dev/null) || true if echo "$INFO_OUT" | grep -q "hello"; then pass else fail fi # ── Step 11: Remove (-R hello --noconfirm) ─────────────────────────── echo -n "step 11: tofu -R hello --noconfirm (remove)... " if "$TOFU_BIN" -R hello --noconfirm 2>&1; then : else fail fi # Verify: $ZETA_ROOT/usr/bin/hello is GONE if [ ! -f "$ZETA_ROOT/usr/bin/hello" ]; then echo -n " binary removed check... " pass else echo -n " binary removed check... " fail fi # Verify: installed.json no longer has hello if [ -f "$TOFU_CACHE_DIR/installed.json" ] && ! grep -q '"hello"' "$TOFU_CACHE_DIR/installed.json"; then echo -n " installed.json cleared check... " pass else # If installed.json was removed entirely, that's also fine if [ ! -f "$TOFU_CACHE_DIR/installed.json" ]; then echo -n " installed.json cleared check... " pass else echo -n " installed.json cleared check... " fail fi fi # ── Step 12: Install nonexistent → exit 2 ──────────────────────────── echo -n "step 12: tofu -S nonexistent --noconfirm (expect exit 2)... " set +e "$TOFU_BIN" -S nonexistent --noconfirm 2>/dev/null RC=$? set -e if [ "$RC" -eq 2 ]; then pass else echo " got exit code $RC, expected 2" fail fi # ── Step 13: Cleanup (trap handles it) ─────────────────────────────── echo -n "step 13: cleanup... " # trap on EXIT will clean up $TMP and kill server pass # ── Summary ────────────────────────────────────────────────────────── echo "" echo "=== smoketest complete ===" echo "PASS: $PASS_COUNT checks passed" if [ "$FAIL_COUNT" -gt 0 ]; then echo "FAIL: $FAIL_COUNT checks failed" exit 1 fi echo "All checks passed." exit 0