#!/bin/bash # # cond_test.sh — Regression tests for @cond argument delimitation. # # These tests pin down the fix for the bug witnessed by tst/cond_test.kt: # a defined klammer that contains its own bar separators (e.g. @frac a | b @) # nested inside a @cond branch caused @cond to miscount bars and reject the # input with "There should only be one or two bar characters". # # Root cause: @cond delimited its arguments by counting EVERY bar in its flat # katom range, conflating the inner klammer's bars (which belong to the inner # klammer's arity) with @cond's own separators. The fix counts only the bars # at nesting depth 0 within the @cond span (cond_separator_bars() in # mac/machine.cpp), so argument boundaries follow the span tree. # # See doc/cond_evaluation_order.md for the full description and the # theoretical basis (operadic arity, the precedence-order proposition, and # @cond as a non-strict special form). # # Usage: ./cond_test.sh (LSan suppressions come from mac/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() { sed -e 's/[[:space:]]*$//' | sed -e '/./,$!d' | tac | sed -e '/./,$!d' | tac; } # 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/cond_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/cond_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_contains TEST_NAME SUBSTRING KTEXT_ARGS... # Runs ktext, expects exit status 0, and checks that stdout contains SUBSTRING. check_contains() { local test_name="$1" local needle="$2" shift 2 local output status output=$("$KTEXT" "$@" 2>&1) status=$? if [ $status -ne 0 ]; then echo "${red}FAIL${reset} $test_name — ktext exited $status" echo " output: $(echo "$output" | head -3)" FAIL=$((FAIL + 1)) return fi if echo "$output" | grep -qF "$needle"; then echo "${green}PASS${reset} $test_name" PASS=$((PASS + 1)) else echo "${red}FAIL${reset} $test_name — expected to contain [$needle]" echo " output: $(echo "$output" | head -3)" 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 -3)" FAIL=$((FAIL + 1)) fi } FRAC='@@frac a | b : *a*/*b* @@' echo "${bold}@cond argument delimitation tests${reset}" echo "=================================" echo # --- The reported regression (defined klammer with bars inside a branch) --- check_eq \ " 1. inner klammer with bars, true branch" \ "1/2" \ -s "$FRAC @cond true | @frac 1 | 2 @ | @frac 2 | 1 @ @" -d check_eq \ " 2. inner klammer with bars, false branch" \ "2/1" \ -s "$FRAC @cond false | @frac 1 | 2 @ | @frac 2 | 1 @ @" -d # --- The exact witness file from the bug report --- check_contains \ " 3. tst/cond_test.kt @cond_klammer_test true" \ "1/2" \ "$K/tst/cond_test.kt" -d -s '@cond_klammer_test true @' check_contains \ " 4. tst/cond_test.kt @cond_klammer_test false" \ "2/1" \ "$K/tst/cond_test.kt" -d -s '@cond_klammer_test false @' # --- Plain @cond unaffected by the change --- check_eq " 5. plain two-bar, true" "yes" -s '@cond true | yes | no @' -d check_eq " 6. plain two-bar, false" "no" -s '@cond false | yes | no @' -d check_eq " 7. one-bar, true" "shown" -s '@cond true | shown @' -d check_eq " 8. one-bar, false (empty)" "" -s '@cond false | shown @' -d # --- Primitives and nesting inside @cond --- check_eq " 9. @eval in a branch" "42" -s '@cond true | @eval 6*7 @ | no @' -d check_eq "10. @eval as the predicate" "yes" -s '@cond @eval 1==1 @ | yes | no @' -d check_eq "11. nested @cond in a branch" "B" -s '@cond true | @cond false | A | B @ | C @' -d # --- A klammer with its own bars (double-bar / cells) inside a branch --- check_eq "12. klammer with internal bar in a branch" \ "x+y" \ -s '@@two a | b : *a*+*b* @@ @cond true | @two x | y @ | z @' -d # --- Genuine arity errors must still be rejected --- check_error "13. three top-level bars is still an error" \ "one or two bar characters" \ -s '@cond true | a | b | c @' -d check_error "14. zero bars is still an error" \ "one or two bar characters" \ -s '@cond true @' -d rm -f /tmp/cond_test_err.$$ echo echo "=================================" echo "Results: ${green}$PASS passed${reset}, ${red}$FAIL failed${reset}" [ $FAIL -eq 0 ]