lib.sh
#!/bin/bash
## Shared helpers for the testmark-bash tests. Sourced, not executed.
## Sources the library, makes a scratch dir and cd's into it, and provides PASS/FAIL reporting.
## The helpers stay exact under the unusual settings test-caller-env.sh turns on:
## comparisons use `[ = ]`, which `nocasematch` doesn't touch.
# shellcheck disable=SC2034 # RESULT is read by the test scripts
set -euo pipefail
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
TESTDATA=$ROOT/tests/testdata
# shellcheck source=lib/testmark.bash
source "$ROOT/lib/testmark.bash"
RESULT=0
work=$(mktemp -d)
exec {stderr}>&2
trap _exit EXIT
cd "$work"
## A nonzero exit with no FAIL row means the script died, maybe with the library's message in $work/err; this shows it before deleting $work.
## It writes to the saved stderr: a shell that dies inside a redirect runs this trap with the redirect still in place.
_exit() {
local rc=$?
if ((rc != 0 && RESULT == 0)) && [[ -s $work/err ]]; then
>&"$stderr" printf 'died (exit %s); last captured stderr:\n%s\n' "$rc" "$(<"$work/err")"
fi
rm -rf "$work"
}
_pass() {
printf 'PASS %s\n' "$1"
}
_fail() {
printf 'FAIL %s\n' "$1"
RESULT=1
}
## - @param $1 description
## - @param $2 got
## - @param $3 want
## - @stdout one PASS or FAIL line
## - @state RESULT -- set to 1 on failure
expect_eq() {
if [ "$2" = "$3" ]; then
_pass "$1"
else
_fail "$1"$'\n'"--- got:"$'\n'"$2"$'\n'"--- want:"$'\n'"$3"
fi
}
## Runs a command that should fail, and checks it printed exactly the given error and nothing on stdout.
##
## - @param $1 description
## - @param $2 the expected stderr, without its final newline
## - @param $3... the command
expect_fail() {
local rc=0
"${@:3}" >"$work/out" 2>"$work/err" || rc=$?
if [[ $rc == 0 ]]; then
_fail "$1: succeeded; want a failure"
else
expect_eq "$1" "$(<"$work/err")$(<"$work/out")" "$2"
fi
}
## Runs a command in this shell, so it can change state, and checks it succeeded with nothing on stderr.
##
## - @param $1 description
## - @param $2... the command
expect_ok() {
local rc=0
"${@:2}" 2>"$work/err" || rc=$?
expect_eq "$1" "$rc $(<"$work/err")" "0 "
}
## Quotes bytes the way the conformance harnesses do: printable ASCII as is, escapes for the rest.
##
## - @param $1 bytes
## - @stdout the quoted string, without a newline
esc() {
local LC_ALL=C s=$1 out='' c i
for ((i = 0; i < ${#s}; i++)); do
c=${s:i:1}
case $c in
'\') out+='\\' ;;
'"') out+='\"' ;;
$'\n') out+='\n' ;;
$'\r') out+='\r' ;;
$'\t') out+='\t' ;;
[\ -~]) out+=$c ;;
*)
printf -v c '\\x%02x' "'$c"
out+=$c
;;
esac
done
printf '"%s"' "$out"
}
## - @param $1 file
## - @stdout the file's bytes, exactly, quoted by `esc`
bytes() {
local s
IFS= read -r -d '' s <"$1" || : # 1 is end of file; the test files hold no NUL
esc "$s"
}
## Prints every hunk of a parsed document: `hunk`, `info`, and `body` lines, values quoted by `esc`.
##
## - @param $1 handle
## - @stdout the dump
dump() {
local names name body
mapfile -t names < <(testmark_names "$1")
for name in "${names[@]}"; do
body=$(testmark_body "$1" "$name" && printf x) || return
printf 'hunk %s\ninfo %s\nbody %s\n' "$(esc "$name")" "$(esc "$(testmark_info "$1" "$name")")" "$(esc "${body%x}")"
done
}
## Parses FILE as handle "doc", and prints its dump, or "error: " and the error message.
## A successful parse that still wrote to stderr gets a "stderr: " line too.
##
## - @param $1 file
## - @stdout the dump or the error
dump_parse() {
if testmark_parse doc "$1" 2>"$work/err"; then
dump doc
[[ ! -s $work/err ]] || printf 'stderr: %s\n' "$(<"$work/err")"
else
printf 'error: %s\n' "$(<"$work/err")"
fi
}
## Parses a document given as a printf format, and checks its dump against the expected lines.
##
## - @param $1 case name
## - @param $2 document, as a printf format
## - @param $3... expected dump lines
parse_case() {
printf -- "$2" >in.md
dump_parse in.md >got # not in $(...), so a library bug that kills the shell kills this script too
expect_eq "$1" "$(<got)" "$(printf '%s\n' "${@:3}")"
}
## Writes t/doc.md and t/test.sh, runs the script in a fresh bash from $work, and checks what it printed and its status.
## The script gets a line sourcing the library first. $work in the output shows as WORK.
##
## - @param $1 description
## - @param $2 t/doc.md's content
## - @param $3 the rest of t/test.sh
## - @param $4 the expected stdout, then a line `--- stderr`, then the expected stderr, then `--- status N`
run_case() {
mkdir -p t
printf '%s' "$2" >t/doc.md
printf 'source %q\n%s\n' "$ROOT/lib/testmark.bash" "$3" >t/test.sh
local rc=0 got
"$BASH" t/test.sh >"$work/out" 2>"$work/err" </dev/null || rc=$?
got="$(<"$work/out")"$'\n'"--- stderr"$'\n'"$(<"$work/err")"$'\n'"--- status $rc"
got=${got//$work/WORK}
expect_eq "$1" "$got" "$4"
}
tests/lib.sh · 4.7 KiB · 4fa0622381