lib.sh
#!/bin/bash
## Shared helpers for the patchette tests. Sourced, not executed.
## Provides a hermetic, fully-pinned git environment (fixed identities and a
## tick clock belong HERE, not in the tool: tests author commits, patchette
## never does), the test corpus builder, and PASS/FAIL reporting helpers.
set -euo pipefail
export GIT_CONFIG_GLOBAL=/dev/null
export GIT_CONFIG_SYSTEM=/dev/null
export GIT_CONFIG_NOSYSTEM=1
export GIT_ATTR_NOSYSTEM=1
export LC_ALL=C.UTF-8
export TZ=UTC
export GIT_AUTHOR_NAME="Ada Downstream"
export GIT_AUTHOR_EMAIL="ada@downstream.example"
export GIT_COMMITTER_NAME="Ada Downstream"
export GIT_COMMITTER_EMAIL="ada@downstream.example"
PATCHETTE=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/bin/patchette
RESULT=0
## Report one passing check.
##
## - @param $@ description
pass() {
echo "PASS $*"
}
## Report one failing check and mark the test script failed.
##
## - @param $@ description
## - @state RESULT -- set to 1
fail() {
echo "FAIL $*"
RESULT=1
}
## Run a command expected to refuse, and check it does so for the right reason.
##
## PASSes only when the command exits nonzero AND its output contains the
## expected message fragment: an exit code alone could come from any guard
## (or any crash), and we want each test tied to its specific refusal.
##
## - @param $1 check description
## - @param $2 message fragment the combined output must contain
## - @param $@ the command
expect_refusal() {
local desc=$1 want=$2 out rc=0
shift 2
out=$("$@" 2>&1) || rc=$?
if [[ $rc -eq 0 ]]; then
fail "$desc: expected a refusal, but the command succeeded"
elif [[ $out != *"$want"* ]]; then
fail "$desc: refused, but not with the expected message '$want'; got: $out"
else
pass "$desc"
fi
}
_TICK=1700000000
## Advance the fixed clock by one minute, for the next commit.
##
## - @state GIT_AUTHOR_DATE, GIT_COMMITTER_DATE -- set to the new clock value.
tick() {
_TICK=$((_TICK + 60))
export GIT_AUTHOR_DATE="@${_TICK} +0000"
export GIT_COMMITTER_DATE="@${_TICK} +0000"
}
## Build the test corpus: an upstream repo, and a dev clone carrying two
## to-be-tracked branches. Every commit is hostile on purpose.
##
## `topic/fancy` exercises: linear commits, a subject-slug collision, a rename,
## a merge (with an in-range side branch), an extra commit header in the
## jj `change-id` style, an empty commit, a body with a blank line, and a
## message without a final newline.
## `docs-polish` exists to make the layout's branch mapping visible.
##
## - @param $1 output directory; created. Gets `upstream/`, `dev/`, and a
## sourceable `FACTS` file recording the base SHA and branch tips.
make_corpus() (
local out=$1 base first raw new
mkdir -p "$out"
out=$(cd "$out" && pwd)
# --- upstream ---
git init -q -b main "$out/upstream"
cd "$out/upstream"
{
printf 'The frobnicator.\n'
for i in 1 2 3 4 5 6 7 8; do printf 'gear %s turns smoothly\n' "$i"; done
} >frobnicator.txt
mkdir widgets
printf 'widget: mk1\nstate: dusty\n' >widgets/widget.txt
printf '# Machinery\n\nUpstream docs.\n' >README.md
git add -A
tick
git commit -q -m "Initial machinery"
printf '\nMore upstream docs.\n' >>README.md
git add -A
tick
git commit -q -m "Document the machinery"
base=$(git rev-parse HEAD)
# --- dev clone, branch topic/fancy ---
git clone -q "$out/upstream" "$out/dev"
cd "$out/dev"
git switch -qc topic/fancy
# plain linear change
sed -i 's/gear 3 turns smoothly/gear 3 turns BETTER/' frobnicator.txt
git add -A
tick
git commit -q -m "Fix the frobnicator"
first=$(git rev-parse HEAD)
# same subject again: forces the slug collision rule to show itself
sed -i 's/gear 5 turns smoothly/gear 5 turns BETTER/' frobnicator.txt
git add -A
tick
git commit -q -m "Fix the frobnicator"
# rename plus edit
mkdir gizmos
git mv widgets/widget.txt gizmos/widget.txt
sed -i 's/state: dusty/state: polished/' gizmos/widget.txt
git add -A
tick
git commit -q -m "Move widget into place"
# a side branch forked in-range, then merged: exercises parents-as-slugs
git switch -qc sprocket-work "$first"
printf '# Sprockets\n\nThey spin.\n' >SPROCKETS.md
git add -A
tick
git commit -q -m "Add sprocket docs"
git switch -q topic/fancy
tick
git merge -q --no-ff -m "Merge sprocket work" sprocket-work
git branch -qD sprocket-work
# extra commit header, jj change-id style; handcrafted via raw object rewrite.
# The body (with its blank line) exercises message framing in the exports.
sed -i 's/gear 7 turns smoothly/gear 7 hums quietly/' frobnicator.txt
git add -A
tick
git commit -q -m "Teach gizmo to hum" -m $'The hum is load-bearing.\nDo not ask why.'
raw=$(mktemp)
git cat-file commit HEAD >"$raw"
new=$(sed '/^committer /a change-id kpqxwvuzyntsmrlokqjihvgzfyexwdcu' "$raw" |
git hash-object -t commit -w --stdin)
rm "$raw"
git update-ref refs/heads/topic/fancy "$new"
# empty commit
tick
git commit -q --allow-empty -m "Note to self"
# message without a final newline: porcelain never makes one, but the object
# model allows it, and total fidelity means total
sed -i 's/gear 8 turns smoothly/gear 8 clicks twice/' frobnicator.txt
git add -A
tick
git commit -q -m "Scribble scribble"
new=$(git cat-file commit HEAD | head -c -1 | git hash-object -t commit -w --stdin)
git update-ref refs/heads/topic/fancy "$new"
# --- second branch, small on purpose ---
git switch -qc docs-polish main
printf '\nDownstream notes: be kind to the machinery.\n' >>README.md
git add -A
tick
git commit -q -m "Polish the readme"
{
printf 'base=%s\n' "$base"
printf 'tip_topic_fancy=%s\n' "$(git rev-parse topic/fancy)"
printf 'tip_docs_polish=%s\n' "$(git rev-parse docs-polish)"
} >"$out/FACTS"
)
tests/lib.sh · 5.6 KiB · 94fd195743