#!/bin/bash # # kdesc_test.sh — the kdesc command's interface. # # The flag structure was reorganised 2026-08-11 # (notes/modifying_the_kdesc_arguments.md) on two rules: # # * a flag a user reaches for often gets a single letter (-k klammers, # -t targets, -c character codes, -i input); a more specialised topic gets # a multi-letter name (--argtypes, --katoms, --rewrite, --optionsets, # --coverage, --klammersets, --font); # * -v says how much to show about the command's PROCESSING and never what # its RESULT contains. So the katom regex column is "--katoms full" and # the coverage file column is "--coverage all", not verbosity levels. # # "-k" is the one rename that changes a name's MEANING rather than retiring # it: it used to show katom types (now --katoms). A name-based guard cannot # catch that -- the old spelling still works and does something else -- so # these tests pin the new meaning down. # # Usage: ./kdesc_test.sh (needs KLAMMERTEXT_HOME set; kdesc on PATH) # Exit code: 0 if all tests pass, 1 otherwise. PASS=0 FAIL=0 KDESC=kdesc K=${KLAMMERTEXT_HOME:?KLAMMERTEXT_HOME must be set} DIR="$(cd "$(dirname "$0")" && pwd)/coverage" red=$'\033[31m' green=$'\033[32m' bold=$'\033[1m' reset=$'\033[0m' plain() { sed 's/\x1b\[[0-9;]*m//g'; } # GNU coreutils' "timeout" is NOT present on macOS, and Homebrew's is named # "gtimeout", so a bare "timeout" made this suite fail WHOLESALE there -- every # case, because the command never ran at all (found on Olion, 2026-08-16). The # guard is a safety net against a hung command, not part of what is being # tested, so it is optional: bound the command where the tool exists, run it # directly where it does not. if command -v timeout >/dev/null 2>&1; then limited() { timeout 60 "$@"; } elif command -v gtimeout >/dev/null 2>&1; then limited() { gtimeout 60 "$@"; } else limited() { "$@"; } fi # shows NAME PATTERN CMD... — exits 0 and the output matches PATTERN. shows() { local name="$1" pattern="$2"; shift 2 local out status out=$(limited "$@" 2>&1 | plain); status=$? if [ $status -gt 128 ]; then echo "${red}FAIL${reset} $name — died by signal $((status-128))"; FAIL=$((FAIL+1)); return fi if printf '%s' "$out" | grep -Eq -- "$pattern"; then echo "${green}PASS${reset} $name"; PASS=$((PASS+1)) else echo "${red}FAIL${reset} $name — no line matching [$pattern]" echo " got: $(printf '%s' "$out" | head -2)"; FAIL=$((FAIL+1)) fi } # absent NAME PATTERN CMD... — the output does NOT match PATTERN. absent() { local name="$1" pattern="$2"; shift 2 if limited "$@" 2>&1 | plain | grep -Eq -- "$pattern"; then echo "${red}FAIL${reset} $name — unexpected match [$pattern]"; FAIL=$((FAIL+1)) else echo "${green}PASS${reset} $name"; PASS=$((PASS+1)) fi } echo "${bold}kdesc interface tests${reset}" echo "=====================" echo echo "-- the single-letter flags --" shows " 1. -k lists klammers" '@table' "$KDESC" -k shows " 2. -t lists targets" 'html' "$KDESC" -t shows " 3. -c lists character codes" '.' "$KDESC" -c shows " 4. -i reads the named input" '@x' "$KDESC" -i "$DIR/split.k" -k echo echo "-- the multi-letter topics --" shows " 5. --katoms lists katom types" 'katom' "$KDESC" --katoms shows " 6. --argtypes lists argtypes" 'fraction' "$KDESC" --argtypes shows " 7. --rewrite lists rewrites" '.' "$KDESC" --rewrite shows " 8. --optionsets lists sets" 'caption_args' "$KDESC" --optionsets echo echo "-- result detail is a word, not a verbosity level --" shows " 9. --katoms full adds the regex column" 'Regex' "$KDESC" --katoms full absent "10. --katoms alone omits it" 'Regex' "$KDESC" --katoms shows "11. --coverage all adds the file column" 'sks/table/table\.k' "$KDESC" --coverage all absent "12. --coverage alone omits it" 'sks/table/table\.k' "$KDESC" --coverage # "all" is one word for both kinds of missing information: if you want some of # it you probably want all of it. It shows an empty REPORTING category -- a # designer's checklist of the shapes a klammer set can have -- but not an # empty PROBLEM one, which would state the opposite of the "Needs attention" # banner above it. # Against a FIXTURE, not the SKS: which categories are empty there changes as # the klammer set is worked on, and a test pinned to that state fails for a # reason that has nothing to do with what it is testing. (It did: adopting # ".*" in the SKS made "All targets, declared" non-empty.) shows "12a. --coverage all shows an empty reporting category" \ 'All targets, declared \(0\)' "$KDESC" --klammersets none -i "$DIR/cycle.k" --coverage all absent "12b. ... hidden without it" \ 'All targets, declared \(0\)' "$KDESC" --klammersets none -i "$DIR/cycle.k" --coverage absent "12c. an empty problem category stays hidden" \ 'Declared but never defined \(0\)' "$KDESC" --klammersets none -i "$DIR/cycle.k" --coverage all absent "13. -v adds no result detail" 'Regex' "$KDESC" --katoms -v 3 echo echo "-- -v is about processing only --" shows "14. -v names the input it read" 'input_filenames' "$KDESC" -i "$DIR/clean.k" -t -v 1 absent "15. ... and is silent without it" 'input_filenames' "$KDESC" -i "$DIR/clean.k" -t echo echo "-- the klammer search --" shows "16. a name matches" '@table' "$KDESC" -k table absent "17. ... and others are excluded" '@document' "$KDESC" -k table shows "18. matching is case-insensitive" '@table' "$KDESC" -k TABLE shows "19. a description word matches" '@' "$KDESC" -k verbatim # The words after -k are one phrase, and whitespace is collapsed on both # sides -- a description written across several lines in a .k file must still # match a phrase typed on one. shows "20. several words are one phrase" '@' "$KDESC" -k displayed verbatim shows "21. ... with runs of space collapsed" '@' "$KDESC" -k displayed verbatim shows "22. no match says so" 'No klammer names or descriptions contained "zzqq"\.' "$KDESC" -k zzqq absent "23. ... and lists nothing" '^ @' "$KDESC" -k zzqq echo echo "-- the subcommand words --" shows "24. --coverage help explains" 'Coverage commands' "$KDESC" --coverage help shows "25. --katoms help explains" 'Katom commands' "$KDESC" --katoms help shows "26. an unknown coverage word" 'Unrecognized coverage command' "$KDESC" --coverage nonsense shows "27. an unknown katom word" 'Unrecognized katom command' "$KDESC" --katoms nonsense echo echo "-- provenance: -i shows what the INPUT defines --" # A klammerset is loaded so the input can be ANALYSED against it, but it is # not what the user asked about. A designer wants the klammers this file # defines; a reader of an unfamiliar document wants the custom ones it # carries. So "-i" restricts every listing to definitions from outside the # loaded klammersets -- while the klammersets stay loaded. PROV=$(mktemp /tmp/kdesc_prov.XXXXXX.k) printf '@@shout.k s : Say it loudly @@\n@@shout.html :: *s* @@\n' > "$PROV" shows "32. -i lists the input's own klammer" '@shout' "$KDESC" -i "$PROV" -k absent "33. ... and not the klammerset's" '@table' "$KDESC" -i "$PROV" -k shows "34. without -i the klammerset is shown" '@table' "$KDESC" -k # The klammerset is still LOADED: a coverage derived from a klammer it # supplies proves the analysis saw it even though the report does not. DERIVE=$(mktemp /tmp/kdesc_derive.XXXXXX.k) printf '@@wave.k s : Wave @@\n@@wave :: @i *s* @ @@\n' > "$DERIVE" shows "35. coverage is derived through the unlisted klammerset" \ '@wave +html pdf tex txt +from @i' "$KDESC" -i "$DERIVE" --coverage absent "36. ... and the klammerset is not reported" '@table' "$KDESC" -i "$DERIVE" --coverage # A klammer the input REDEFINES came from the klammerset, but the input # changed it -- which is exactly what a reader needs to know. REDEF=$(mktemp /tmp/kdesc_redef.XXXXXX.k) printf '@@i.html ::: *text* @@\n' > "$REDEF" shows "37. a redefined klammer is the input's too" '@i' "$KDESC" -i "$REDEF" -k # An input that declares its own klammerset is still the input. DECL=$(mktemp -d /tmp/kdesc_decl.XXXXXX) mkdir -p "$DECL/own" printf '@@@klammerset own | A designer set @@@\n@@yell.k s : Yell @@\n@@yell.html :: *s* @@\n' \ > "$DECL/own/own.k" shows "38. an input declaring a klammerset is not filtered out" \ '@yell' "$KDESC" -i "$DECL/own/own.k" -k rm -rf "$PROV" "$DERIVE" "$REDEF" "$DECL" echo echo "-- the usage text --" shows "28. -k shows its optional argument" '\-k \[\]' "$KDESC" shows "29. -i shows its filename" '\-i ' "$KDESC" # Ordered by likely use: the single letters come before the long names. # Line numbers, not a multi-line pattern -- grep is line-oriented. usage=$(limited "$KDESC" 2>&1 | plain) k_line=$(printf '%s\n' "$usage" | grep -n -- '-k \[\]' | head -1 | cut -d: -f1) katoms_line=$(printf '%s\n' "$usage" | grep -n -- '--katoms' | head -1 | cut -d: -f1) if [ -n "$k_line" ] && [ -n "$katoms_line" ] && [ "$k_line" -lt "$katoms_line" ]; then echo "${green}PASS${reset} 30. -k is listed before --katoms"; PASS=$((PASS+1)) else echo "${red}FAIL${reset} 30. usage order: -k at line $k_line, --katoms at $katoms_line" FAIL=$((FAIL+1)) fi echo echo "-- a no-result search is not an error --" limited "$KDESC" -k zzqq >/dev/null 2>&1 if [ $? -eq 0 ]; then echo "${green}PASS${reset} 31. finding nothing exits 0"; PASS=$((PASS+1)) else echo "${red}FAIL${reset} 31. finding nothing exits nonzero"; FAIL=$((FAIL+1)) fi echo echo "=====================" echo "Results: ${PASS} passed, ${FAIL} failed" [ "$FAIL" -eq 0 ] || exit 1 exit 0