#!/bin/bash
#
# command_option_test.sh — Command-line option parsing (mac/argv.cpp).
#
# The commands' own argument handling, which no suite covered: argv_test.cpp
# exercises Argv but asserts nothing, so two defects lived there unnoticed.
#
#   * An option declared with opt() takes a value.  Written LAST with nothing
#     after it -- "kdesc -v", "ktext doc.kt -t" -- parse_optional() read one
#     past the end of the word vector and the command died with SIGSEGV,
#     naming nothing.  Two bounds checks sat commented out at that spot; they
#     would have returned a half-parsed option instead of reporting the
#     mistake.  It is now a located argument error.
#   * kdesc and kdiag printed an error and then exited 0, so a script could
#     not tell a failed run from a successful one.  Both return 1 now, as
#     ktext already did.
#
# Also here: two required positional arguments.  parse_positional() consumes
# one per required name, but regex_split_prefix() requires its match at
# position 0 and returned the remainder with the separating space intact, so
# the second was always "not found".  Latent, because no shipped command
# declares two -- tst/argv_test.cpp is the only program that does.
#
# Usage: ./command_option_test.sh   (needs KLAMMERTEXT_HOME set; commands on PATH)
# Exit code: 0 if all tests pass, 1 otherwise.

PASS=0
FAIL=0
K=${KLAMMERTEXT_HOME:?KLAMMERTEXT_HOME must be set}
TSTDIR="$(cd "$(dirname "$0")" && pwd)"

red=$'\033[31m'
green=$'\033[32m'
bold=$'\033[1m'
reset=$'\033[0m'

# A signal death is not an error exit: 128+n, and the point of these tests is
# the difference.  Report it distinctly so a crash can never read as a pass.
died_by_signal() { [ "$1" -gt 128 ]; }

# error NAME PATTERN CMD... — nonzero exit, no signal, PATTERN in the output.
error() {
    local name="$1" pattern="$2"; shift 2
    local out status
    out=$("$@" 2>&1); status=$?
    if died_by_signal $status; then
        echo "${red}FAIL${reset} $name — died by signal $((status-128))"; FAIL=$((FAIL+1)); return
    fi
    if [ $status -eq 0 ]; then
        echo "${red}FAIL${reset} $name — reported an error but exited 0"; FAIL=$((FAIL+1)); return
    fi
    if printf '%s' "$out" | tr '\n' ' ' | tr -s ' ' | grep -qF -- "$pattern"; then
        echo "${green}PASS${reset} $name"; PASS=$((PASS+1))
    else
        echo "${red}FAIL${reset} $name — expected [$pattern]"
        echo "       got: $(printf '%s' "$out" | head -2)"; FAIL=$((FAIL+1))
    fi
}

# ok NAME CMD... — exits 0 and does not die by signal.
ok() {
    local name="$1"; shift
    local status
    "$@" >/dev/null 2>&1; status=$?
    if died_by_signal $status; then
        echo "${red}FAIL${reset} $name — died by signal $((status-128))"; FAIL=$((FAIL+1))
    elif [ $status -ne 0 ]; then
        echo "${red}FAIL${reset} $name — exit $status"; FAIL=$((FAIL+1))
    else
        echo "${green}PASS${reset} $name"; PASS=$((PASS+1))
    fi
}

echo "${bold}Command option tests${reset}"
echo "===================="
echo

echo "-- an option written last, with no value --"
error " 1. kdesc -v"                "needs a value" kdesc -v
error " 2. kdesc with a flag first" "needs a value" kdesc --katoms -v
error " 3. kdiag -v"                "needs a value" kdiag -v
error " 4. ktext -t"                "needs a value" ktext -s '@i-x' -t
error " 5. the message names the option" "-v <level>" kdesc -v
error " 6. ... and how to get the usage"  "for the list of arguments" kdesc -v

echo
echo "-- an option value that fails its pattern (gallery review) --"
error "21. a non-integer verbosity is an error" "is not a verbosity level" kdesc -v abc
# The location must be absent, not a C++ source file: the default Locator
# captures the throw site (argv.cpp, line N) unless the throw says
# Locator::none() -- the leak class the error gallery found 2026-08-22.
out=$(kdesc -v abc 2>&1)
if printf '%s' "$out" | grep -q '\.cpp, line'; then
    echo "${red}FAIL${reset} 22. the error leaks a C++ source location"
    echo "       got: $(printf '%s' "$out" | grep '\.cpp, line' | head -1)"; FAIL=$((FAIL+1))
else
    echo "${green}PASS${reset} 22. no C++ source location in the error"; PASS=$((PASS+1))
fi

echo
echo "-- the same options WITH a value still work --"
# NOT "kdesc -v 1": show_usage() treats exactly "<command> -v <n>" as a
# request for the usage text, which exits 1 by design.  Give it another flag.
ok " 7. kdesc -c -v 1"        kdesc -c -v 1
ok " 8. kdesc --katoms -v 1"  kdesc --katoms -v 1
ok " 9. kdiag -v 1 '@i-x'"    kdiag -v 1 '@i-x'
ok "10. ktext -t html"        ktext -s '@i-x' -t html -d

echo
echo "-- an error exit is nonzero, not a printed message and exit 0 --"
error "11. kdesc reports failure"  "needs a value" kdesc -v
error "12. kdiag reports failure"  "needs a value" kdiag -v

echo
echo "-- two required positional arguments --"
# argv_test is the only program declaring two; it prints what it parsed.
if [ -x "$TSTDIR/argv_test" ]; then
    out=$("$TSTDIR/argv_test" first second --bool 2>&1 | sed 's/\x1b\[[0-9;]*m//g')
    for expect in "<input> : first" "<input2> : second"; do
        if printf '%s' "$out" | tr '\n' ' ' | tr -s ' ' | grep -qF "$expect"; then
            echo "${green}PASS${reset} 13/14. positional [$expect]"; PASS=$((PASS+1))
        else
            echo "${red}FAIL${reset} 13/14. positional [$expect] not parsed"; FAIL=$((FAIL+1))
        fi
    done
else
    echo "SKIP  13/14. two positionals (argv_test not built; run make -C tst)"
fi

echo
echo "-- the output policy: three categories, two streams --"
# The three commands display text in exactly three cases:
#   1. logging under "-v" > 0        -> STDERR
#   2. an error before termination   -> STDERR
#   3. output the user asked for     -> STDOUT
# For ktext, category 3 is a DOCUMENT that may be piped, so nothing else may
# share the stream.  It did: K::log and msg() both wrote to stdout, so
# "ktext -d -v 1 > doc.txt" put the whole argument dump inside the document.
OUTF=$(mktemp /tmp/kout.XXXXXX); ERRF=$(mktemp /tmp/kerr.XXXXXX)
trap 'rm -f "$OUTF" "$ERRF"' EXIT

ktext --klammersets none -s 'hello' -d -v 1 > "$OUTF" 2> "$ERRF"
if [ "$(cat "$OUTF")" = "hello" ]; then
    echo "${green}PASS${reset} 15. stdout under -d -v 1 is the document, nothing else"
    PASS=$((PASS+1))
else
    echo "${red}FAIL${reset} 15. stdout carried more than the document"
    echo "       got: $(head -3 "$OUTF")"; FAIL=$((FAIL+1))
fi
if [ -s "$ERRF" ]; then
    echo "${green}PASS${reset} 16. ... and the -v 1 logging went to stderr"; PASS=$((PASS+1))
else
    echo "${red}FAIL${reset} 16. -v 1 produced no stderr"; FAIL=$((FAIL+1))
fi
# -v 0 adds nothing anywhere: silence is the default, on both streams.
ktext --klammersets none -s 'hello' -d > "$OUTF" 2> "$ERRF"
if [ "$(cat "$OUTF")" = "hello" ] && [ ! -s "$ERRF" ]; then
    echo "${green}PASS${reset} 17. -v 0 is silent on both streams"; PASS=$((PASS+1))
else
    echo "${red}FAIL${reset} 17. -v 0 was not silent (stderr: $(head -2 "$ERRF"))"
    FAIL=$((FAIL+1))
fi
# An error is category 2: stderr, and nothing on stdout to confuse a pipe.
ktext --klammersets none -s '@nosuch x @' -d > "$OUTF" 2> "$ERRF"
if [ ! -s "$OUTF" ] && [ -s "$ERRF" ]; then
    echo "${green}PASS${reset} 18. an error goes to stderr, leaving stdout empty"; PASS=$((PASS+1))
else
    echo "${red}FAIL${reset} 18. error stream discipline (stdout: $(head -2 "$OUTF"))"
    FAIL=$((FAIL+1))
fi
# Colour is emitted only to a terminal, so a redirected stream never carries
# escape sequences -- into a document, a pipe, or a captured log.
for pair in "ktext:--klammersets none -s hello -d" "kdesc:-k table" "kdiag:@i-x"; do
    cmd=${pair%%:*}; rest=${pair#*:}
    # shellcheck disable=SC2086
    $cmd $rest > "$OUTF" 2> "$ERRF"
    if ! grep -q $'\033' "$OUTF" && ! grep -q $'\033' "$ERRF"; then
        echo "${green}PASS${reset} 19. no escape sequences from $cmd when redirected"
        PASS=$((PASS+1))
    else
        echo "${red}FAIL${reset} 19. $cmd emitted colour to a non-terminal"; FAIL=$((FAIL+1))
    fi
done

echo
echo "-- a bare command is a request, not a failure --"
# Usage is the result being asked for: stdout, exit 0.  It exited 1, so
# "kdesc && echo ok" reported failure for a successful help request.
for cmd in ktext kdesc kdiag; do
    $cmd > "$OUTF" 2> "$ERRF"; status=$?
    if [ $status -eq 0 ] && [ -s "$OUTF" ] && ! [ -s "$ERRF" ]; then
        echo "${green}PASS${reset} 20. $cmd with no arguments: usage on stdout, exit 0"
        PASS=$((PASS+1))
    else
        echo "${red}FAIL${reset} 20. $cmd bare: exit $status, stdout $(wc -l < "$OUTF") lines, stderr $(wc -l < "$ERRF") lines"
        FAIL=$((FAIL+1))
    fi
done

echo
echo "===================="
echo "Results: ${PASS} passed, ${FAIL} failed"
[ "$FAIL" -eq 0 ] || exit 1
exit 0
