#!/bin/bash # # alone_test.sh — Regression tests for an argument type's :alone value. # # An optional argument has three possible values, not two: # # 1. the option name is not written at all -> the default # 2. the name is written alone, with no value -> the type's :alone value # 3. the name is written with a value -> that value # # The :alone value is declared by the argument type (@@@argtype ... :alone), # never by a klammer's parameter declaration. A default is what a klammer # means by silence and is properly per-klammer; a bare option name must read # the same way in every klammer, or a writer cannot know what it means # without consulting each signature. # # The bool type declares :alone true, which is where the convention that a # bare boolean option means true comes from. This is a declaration like any # other type's, not an engine special case for booleans. # # A type whose pattern matches running text cannot declare an :alone value: # an option's value runs to the next bar or option name, so ":opt some words" # would take "some words" as the value and the alone value would never be # reached. That is a definition-time error. # # Engine tier: these tests run with --klammersets none and define their own argtypes # and klammers inline, so they do not depend on the Standard Klammer Set. # # Usage: ./alone_test.sh (LSan suppressions come from env/runtime.env) # Exit code: 0 if all tests pass, 1 otherwise. PASS=0 FAIL=0 KTEXT=ktext K=${KLAMMERTEXT_HOME:?KLAMMERTEXT_HOME must be set} red=$'\033[31m' green=$'\033[32m' bold=$'\033[1m' reset=$'\033[0m' # strip leading/trailing blank lines and surrounding whitespace trim() { awk '{ sub(/[ \t\r]+$/, "") } { line[NR]=$0 } END { f=1; while (f<=NR && line[f]=="") f++; l=NR; while (l>=1 && line[l]=="") l--; for (i=f;i<=l;i++) print line[i] }'; } # check_eq TEST_NAME EXPECTED KTEXT_ARGS... # Runs ktext, expects exit status 0, and compares trimmed stdout to EXPECTED. check_eq() { local test_name="$1" local expected="$2" shift 2 local output status output=$("$KTEXT" "$@" 2>/tmp/alone_test_err.$$) status=$? output=$(printf '%s' "$output" | trim) if [ $status -ne 0 ]; then echo "${red}FAIL${reset} $test_name — ktext exited $status" echo " stderr: $(head -3 /tmp/alone_test_err.$$)" FAIL=$((FAIL + 1)) return fi if [ "$output" = "$expected" ]; then echo "${green}PASS${reset} $test_name" PASS=$((PASS + 1)) else echo "${red}FAIL${reset} $test_name" echo " expected: [$expected]" echo " got: [$output]" FAIL=$((FAIL + 1)) fi } # check_error TEST_NAME PATTERN KTEXT_ARGS... # Runs ktext, expects a NONZERO exit status and PATTERN in the message. check_error() { local test_name="$1" local pattern="$2" shift 2 local output status output=$("$KTEXT" "$@" 2>&1) status=$? if [ $status -eq 0 ]; then echo "${red}FAIL${reset} $test_name — expected an error but ktext succeeded" FAIL=$((FAIL + 1)) return fi if echo "$output" | grep -qF "$pattern"; then echo "${green}PASS${reset} $test_name" PASS=$((PASS + 1)) else echo "${red}FAIL${reset} $test_name — expected error to contain [$pattern]" echo " output: $(echo "$output" | head -5)" FAIL=$((FAIL + 1)) fi } # A type with both a default and an alone value, and a klammer using it. DEPTH='@@@argtype depth | a table of contents depth :pattern \d+ :default 0 :alone 3 @@@' DK='@@d :n.depth : [*n*] @@' # A type with an alone value but no default. MARK='@@@argtype mark | a mark character :pattern [-*+] :alone * @@@' MK='@@m :c.mark : [*c*] @@' echo "${bold}Argument type :alone value tests${reset}" echo "================================" echo # --- The three values of an optional argument --- check_eq \ " 1. name absent — the default" \ "[0]" \ --klammersets none -s "$DEPTH $DK @d@" -d check_eq \ " 2. name written alone — the type's alone value" \ "[3]" \ --klammersets none -s "$DEPTH $DK @d :n @" -d check_eq \ " 3. name written with a value — that value" \ "[7]" \ --klammersets none -s "$DEPTH $DK @d :n 7 @" -d # --- A type with an alone value but no default --- check_eq \ " 4. no default declared — absent is empty" \ "[]" \ --klammersets none -s "$MARK $MK @m@" -d check_eq \ " 5. no default declared — alone still applies" \ "[*]" \ --klammersets none -s "$MARK $MK @m :c @" -d # --- The alone value belongs to the type, so every parameter of that # type gets it, and a parameter default does not disturb it --- check_eq \ " 6. two parameters of one type share the alone value" \ "[3][3]" \ --klammersets none -s "$DEPTH @@d2 :a.depth :b.depth : [*a*][*b*] @@ @d2 :a :b @" -d check_eq \ " 7. a parameter default overrides the type default, not the alone value" \ "[5]|[3]" \ --klammersets none -s "$DEPTH @@d3 :n.depth 5 : [*n*] @@ @d3@|@d3 :n @" -d # --- bool: the convention that a bare boolean option means true --- check_eq \ " 8. bool written alone is true" \ "[true]" \ --klammersets none -s '@@b :f.bool : [*f*] @@ @b :f @' -d check_eq \ " 9. bool written with false stays false" \ "[false]" \ --klammersets none -s '@@b :f.bool : [*f*] @@ @b :f false @' -d check_eq \ "10. bool absent with no default is empty" \ "[]" \ --klammersets none -s '@@b :f.bool : [*f*] @@ @b@' -d check_eq \ "11. bool absent with a true parameter default" \ "[true]" \ --klammersets none -s '@@b :f.bool true : [*f*] @@ @b@' -d check_eq \ "12. bool false explicitly against a true default" \ "[false]" \ --klammersets none -s '@@b :f.bool true : [*f*] @@ @b :f false @' -d # --- The value reaching @eval --- check_eq \ "13. python value of a bool written alone" \ "True" \ --klammersets none -s '@@b :f.bool : @eval repr(K.f) @ @@ @b :f @' -d check_eq \ "14. python value of an absent bool" \ "None" \ --klammersets none -s '@@b :f.bool : @eval repr(K.f) @ @@ @b@' -d check_eq \ "15. python value of a user type written alone" \ "'3'" \ --klammersets none -s "$DEPTH @@d4 :n.depth : @eval repr(K.n) @ @@ @d4 :n @" -d # --- Types that cannot delimit a bare option name --- check_error \ "16. :alone refused on a type matching running text" \ "cannot declare an :alone value" \ --klammersets none -s '@@@argtype loose | anything at all :alone x @@@' -d check_error \ "17. :alone refused on an explicit match-everything pattern" \ "cannot declare an :alone value" \ --klammersets none -s '@@@argtype loose | anything :pattern (?:.^|\n)* :alone x @@@' -d check_error \ "18. an alone value must match its own type's pattern" \ "does not match its own pattern" \ --klammersets none -s '@@@argtype depth | a depth :pattern \d+ :alone many @@@' -d # --- Delimitation: an option value still runs to the next bar or option # name, so text after a bare name is taken as the value and rejected # by the type. This is what makes the guard above necessary. check_error \ "19. text after a bare option name is taken as its value" \ "does not match" \ --klammersets none -s "$DEPTH $DK @d :n some words @" -d check_eq \ "20. a bar separates a bare option name from following text" \ "[3]two" \ --klammersets none -s "$DEPTH @@d5 :n.depth | t : [*n*]*t* @@ @d5 :n | two @" -d # --- A type with no alone value is unchanged: a bare name is empty --- check_eq \ "21. bare option of a type with no alone value is empty" \ "[]" \ --klammersets none -s '@@s :t.word : [*t*] @@ @s :t @' -d echo echo "================================" echo "Passed: $PASS Failed: $FAIL" rm -f /tmp/alone_test_err.$$ [ $FAIL -eq 0 ]