#!/bin/sh # tests/run.sh - test runner for stupidtools (skeleton). # # Grows with the project; today it proves the toolchain works end to end: # the binary must exist, run, and report its version. # # POSIX sh only (dash/bash/zsh safe). Fails fast on the first failed check. 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 and be executable ------------------- 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 # --- 2. --version must exit 0 and print exactly 'stupidtools ' - version_out= if version_out=$("$BIN" --version 2>&1); then if [ "$version_out" = "stupidtools 1.0.0" ]; then pass "--version reports: $version_out" else fail "--version output unexpected: '$version_out' (want 'stupidtools 1.0.0')" fi else rc=$? fail "--version exited non-zero (rc=$rc): $version_out" fi # --- 3. --help must exit 0 and print usage on stdout ---------------------- help_out= help_rc=0 help_out=$("$BIN" --help 2>/dev/null) || help_rc=$? if [ "$help_rc" -eq 0 ]; then case "$help_out" in *"Usage: "*) pass "--help exits 0 and prints usage on stdout" ;; *) fail "--help printed no usage on stdout: '$help_out'" ;; esac else fail "--help exited non-zero (rc=$help_rc): $help_out" fi # --- 4. unknown option must exit 2, usage on stderr, silent stdout ------- bogus_out= bogus_rc=0 bogus_out=$("$BIN" --bogus 2>&1 >/dev/null) || bogus_rc=$? if [ "$bogus_rc" -eq 2 ]; then case "$bogus_out" in *"Usage: "*) pass "--bogus exits 2 and prints usage on stderr" ;; *) fail "--bogus printed no usage on stderr: '$bogus_out'" ;; esac else fail "--bogus exited rc=$bogus_rc (want 2): $bogus_out" fi if [ -z "$("$BIN" --bogus 2>/dev/null)" ]; then pass "--bogus prints nothing on stdout" else fail "--bogus leaked output on stdout" fi # --- 5. no arguments must exit 1 with usage on stderr --------------------- noargs_out= noargs_rc=0 noargs_out=$("$BIN" 2>&1 >/dev/null) || noargs_rc=$? if [ "$noargs_rc" -eq 1 ]; then case "$noargs_out" in *"Usage: "*) pass "no arguments exits 1 and prints usage on stderr" ;; *) fail "no arguments printed no usage on stderr: '$noargs_out'" ;; esac else fail "no arguments exited rc=$noargs_rc (want 1): $noargs_out" fi # --- 6. one positional buildfile must be accepted (exit 0, no read) ------ if "$BIN" buildfile.kdl >/dev/null 2>&1; then pass "one positional buildfile is accepted (exit 0)" else fail "one positional buildfile was rejected (rc=$?)" fi # --- 7. --version=1 is an unknown option form -> exit 2 ------------------- "$BIN" --version=1 >/dev/null 2>&1 rc=$? if [ "$rc" -eq 2 ]; then pass "--version=1 exits 2 (unknown option)" else fail "--version=1 exited rc=$rc (want 2)" fi # --- 8. -- ends option parsing; following text is positional -------------- if "$BIN" -- --bogus >/dev/null 2>&1; then pass "-- terminates option parsing (--bogus taken as buildfile)" else fail "-- --bogus exited non-zero (rc=$?)" fi # --- 9. two positionals are a usage error -> exit 2 ----------------------- "$BIN" a.kdl b.kdl >/dev/null 2>&1 rc=$? if [ "$rc" -eq 2 ]; then pass "two positionals exit 2 (usage error)" else fail "two positionals exited rc=$rc (want 2)" fi # --- 10. unit tests (tests/unit/*.c via munit) ---------------------------- # Harness contract (see .omo/notepads/stupidtools/learnings.md): # - each tests/unit/.c declares its extra link sources on its FIRST # line as: /* LINK: ../../src/foo.c ../../src/bar.c */ # (paths relative to tests/unit/, space-separated) # - the harness adds -I /thirdparty/munit -I /src and # thirdparty/munit/munit.c automatically, compiles with # -std=c23 -Wall -Wextra -Wpedantic, and runs each binary. # - a missing LINK line, a compile failure, or a non-zero exit all FAIL. UNIT_DIR="$TOP_DIR/tests/unit" UNIT_BUILD="$TOP_DIR/tests/.unit-build" if [ -d "$UNIT_DIR" ]; then if ! mkdir -p "$UNIT_BUILD"; then fail "cannot create unit build dir: $UNIT_BUILD" fi for t in "$UNIT_DIR"/*.c; do [ -e "$t" ] || continue name=$(basename "$t" .c) # first line only: /* LINK: src1.c src2.c */ link=$(sed -n '1s|^/\* LINK: \(.*\) \*/$|\1|p' "$t") if [ -z "$link" ]; then fail "unit test $name: missing LINK comment on line 1" continue fi # compile with cwd=tests/unit so LINK paths resolve relative to it; # absolute paths for everything else. if (cd "$UNIT_DIR" && cc -std=c23 -Wall -Wextra -Wpedantic \ -I "$TOP_DIR/thirdparty/munit" -I "$TOP_DIR/src" \ "$TOP_DIR/thirdparty/munit/munit.c" $link "$t" \ -o "$UNIT_BUILD/unit_$name") 2>"$UNIT_BUILD/unit_$name.build.log" then : else fail "unit test $name: compile failed (see $UNIT_BUILD/unit_$name.build.log)" continue fi if "$UNIT_BUILD/unit_$name" >"$UNIT_BUILD/unit_$name.log" 2>&1; then pass "unit test $name passed" else rc=$? fail "unit test $name exited rc=$rc (see $UNIT_BUILD/unit_$name.log)" fi done 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 checks passed.\n' "$TESTS_RUN"