Files
klammertext/tst/kdesc_test.sh
T

259 lines
14 KiB
Bash
Raw Normal View History

#!/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, --typeface);
# * -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
# -u describes how to write a Unicode character. Asserted by OUTCOME, on
# the two things the section must carry however its prose is rewritten:
# the ^...^ notation it teaches, and a rendered character beside its code
# point -- the whole point of the section is that a reader sees the glyph
# they are about to write. It does NOT pin the prose, which is meant to
# grow (as of 2026-08-25 the section predates the sequence form and the
# typeface klammer, and does not yet mention either).
shows " 4a. -u describes the Unicode syntax" '\^0418\^' "$KDESC" -u
# The rendered glyph, as UTF-8 BYTE escapes: macOS ships bash 3.2, which
# expands $'\xHH' but not $'\U...' (that difference failed this suite's
# sibling on Olion, 2026-08-23).
shows " 4b. ... and shows the characters" $'\xf0\x93\x8a\xaa' "$KDESC" -u
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
# The regex goes through @c, which processes caret forms: a bare "[^^@#|]+"
# displayed as "[^@#|]+", a different regex, until the generator quoted its
# carets. These two are the patterns that carry a caret before a special.
shows " 9a. ... a regex keeps a caret before a special" '\[\^\^@#\|\]\+' "$KDESC" --katoms full
shows " 9b. ... and a caret before a colon" '\[@\|#\^:\*\]' "$KDESC" --katoms full
shows " 9c. the types are grouped under titles" '^ +Klammer definition *$' "$KDESC" --katoms
# The table is wider than the text by the klammer's own argument, so the
# paragraphs above it keep the text width (72) while a table row exceeds it.
absent "10a. the prose above the table keeps the text width" \
'^[A-Za-z].{72,}$' "$KDESC" --katoms
shows "10b. ... while the table is wider than the text" '^ +Index .{72,}$' "$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
# The help text and the error must name the flag by its CURRENT spelling:
# both still said --font for three weeks after the 2026-08-25 rename, and # retired-ok
# the retired-name guard's entries did not match the bare word.
shows "27a. --typeface help names --typeface install" '--typeface install' "$KDESC" --typeface help
shows "27b. an unknown typeface word names the flag" 'Unrecognized typeface command: --typeface' "$KDESC" --typeface nonsense
# --typeface lists the font store as one @table per search directory,
# rendered from ref/ (@klammertext_fonts, 2026-09-23). The search path is
# DESCRIBED, not validated: a directory that does not exist is a row, never
# an error -- resolve_font skips one silently, and a fresh install has no
# ~/.klammertext/fonts yet, so an error there would fail every new user's
# first "kdesc --typeface". A typeface an earlier directory also holds is
# shadowed, and the LATER row says by which directory.
FONTS=$(mktemp -d /tmp/kdesc_fonts.XXXXXX)
shows "27c. --typeface lists the default set" 'Klammertext default typefaces' "$KDESC" --typeface
shows "27d. a missing search directory is a row" 'directory does not exist' \
env KLAMMERTEXT_FONTS="$FONTS/none" "$KDESC" --typeface
env KLAMMERTEXT_FONTS="$FONTS/none" "$KDESC" --typeface >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo "${green}PASS${reset} 27e. ... and exits 0"; PASS=$((PASS+1))
else
echo "${red}FAIL${reset} 27e. a missing search directory exits nonzero"; FAIL=$((FAIL+1))
fi
shows "27f. an empty search directory says so" 'no fonts' env KLAMMERTEXT_FONTS="$FONTS" "$KDESC" --typeface
mkdir -p "$FONTS/crimson-pro"
printf "@font-face { font-family: 'Crimson Pro'; }\n" > "$FONTS/crimson-pro.css"
shows "27g. an installed typeface shadows the default one, and the default row says so" \
"^ *Crimson Pro .*$FONTS" env KLAMMERTEXT_FONTS="$FONTS" "$KDESC" --typeface
# Under a search path that installs nothing (the machine's own fonts may
# shadow a default one, so the default path is not a controlled case).
absent "27h. ... and a table with nothing shadowed has no such column" 'Shadowed by' \
env KLAMMERTEXT_FONTS="$FONTS/none" "$KDESC" --typeface
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 :: <b>*s*</b> @@\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 +ansi 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 ::: <em class="mine">*text*</em> @@\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 :: <b>*s*</b> @@\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 \[<text>\]' "$KDESC"
shows "29. -i shows its filename" '\-i <filename>' "$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 \[<text>\]' | 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