#!/bin/bash # # target_test.sh — The @@@target system command's :after_apply phase list. # # Added 2026-08-22, when the txt target became the first to declare TWO # phases and found the list separator undocumented and unguarded: two bare # specs written without " ; " were glued into one Python call and failed at # render time with a SyntaxError located at "phase, line 1" rather than at # the declaration. What is pinned here: # # * several phases separated by " ; " run, in order, each seeing its # predecessor's result; # * a bare spec containing whitespace is a DEFINITION-TIME error naming # the " ; " convention (the guard in Target::add_after_apply); # * a mode-tagged spec (":cpp ") is exempt from the # whitespace guard -- its library is a filename, and filenames may # contain spaces, which is why the separator is ";" at all. # # SKS-independent: --klammersets none, an inline fixture target, and a # fixture Python module placed next to the input file (the module-resolution # rule adds the input file's directory to sys.path). # # NOT to be confused with "target_test", the C++ diagnostic program built # from target_test.cpp in this directory (asserts nothing; see the `smoke` # target in tst/Makefile). The ".sh" is the only distinguisher -- the third # such collision, after eval_test and state_test. # # Usage: ./target_test.sh (needs ktext on PATH) # Exit code: 0 if all tests pass, 1 otherwise. PASS=0 FAIL=0 KTEXT=ktext red=$'\033[31m' green=$'\033[32m' bold=$'\033[1m' reset=$'\033[0m' DIR=$(mktemp -d) trap 'rm -rf "$DIR"' EXIT # Two phases that mark their passage; order is observable in the output. cat > "$DIR/phasefix.py" <<'EOF' def one(text, K=None): return text + "+ONE" def two(text, K=None): return text + "+TWO" def width(text, K=None): # K is the state's "class K"; a fixture state variable proves the phase # receives it (attributes arrive as strings unless the argtype is typed). return text + "|" + str(K.Fixture_width) EOF check_eq() { # check_eq NAME EXPECTED INPUT_FILE_TEXT TARGET_DECL local name="$1" expected="$2" body="$3" decl="$4" printf '%s' "$body" > "$DIR/in.kt" local out status out=$("$KTEXT" "$DIR/in.kt" --klammersets none -s "$decl" -t t -d 2>/dev/null) status=$? out=$(printf '%s' "$out" | tr -d '\n') if [ $status -eq 0 ] && [ "$out" = "$expected" ]; then echo "${green}PASS${reset} $name"; PASS=$((PASS+1)) else echo "${red}FAIL${reset} $name" echo " expected: [$expected]"; echo " got: [$out] (exit $status)" FAIL=$((FAIL+1)) fi } check_def_error() { # check_def_error NAME PATTERN TARGET_DECL local name="$1" pattern="$2" decl="$3" local out status out=$("$KTEXT" --klammersets none -s "$decl hello" -t t -d 2>&1) status=$? if [ $status -eq 0 ]; then echo "${red}FAIL${reset} $name — expected a definition error but ktext succeeded" FAIL=$((FAIL+1)); return fi if printf '%s' "$out" | tr '\n' ' ' | tr -s ' ' | grep -qF "$pattern"; then echo "${green}PASS${reset} $name"; PASS=$((PASS+1)) else echo "${red}FAIL${reset} $name — expected error to contain [$pattern]" echo " got: $(printf '%s' "$out" | sed 's/\x1b\[[0-9;]*m//g' | tr '\n' ' ' | head -c 200)" FAIL=$((FAIL+1)) fi } echo "${bold}@@@target :after_apply tests${reset}" echo "============================" echo echo "-- several phases: ';'-separated, run in declared order --" check_eq " 1. one phase runs" \ "text+ONE" "text" \ '@@@target t | test | :after_apply phasefix.one @@@' check_eq " 2. two phases run in order (ONE then TWO)" \ "text+ONE+TWO" "text" \ '@@@target t | test | :after_apply phasefix.one ; phasefix.two @@@' check_eq " 3. ... and reversing the list reverses the order" \ "text+TWO+ONE" "text" \ '@@@target t | test | :after_apply phasefix.two ; phasefix.one @@@' echo echo "-- a phase receives the state as K --" check_eq " 4. K. is readable inside a phase" \ "text|42" "text" \ '@@@state Fixture_width :value 42 @@@ @@@target t | test | :after_apply phasefix.width @@@' echo echo "-- the missing separator is a DEFINITION-time error --" check_def_error " 5. two bare specs without ';' are rejected at the declaration" \ "separated by \" ; \"" \ '@@@target t | test | :after_apply phasefix.one phasefix.two @@@' check_def_error " 6. ... and the error names the offending spec" \ "phasefix.one phasefix.two" \ '@@@target t | test | :after_apply phasefix.one phasefix.two @@@' echo echo "-- a mode-tagged spec may contain spaces (its library is a filename) --" # Declaration-time only: the :cpp library is never dlopened unless the # target renders, so declaring it under another name proves the exemption # without needing a real library. check_eq " 7. a ':cpp lib func' spec passes the declaration guard" \ "text+ONE" "text" \ '@@@target u | unused | :after_apply ^:cpp some lib func @@@ @@@target t | test | :after_apply phasefix.one @@@' echo echo "============================" echo "Results: ${green}$PASS passed${reset}, ${red}$FAIL failed${reset}" [ $FAIL -eq 0 ]