#!/bin/bash # # klammerset_test.sh — Regression tests for the @@@klammerset system command. # # @@@klammerset declares a Klammerset: a named, logically related group of # klammer definitions. The declaration is operative — processing it reads # the :requires files and then the :files, in list order, at the point of the # declaration; relative names resolve against the declaring file's directory, # never the cwd. A repeated declaration of an already-registered symbol is # skipped (loaded once), which is what makes :requires idempotent. The # klammers themselves live in the Machine's flat Klammer_registry; the # Klammerset holds metadata and the file list only. # # Engine tier: no SKS. Fixtures live in tst/klammerset/ and define their own # target ("fix") inline. # # Usage: ./klammerset_test.sh # Exit code: 0 if all tests pass, 1 otherwise. PASS=0 FAIL=0 KTEXT=ktext K=${KLAMMERTEXT_HOME:?KLAMMERTEXT_HOME must be set} FIX=$K/tst/klammerset red=$'\033[31m' green=$'\033[32m' bold=$'\033[1m' reset=$'\033[0m' # strip leading/trailing blank lines and surrounding whitespace trim() { awk '{ sub(/[ \t\r]+$/, "") } { line[NR]=$0 } END { f=1; while (f<=NR && line[f]=="") f++; l=NR; while (l>=1 && line[l]=="") l--; for (i=f;i<=l;i++) print line[i] }'; } # check_eq TEST_NAME EXPECTED KTEXT_ARGS... # Runs ktext, expects exit status 0, and compares trimmed stdout to EXPECTED. check_eq() { local test_name="$1" local expected="$2" shift 2 local output status output=$("$KTEXT" "$@" 2>/tmp/klammerset_test_err.$$) status=$? output=$(printf '%s' "$output" | trim) if [ $status -ne 0 ]; then echo "${red}FAIL${reset} $test_name — ktext exited $status" echo " stderr: $(head -3 /tmp/klammerset_test_err.$$)" FAIL=$((FAIL + 1)) return fi if [ "$output" = "$expected" ]; then echo "${green}PASS${reset} $test_name" PASS=$((PASS + 1)) else echo "${red}FAIL${reset} $test_name" echo " expected: [$expected]" echo " got: [$output]" FAIL=$((FAIL + 1)) fi } # check_contains TEST_NAME SUBSTRING KTEXT_ARGS... # Runs ktext, expects exit status 0, and checks that the combined output # contains SUBSTRING. check_contains() { local test_name="$1" local needle="$2" shift 2 local output status output=$("$KTEXT" "$@" 2>&1) status=$? if [ $status -ne 0 ]; then echo "${red}FAIL${reset} $test_name — ktext exited $status" echo " output: $(echo "$output" | head -3)" FAIL=$((FAIL + 1)) return fi if printf '%s' "$output" | grep -qF "$needle"; then echo "${green}PASS${reset} $test_name" PASS=$((PASS + 1)) else echo "${red}FAIL${reset} $test_name" echo " expected output to contain: [$needle]" echo " got: $(echo "$output" | head -5)" FAIL=$((FAIL + 1)) fi } # check_fails TEST_NAME SUBSTRING KTEXT_ARGS... # Runs ktext, expects a NONZERO exit status, and checks that the combined # output contains SUBSTRING. check_fails() { local test_name="$1" local needle="$2" shift 2 local output status output=$("$KTEXT" "$@" 2>&1) status=$? if [ $status -eq 0 ]; then echo "${red}FAIL${reset} $test_name — expected an error, ktext exited 0" FAIL=$((FAIL + 1)) return fi if printf '%s' "$output" | grep -qF "$needle"; then echo "${green}PASS${reset} $test_name" PASS=$((PASS + 1)) else echo "${red}FAIL${reset} $test_name" echo " expected error output to contain: [$needle]" echo " got: $(echo "$output" | head -5)" FAIL=$((FAIL + 1)) fi } echo "${bold}@@@klammerset tests${reset}" echo "=======================" echo # --- Loading --------------------------------------------------------------- # 1. :files load in list order: klammers.k defines @greet for the target # that base.k declares, so base.k must have been read first. check_eq "1. :files load in order (target before klammer)" \ "Hello World" \ -k "$FIX/decl.k" -s '@greet World @' -t fix # 2. :requires loads the dependency before the set's own files. check_eq "2. :requires loads the dependency" \ "--" \ -k "$FIX/decl.k" -s '@dash@' -t fix # 3. Program order: a definition AFTER the declaration in the declaring file # is available (there is no :text argument; the declaring file's own # content plays that role). check_eq "3. trailing definition in the declaring file" \ "AFTER" \ -k "$FIX/decl.k" -s '@after@' -t fix # 4. Relative :files names resolve against the DECLARING file's directory, # not the cwd (run from an unrelated directory). output=$( (cd /tmp && "$KTEXT" -k "$FIX/decl.k" -s '@greet Elsewhere @' -t fix) 2>/dev/null | trim ) if [ "$output" = "Hello Elsewhere" ]; then echo "${green}PASS${reset} 4. :files resolve against the declaring file's directory" PASS=$((PASS + 1)) else echo "${red}FAIL${reset} 4. :files resolve against the declaring file's directory" echo " expected: [Hello Elsewhere]" echo " got: [$output]" FAIL=$((FAIL + 1)) fi # 5. A filename with a space in the :files list (standalone "/" separator). check_eq "5. spacey filename in :files" \ "SPACEY" \ -k "$FIX/spacey.k" -s '@spacey@' -t fix # --- The already-loaded guard ------------------------------------------------ # 6. A second declaration of an already-registered symbol is skipped, not an # error: wrapper.k requires decl.k (registers "kit") and then decl2.k # (re-declares "kit"); the wrapper still loads and kit's klammers work. check_eq "6. duplicate declaration is skipped, not an error" \ "Hello Again" \ -k "$FIX/wrapper.k" -s '@greet Again @' -t fix # 7. ...and the skipped declaration's files are NOT loaded. check_fails "7. skipped declaration loads none of its files" \ "only_dup" \ -k "$FIX/wrapper.k" -s '@only_dup@' -t fix # --- Introspection ----------------------------------------------------------- # 8. -m lists the registered klammersets. check_contains "8. -m shows the klammerset symbol" \ "kit" \ -k "$FIX/decl.k" -s 'x' -t fix -m check_contains "9. -m shows the required klammerset too" \ "Utility klammers for engine tests" \ -k "$FIX/decl.k" -s 'x' -t fix -m # --- Errors -------------------------------------------------------------------- # 10. A listed file that does not exist is a clean klammerset error. check_fails "10. missing file in :files" \ "missing.k" \ -k "$FIX/bad_file.k" -s 'x' -t fix # 11. A symbol must be an identifier (starts with a letter; letters, digits, # underscores). check_fails "11. invalid symbol rejected" \ "not valid" \ -k none -s '@@@klammerset 9bad | Bad symbol @@@' -d # --- The search path (symbol ->