#!/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 <library> <function>") 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.<state variable> 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 ]
