test-api.sh
#!/bin/bash
## The read API: handles, errors, and exact output.
set -euo pipefail
cd "$(dirname "$0")"
# shellcheck source=tests/lib.sh
source ./lib.sh
## 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 "
}
printf '[testmark]:# (x)\n```text\none\n```\n[testmark]:# (old)\n```\ngone\n```\n' >a.md
printf '[testmark]:# (x)\n```\ntwo\n```\n' >b.md
printf 'no hunks here\n' >none.md
: >empty.md
# --- output is exact, and travels through files ---
testmark_parse doc a.md
testmark_names doc >out
expect_eq "names: one per line, in document order" "$(od -c out)" "$(printf 'x\nold\n' | od -c)"
testmark_body doc x >out
expect_eq "body: exact bytes" "$(od -c out)" "$(printf 'one\n' | od -c)"
testmark_info doc x >out
expect_eq "info: then a newline" "$(od -c out)" "$(printf 'text\n' | od -c)"
testmark_parse doc none.md
testmark_names doc >out
expect_eq "names: nothing, for no hunks" "$(wc -c <out)" "0"
# --- several handles at once, and re-parsing ---
testmark_parse a a.md
testmark_parse b b.md
testmark_parse e empty.md
expect_eq "handle a" "$(testmark_body a x)" "one"
expect_eq "handle b" "$(testmark_body b x)" "two"
expect_fail "an empty doc doesn't see another doc's hunks" 'testmark: e: no hunk named "x"' testmark_body e x
expect_ok "re-parse" testmark_parse a b.md
expect_eq "re-parse replaces the doc" "$(testmark_body a x)" "two"
expect_fail "re-parse drops the old doc's names" 'testmark: a: no hunk named "old"' testmark_body a old
expect_eq "re-parse leaves other handles alone" "$(testmark_body b x)" "two"
expect_ok "re-parse of the same file" testmark_parse b b.md
expect_eq "and it reads" "$(testmark_body b x)" "two"
expect_fail "a failed re-parse fails" 'testmark: missing.md: not a readable file' testmark_parse b missing.md
expect_fail "and leaves its handle unusable" 'testmark: unknown handle "b"' testmark_names b
# --- what a parse doesn't keep is gone: white-box, comparing every library global and its value ---
printf '[testmark]:# (x)\n```\none\n```\n[testmark]:# (y)\n' >dangling.md
## - @stdout `declare -p` of every `_testmark_*` global
globals() {
local vars
mapfile -t vars < <(compgen -v _testmark_)
declare -p "${vars[@]}"
}
before=$(globals)
testmark_parse g a.md
expect_fail "a parse that fails late" 'testmark: dangling.md:5: annotation not followed by a code block' testmark_parse g dangling.md
expect_eq "leaves no globals for its handle, of the old doc or the partial one" "$(globals)" "$before"
testmark_parse g b.md
before=$(globals)
testmark_parse g a.md
testmark_parse g b.md
expect_eq "a re-parse leaves nothing of the old doc" "$(globals)" "$before"
# --- sourcing: twice keeps state; inside a function still makes globals ---
# shellcheck source=lib/testmark.bash
source "$ROOT/lib/testmark.bash"
expect_eq "sourcing again keeps open docs" "$(testmark_body a x)" "two"
testmark_parse c none.md
expect_fail "and later parses don't collide with them" 'testmark: c: no hunk named "x"' testmark_body c x
expect_eq "sourcing inside a function" "$(
"$BASH" -c 'f() { source "$1"; }; f "$1"; testmark_parse d1 a.md; testmark_parse d2 b.md; testmark_body d1 x' _ "$ROOT/lib/testmark.bash"
)" "one"
# --- errors ---
expect_fail "unknown handle" 'testmark: unknown handle "nope"' testmark_body nope x
expect_fail "unknown hunk" 'testmark: a: no hunk named "nope"' testmark_info a nope
expect_fail "invalid handle" 'testmark: invalid handle "a-b": must match [A-Za-z0-9_]+' testmark_parse a-b a.md
expect_fail "empty handle" 'testmark: invalid handle "": must match [A-Za-z0-9_]+' testmark_parse '' a.md
expect_fail "directory" 'testmark: .: not a readable file' testmark_parse a .
expect_fail "parse arity" 'testmark: usage: testmark_parse HANDLE FILE' testmark_parse a
expect_fail "names arity" 'testmark: usage: testmark_names HANDLE' testmark_names
expect_fail "body arity" 'testmark: usage: testmark_body HANDLE NAME' testmark_body a x y
expect_fail "info arity" 'testmark: usage: testmark_info HANDLE NAME' testmark_info a
# --- names with shell syntax: plain data, never evaluated ---
names=('$(>pwned1)' '`>pwned2`' 'a]b' 'x[0]' '@' '*' '$HOME' "'q'" '"q"' 'a\b')
for name in "${names[@]}"; do
printf '[testmark]:# (%s)\n```\nbody of %s\n```\n\n' "$name" "$name"
done >syntax.md
testmark_parse h syntax.md
mapfile -t got < <(testmark_names h)
expect_eq "names with shell syntax: list" "$(printf '%s\n' "${got[@]}")" "$(printf '%s\n' "${names[@]}")"
for name in "${names[@]}"; do
expect_eq "name $name" "$(testmark_body h "$name")" "body of $name"
done
expect_fail "an unknown name with shell syntax" 'testmark: h: no hunk named "$(>pwned3)"' testmark_body h '$(>pwned3)'
expect_fail "a handle with shell syntax" 'testmark: invalid handle "h[$(>pwned4)]": must match [A-Za-z0-9_]+' testmark_body 'h[$(>pwned4)]' x
expect_eq "nothing was executed" "$(echo pwned*)" "pwned*"
exit "$RESULT"
tests/test-api.sh · 5.4 KiB · c24db4dd24