#!/bin/sh # tests/selfhost.sh - self-host integration test (plan todo 23). # # Proves stupidtools generates its OWN ./configure end to end: the tool # (already built by `make`) reads the repo's stupid.kdl, emits ./configure # into a TEMP dir, that configure produces a Makefile, and `make` builds a # working stupidtools whose compile line used -std=c23. The generated # configure/Makefile/binary live in the temp dir ONLY - the repo's own # autotools bootstrap (configure.ac + Makefile.am) is never touched, and no # in-tree residue is produced. # # POSIX sh only (dash/bash/zsh safe); no [[ ]], arrays, local, ==, <<<, &>. # Exit 0 = pass, non-zero = fail. Fails fast on a hard setup error. TOP_DIR=$(cd "$(dirname "$0")/.." && pwd) || { echo "FAIL: cannot resolve project root" >&2 exit 1 } BIN="$TOP_DIR/src/stupidtools" TESTS_RUN=0 TESTS_FAILED=0 pass() { TESTS_RUN=$((TESTS_RUN + 1)) printf 'ok %d - %s\n' "$TESTS_RUN" "$1" } fail() { TESTS_RUN=$((TESTS_RUN + 1)) TESTS_FAILED=$((TESTS_FAILED + 1)) printf 'not ok %d - %s\n' "$TESTS_RUN" "$1" >&2 } # --- 1. the built binary must exist -------------------------------------- if [ -x "$BIN" ]; then pass "binary exists and is executable: $BIN" else fail "binary missing: $BIN (run 'make' first)" printf 'FAILED: %d/%d checks failed\n' "$TESTS_FAILED" "$TESTS_RUN" >&2 exit 1 fi # --- temp dir (isolated; cleaned on every exit path) --------------------- WORK=$(mktemp -d "${TMPDIR:-/tmp}/stupidtools-selfhost.XXXXXX") || { fail "cannot create temp dir" exit 1 } cleanup() { rm -rf "$WORK"; } trap cleanup EXIT HUP INT TERM # --- 2. copy stupid.kdl + write a Makefile.in into the temp dir ---------- cp "$TOP_DIR/stupid.kdl" "$WORK/stupid.kdl" || { fail "cannot copy stupid.kdl into temp dir" exit 1 } # The tool's own source list (repo-relative, kept in sync with stupid.kdl's # `target` and src/Makefile.am). Prefixed with $TOP_DIR so the self-hosted # build compiles the REAL sources from the isolated temp dir. The `\$(...)` # escapes keep CC/CFLAGS/LIBS as MAKE variables (substituted by configure); # `$SRCS` expands here (shell) into the absolute path list. SRCS="$TOP_DIR/src/main.c $TOP_DIR/src/cli.c $TOP_DIR/src/error.c $TOP_DIR/src/span.c \ $TOP_DIR/src/kdl/lexer.c $TOP_DIR/src/kdl/parser.c $TOP_DIR/src/kdl/value.c $TOP_DIR/src/kdl/schema.c \ $TOP_DIR/src/detect/check_registry.c $TOP_DIR/src/detect/checks.c $TOP_DIR/src/detect/probe.c $TOP_DIR/src/detect/resolve.c \ $TOP_DIR/src/gen/sh_emit.c $TOP_DIR/src/gen/configure.c $TOP_DIR/src/gen/config.c $TOP_DIR/src/gen/args.c \ $TOP_DIR/src/ext/discovery.c $TOP_DIR/src/ext/lua.c $TOP_DIR/src/ext/abi.c $TOP_DIR/src/ext/api.c \ $TOP_DIR/src/ext/lang_c.c $TOP_DIR/src/ext/lang_cpp.c \ $TOP_DIR/thirdparty/lua/lapi.c $TOP_DIR/thirdparty/lua/lauxlib.c \ $TOP_DIR/thirdparty/lua/lbaselib.c $TOP_DIR/thirdparty/lua/lcode.c \ $TOP_DIR/thirdparty/lua/lctype.c $TOP_DIR/thirdparty/lua/ldebug.c \ $TOP_DIR/thirdparty/lua/ldo.c $TOP_DIR/thirdparty/lua/ldump.c \ $TOP_DIR/thirdparty/lua/lfunc.c $TOP_DIR/thirdparty/lua/lgc.c \ $TOP_DIR/thirdparty/lua/llex.c $TOP_DIR/thirdparty/lua/lmem.c \ $TOP_DIR/thirdparty/lua/lobject.c $TOP_DIR/thirdparty/lua/lopcodes.c \ $TOP_DIR/thirdparty/lua/lparser.c $TOP_DIR/thirdparty/lua/lstate.c \ $TOP_DIR/thirdparty/lua/lstring.c $TOP_DIR/thirdparty/lua/ltable.c \ $TOP_DIR/thirdparty/lua/ltm.c $TOP_DIR/thirdparty/lua/lundump.c \ $TOP_DIR/thirdparty/lua/lvm.c $TOP_DIR/thirdparty/lua/lzio.c \ $TOP_DIR/thirdparty/lua/lstrlib.c $TOP_DIR/thirdparty/lua/ltablib.c" cat > "$WORK/Makefile.in" <gen.log 2>&1); then if [ -s "$WORK/configure" ]; then pass "stupidtools generated a non-empty ./configure" else fail "stupidtools exited 0 but wrote no ./configure (see $WORK/gen.log)" fi else rc=$? fail "stupidtools failed on stupid.kdl (rc=$rc): $(cat "$WORK/gen.log" 2>/dev/null)" fi # --- 4. run ./configure -> Makefile -------------------------------------- if [ -s "$WORK/configure" ]; then if sh -n "$WORK/configure" 2>"$WORK/configure.n.err"; then pass "generated ./configure passes sh -n (POSIX syntax)" else fail "generated ./configure fails sh -n: $(cat "$WORK/configure.n.err" 2>/dev/null)" fi if (cd "$WORK" && sh ./configure >config.log 2>&1); then if [ -s "$WORK/Makefile" ]; then pass "./configure ran and wrote a non-empty Makefile" else fail "./configure exited 0 but wrote no Makefile (see $WORK/config.log)" fi else rc=$? fail "./configure failed (rc=$rc): $(cat "$WORK/config.log" 2>/dev/null)" fi else fail "skipping configure run (no ./configure was generated)" fi # --- 5+6. build with make V=1 and assert -std=c23 ------------------------ if [ -s "$WORK/Makefile" ]; then if command -v timeout >/dev/null 2>&1; then timeout 300 make -C "$WORK" V=1 >"$WORK/make.log" 2>&1 make_rc=$? else make -C "$WORK" V=1 >"$WORK/make.log" 2>&1 make_rc=$? fi if [ "$make_rc" -eq 0 ] && [ -x "$WORK/stupidtools" ]; then pass "make built a stupidtools binary in the temp dir" else fail "make failed (rc=$make_rc): $(tail -n 5 "$WORK/make.log" 2>/dev/null)" fi if grep -q -- '-std=c23' "$WORK/make.log"; then pass "self-hosted compile line used -std=c23" else fail "compile line did NOT contain -std=c23 (see $WORK/make.log)" fi else fail "skipping make (no Makefile was written)" fi # --- 7. the self-hosted binary must run ---------------------------------- if [ -x "$WORK/stupidtools" ]; then if version_out=$("$WORK/stupidtools" --version 2>&1); then if [ "$version_out" = "stupidtools 1.0.0" ]; then pass "self-hosted binary --version reports: $version_out" else fail "self-hosted --version unexpected: '$version_out'" fi else rc=$? fail "self-hosted --version exited non-zero (rc=$rc): $version_out" fi else fail "skipping --version check (no self-hosted binary was built)" fi # --- summary ------------------------------------------------------------- if [ "$TESTS_FAILED" -ne 0 ]; then printf 'FAILED: %d/%d checks failed\n' "$TESTS_FAILED" "$TESTS_RUN" >&2 exit 1 fi printf 'All %d self-host checks passed.\n' "$TESTS_RUN"