Files
klammertext/tst/state_test.sh
T

156 lines
5.8 KiB
Bash
Raw Normal View History

#!/bin/bash
#
# state_test.sh — The @@@state system command and *name* substitution.
#
# Added 2026-08-22 by the error-gallery coverage review, which found that no
# suite covered @@@state at all: its two error messages had never been
# printed by any test. What is pinned here, each by outcome:
#
# * :value sets a variable and *name* substitutes it in a klammer BODY
# (bodies are processed at application time). A top-level *name* of a
# document-declared variable does NOT substitute — the same read-time
# asymmetry @cond had before 2026-08-15; if that is ever changed, case 2
# documents today's behaviour and should change with it.
# * :replace replaces an existing value.
# * Redefining WITHOUT :replace is an error that names the remedy.
# * An undefined variable's error lists the frames it searched.
# * A shell environment variable substitutes like any state variable.
#
# NOT to be confused with "state_test", the C++ diagnostic program built from
# state_test.cpp in this directory: that one constructs engine objects and
# prints what it gets, for a person to read, and asserts nothing (see the
# `smoke` target in tst/Makefile). This is the regression suite. The ".sh"
# is the only thing distinguishing them — the same collision as
# eval_test/eval_test.sh, documented there first.
#
# Usage: ./state_test.sh (needs ktext on PATH)
# Exit code: 0 if all tests pass, 1 otherwise.
PASS=0
FAIL=0
KTEXT=ktext
export KT_STATE_PROBE="probe-value" # for the environment-variable case
red=$'\033[31m'
green=$'\033[32m'
bold=$'\033[1m'
reset=$'\033[0m'
# check_eq NAME EXPECTED KTEXT_ARGS... — trimmed stdout equals EXPECTED.
check_eq() {
local name="$1" expected="$2"
shift 2
local out status
out=$("$KTEXT" "$@" 2>/dev/null)
status=$?
out=$(printf '%s' "$out" | sed -e 's/[ \t]*$//' | grep -v '^$')
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_error NAME PATTERN KTEXT_ARGS... — nonzero exit, PATTERN in the
# message. Wrap-insensitive: error prose is justified to 80 columns, so a
# phrase may wrap anywhere.
check_error() {
local name="$1" pattern="$2"
shift 2
local out status
out=$("$KTEXT" "$@" 2>&1)
status=$?
if [ $status -eq 0 ]; then
echo "${red}FAIL${reset} $name — expected an 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}@@@state tests${reset}"
echo "=============="
echo
echo "-- setting and substituting --"
check_eq " 1. :value sets; *name* substitutes in a body" \
"[hello]" \
--klammersets none -s '@@@state V :value hello @@@ @@f : [*V*] @@ @f@' -d
# Documents TODAY'S behaviour: a top-level *name* of a document-declared
# variable is left as written (bodies substitute; the top level does not).
check_eq " 2. a top-level *name* does not substitute (current behaviour)" \
"[*V*]" \
--klammersets none -s '@@@state V :value hello @@@ [*V*]' -d
check_eq " 3. an environment variable substitutes" \
"[probe-value]" \
--klammersets none -s '@@f : [*KT_STATE_PROBE*] @@ @f@' -d
echo
echo "-- replacing --"
check_eq " 4. :replace replaces an existing value" \
"[b]" \
--klammersets none \
-s '@@@state V :value a @@@ @@@state V :replace b @@@ @@f : [*V*] @@ @f@' -d
check_error " 5. redefining without :replace is an error naming the remedy" \
"Use ':replace <new-value>' to replace the current value" \
--klammersets none -s '@@@state V :value a @@@ @@@state V :value b @@@' -d
echo
echo "-- the undefined variable --"
check_error " 6. an undefined *name* in a body is a located error" \
'Variable "Missing" not defined' \
--klammersets none -s '@@f : [*Missing*] @@ @f@' -d
check_error " 7. ... that lists the frames it searched" \
"(searched:" \
--klammersets none -s '@@f : [*Missing*] @@ @f@' -d
# 8-9: bash EXPORTS SHELL FUNCTIONS, and they arrive in the environment as
# "BASH_FUNC_name%%=() { ... }". Those were taken as state variables and
# emitted into the @eval Python as "class K" attributes, where
# "BASH_FUNC_mdpdf%%" is not a valid identifier -- so a single "export -f"
# anywhere in a user's shell profile made EVERY @eval fail with a Python
# SyntaxError, reported against whichever .k file held the first one.
# Nothing in the message named the environment. Found 2026-09-04.
#
# Run in a subshell so the exported function does not leak into the rest of
# the suite.
probe() { # probe NAME EXPECTED ARGS...
local name="$1" expected="$2"; shift 2
local out
out=$(bash -c '
ktexport_fn() { echo hi; }
export -f ktexport_fn
exec "$@"' _ "$KTEXT" "$@" 2>&1)
if [ "$out" = "$expected" ]; then
echo "${green}PASS${reset} $name"; PASS=$((PASS+1))
else
echo "${red}FAIL${reset} $name"
echo " expected: [$expected]"
echo " got: [$(printf '%s' "$out" | head -3 | tr '\n' ' ')]"
FAIL=$((FAIL+1))
fi
}
probe " 8. an exported shell function does not break @eval" \
"4" \
--klammersets none -s '@eval 2 + 2 @' -d
probe " 9. ... and a real environment variable still reaches class K" \
"probe-value" \
--klammersets none -s '@eval K.KT_STATE_PROBE @' -d
echo
echo "=============="
echo "Results: ${green}$PASS passed${reset}, ${red}$FAIL failed${reset}"
[ $FAIL -eq 0 ]