test-usage.sh
#!/bin/bash
## Runs the usage guide's examples. Each `.../session` hunk in docs/userguide/USAGE.md
## is a terminal session: its prompt lines (`~/dir$ command`, or `/dir$ command`) are run in order, in a fresh sandbox,
## and the whole session as it ran -- prompts, commands, output -- must match the hunk.
## A `paired/NAME/session` starts where `fresh/setting-up/session` leaves off (its commands are run first, quietly); a `fresh/NAME/session` starts from the bare sandbox.
## `snapshot/session` is a paired session that also shows two of the files it wrote.
## Shown output has trailing whitespace stripped: it's invisible in the rendered doc, and editors that trim it on save would otherwise break the checks.
## Run with TESTMARK_REGEN=1 to rewrite the sessions from what patchette does now; review with `git diff`.
# No set -e up here: testmark-bash mis-reports failing cases under it. Cases run under set -euo pipefail regardless.
source "${TESTMARK_BASH:-$(dirname "${BASH_SOURCE[0]}")/../../../warplibs/testmark-bash}/lib/testmark.bash" || exit 1
## session_commands prints a session's commands, one per line: its prompt lines (`~/dir$ command`, or `/dir$ command`), without their prompts.
## Everything else in a session is output, and skipped.
##
## - @stdin a session
## - @stdout its commands
session_commands() {
local prompt_re='^[~/][^$]*\$ (.*)$' line
while IFS= read -r line; do
if [[ $line =~ $prompt_re ]]; then printf '%s\n' "${BASH_REMATCH[1]}"; fi
done
}
## run_prelude runs another session's commands in this shell, quietly: the starting point for a session,
## so its `cd`s and settings carry into the session.
## A failing command stops it, printing the command and its output on stderr, so the case fails, even under regen;
## the caller then passes the session through untouched, so regen can't write over it.
##
## - @param $1 path to the prelude session
## - @stderr on failure only: the failed command, its output, and its exit status
run_prelude() {
local cmd rc capture
local -a cmds
capture=$(mktemp)
mapfile -t cmds < <(session_commands <"$1")
for cmd in "${cmds[@]}"; do
rc=0
eval "$cmd" </dev/null >"$capture" 2>&1 || rc=$?
if [[ $rc -ne 0 ]]; then
>&2 printf 'prelude command failed, exit status %s: %s\n' "$rc" "$cmd"
>&2 cat "$capture"
rm -f "$capture"
return 1
fi
done
rm -f "$capture"
}
## replay_session runs a session's commands in this shell, in order, and prints the session as it ran.
## Each command is printed after a prompt naming the cwd (as `~`-relative), then what it printed,
## with the home dir shown as `~` there too, and trailing whitespace stripped; a nonzero exit adds an `[exit status: N]` line.
## A blank line separates each command from the output before it.
## The old session's other lines (its old output, and blank lines) are dropped: they're what gets regenerated.
## Commands get /dev/null on stdin, so nothing can wait on input.
##
## - @stdin the session, as written
## - @stdout the session, as it ran
replay_session() {
local cmd rc out sep='' capture
local -a cmds
capture=$(mktemp)
mapfile -t cmds < <(session_commands)
for cmd in "${cmds[@]}"; do
printf '%s%s$ %s\n' "$sep" "${PWD/#"$HOME"/"~"}" "$cmd"
sep=$'\n'
rc=0
eval "$cmd" </dev/null >"$capture" 2>&1 || rc=$?
out=$(sed 's/[[:space:]]*$//' "$capture")
[[ -z $out ]] || printf '%s\n' "${out//"$HOME"/"~"}"
[[ $rc -eq 0 ]] || printf '[exit status: %s]\n' "$rc"
done
rm -f "$capture"
}
## run_in_sandbox runs the case's session in a fresh sandbox home, after another session's commands, if one is named.
## The sandbox: `~/upstream/frob` is the upstream, and `~/src/frob` a clone of it, with `main` checked out,
## carrying the branches `topic/fancy`, `docs-polish`, and `odd-bytes` (see make_corpus).
## Git's identity and clock are pinned, so commits made in a session are reproducible.
##
## - @param $1 optional: the doc hunk of the session to start after
run_in_sandbox() {
source "$TESTCASE_DIR/lib.sh"
testcase_withtmpdir
make_corpus corpus
export HOME=$PWD/home
mkdir -p "$HOME/upstream" "$HOME/src"
mv corpus/upstream "$HOME/upstream/frob"
mv corpus/dev "$HOME/src/frob"
git -C "$HOME/src/frob" remote set-url origin "$HOME/upstream/frob"
git -C "$HOME/src/frob" switch -q main
export PATH="${PATCHETTE%/patchette}:$PATH"
export GIT_AUTHOR_DATE='@1700086400 +0000' GIT_COMMITTER_DATE='@1700086400 +0000'
local prelude=$PWD/prelude.sh
: >"$prelude"
if [[ -n ${1:-} ]]; then testcase_doc_hunk "$1" >"$prelude"; fi
cd "$HOME"
testcase_hunk session |
bash --norc --noprofile -c "$(declare -f session_commands run_prelude replay_session); if run_prelude \"\$1\"; then replay_session; else cat; exit 1; fi" _ "$prelude" |
testcase_output session
}
exercise_fresh() {
run_in_sandbox
}
exercise_paired() {
run_in_sandbox fresh/setting-up/session
}
## exercise_snapshot runs the first-export session, then shows two of the files it wrote, each as an output of its own.
exercise_snapshot() {
run_in_sandbox fresh/setting-up/session
sed 's/[[:space:]]*$//' "$HOME/src/frob-patches/docs-polish/STACK" | testcase_output STACK
sed 's/[[:space:]]*$//' "$HOME/src/frob-patches/docs-polish/polish-the-readme.patch" | testcase_output polish-the-readme.patch
}
testmarktest_provide session_commands run_prelude replay_session run_in_sandbox
testmarktest_rule 'fresh/*/session' exercise_fresh
testmarktest_rule 'paired/*/session' exercise_paired
testmarktest_rule 'snapshot/session' exercise_snapshot
testmarktest_run ../docs/userguide/USAGE.md
tests/test-usage.sh · 5.4 KiB · 28b2b86895