patchette

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