A snapshot of the development tree. The substantial changes since the last one:
COMMAND OUTPUT POLICY. The three commands display text in exactly three cases,
and each owns a stream: LOGGING under "-v" greater than 0 and an ERROR before
termination go to STDERR; OUTPUT THE USER ASKED FOR goes to STDOUT. For ktext
that output is a document, so "ktext doc.kt -d | ..." is now safe -- logging
used to share the stream and land inside the document. A bare command prints
its usage and succeeds rather than failing. Colour is emitted only to a
terminal, per stream, and NO_COLOR is honoured.
"-v 1" reports every decision whose outcome you could not have read off your own
input: the klammerset that was loaded and from which file, a font's directory, a
":files" name's file, how "-o" was expanded. Higher levels are the trace.
The commands no longer warn and continue: an anomaly is an error, described with
its location. Two exceptions remain, each for a stated reason -- a condition
that is expected and temporary by design, and a judgment that is a heuristic
rather than exact.
@cond IS NOW A TRUE SPECIAL FORM, resolved at APPLICATION time rather than when
the file is read. Two consequences for a writer:
* a state variable reaches the predicate. "@@@state Flag :value true @@@
@cond *Flag* | T | F @" renders "T"; it used to see the literal "*Flag*" and
silently take the false branch. The document now behaves like a klammer
body, whose arguments are bound before its conditionals are decided.
* nothing in a discarded branch happens -- it is not read, not evaluated, not
expanded. An @eval in the branch not taken used to run anyway.
Its predicate relation is total and strict: true, True, 1; false, False, 0, and
empty; anything else is an error at the @cond rather than silently false.
@eval REACHING OUTSIDE. ":shell" and ":haskell" now keep the command's standard
error out of the document (it appears under "-v 1") and treat a nonzero exit as
an error naming what the command reported. A command that exits nonzero on
purpose -- "grep" finding no match -- says so with "|| true".
KLAMMER SETS. Several combine: "--klammersets a b c" loads all three in the
order given, sharing one namespace, with the definition modes deciding
collisions. "none" means none and may not be combined with other symbols. A
klammerset with symbol X is declared in a file X/X.k, which is what lets two
sets require the same third set without loading it twice.
TESTS. Four new suites: the kdiag command's interface, the @eval primitive's
contract with the outside world, and verbosity at both tiers. Three suites
that could not run on macOS at all now do.
Assembled from dev commit 6c8ee6c22fca.
241 lines
7.7 KiB
Bash
Executable File
241 lines
7.7 KiB
Bash
Executable File
#!/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 ]
|