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 after the doc's `paired/prelude` hunk has run; a `fresh/NAME/session` starts from the bare sandbox.
## `snapshot/session` is a paired session that also shows two of the files it wrote.
## 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
## run_prelude runs a prelude script's commands in this shell, one line each, quietly:
## a session's starting point, so its `cd`s and settings carry into the session.
## Blank lines and `#` comments are skipped.
## 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 script
## - @stderr on failure only: the failed command, its output, and its exit status
run_prelude() {
local line rc capture
capture=$(mktemp)
while IFS= read -r line; do
[[ -n $line && $line != '#'* ]] || continue
rc=0
eval "$line" </dev/null >"$capture" 2>&1 || rc=$?
if [[ $rc -ne 0 ]]; then
>&2 printf 'prelude command failed, exit status %s: %s\n' "$rc" "$line"
>&2 cat "$capture"
rm -f "$capture"
return 1
fi
done <"$1"
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; 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 prompt_re='^[~/][^$]*\$ (.*)$' line cmd rc out sep=''
local -a lines
local capture
capture=$(mktemp)
mapfile -t lines
for line in "${lines[@]}"; do
[[ $line =~ $prompt_re ]] || continue
cmd=${BASH_REMATCH[1]}
printf '%s%s$ %s\n' "$sep" "${PWD/#"$HOME"/"~"}" "$cmd"
sep=$'\n'
rc=0
eval "$cmd" </dev/null >"$capture" 2>&1 || rc=$?
out=$(<"$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 a prelude hunk, 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 holding the prelude
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 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 paired/prelude
}
## 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 paired/prelude
testcase_output STACK <"$HOME/src/frob-patches/docs-polish/STACK"
testcase_output polish-the-readme.patch <"$HOME/src/frob-patches/docs-polish/polish-the-readme.patch"
}
testmarktest_provide 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 · 4.8 KiB · d231dd1498