forked from heavy-duty/ceremony
feat: declare the release door path
This commit is contained in:
parent
8f4478f67e
commit
ba55d1d552
2 changed files with 135 additions and 0 deletions
15
.github/scripts/release-path.sh
vendored
Executable file
15
.github/scripts/release-path.sh
vendored
Executable file
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
# The release doors' executable path (discussion #217; issue #237). The
|
||||
# 0.5.0 record had to explain why lib/ruling.sh changed without changing a
|
||||
# door. Keep this list executable so a doors-unchanged record measures the
|
||||
# workflow and only the scripts it actually runs, while the test catches a
|
||||
# new or removed dependency before a record can silently omit it.
|
||||
set -euo pipefail
|
||||
|
||||
printf '%s\n' \
|
||||
.github/workflows/release.yml \
|
||||
bin/ \
|
||||
lib/version.sh \
|
||||
lib/decide.sh \
|
||||
lib/facts.sh \
|
||||
lib/changelog.sh
|
||||
120
test/release-path.test.sh
Executable file
120
test/release-path.test.sh
Executable file
|
|
@ -0,0 +1,120 @@
|
|||
#!/usr/bin/env bash
|
||||
# Contract tests for the release-door path manifest (issue #237). The list
|
||||
# is evidence for skipping a live drill, so drift in either direction must
|
||||
# fail before a release record can make an incomplete claim.
|
||||
set -u
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
# shellcheck source=test/harness.sh
|
||||
. "$ROOT/test/harness.sh"
|
||||
|
||||
PATH_SCRIPT="$ROOT/.github/scripts/release-path.sh"
|
||||
TMP="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP"' EXIT
|
||||
|
||||
# derive_path <tree> — print the workflow, bin/ when a bin command sources a
|
||||
# door library, and the workflow's direct + transitive lib dependencies.
|
||||
derive_path() {
|
||||
local tree="$1" workflow="$tree/.github/workflows/release.yml"
|
||||
local pending seen=" " lib file refs ref bin_uses_lib=no
|
||||
|
||||
printf '%s\n' .github/workflows/release.yml
|
||||
pending="$(sed -n 's|.*\(lib/[[:alnum:]_.-]*\.sh\).*|\1|p' "$workflow" | sort -u)"
|
||||
|
||||
while [ -n "$pending" ]; do
|
||||
lib="$(printf '%s\n' "$pending" | sed -n '1p')"
|
||||
pending="$(printf '%s\n' "$pending" | sed '1d')"
|
||||
case "$seen" in
|
||||
*" $lib "*) continue ;;
|
||||
esac
|
||||
seen="$seen$lib "
|
||||
printf '%s\n' "$lib"
|
||||
file="$tree/$lib"
|
||||
[ -f "$file" ] || continue
|
||||
refs="$(sed -n 's|.*\(lib/[[:alnum:]_.-]*\.sh\).*|\1|p' "$file" | sort -u)"
|
||||
if [ -n "$refs" ]; then
|
||||
pending="$(printf '%s\n%s\n' "$pending" "$refs" | sed '/^$/d' | sort -u)"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "$tree/bin" ]; then
|
||||
for file in "$tree"/bin/*; do
|
||||
[ -f "$file" ] || continue
|
||||
refs="$(sed -n 's|.*\(lib/[[:alnum:]_.-]*\.sh\).*|\1|p' "$file")"
|
||||
for ref in $refs; do
|
||||
case "$seen" in
|
||||
*" $ref "*) bin_uses_lib=yes ;;
|
||||
esac
|
||||
done
|
||||
done
|
||||
fi
|
||||
[ "$bin_uses_lib" = no ] || printf '%s\n' bin/
|
||||
}
|
||||
|
||||
declared_path() {
|
||||
bash "$1/.github/scripts/release-path.sh"
|
||||
}
|
||||
|
||||
path_check() {
|
||||
local tree="$1" declared derived missing extra
|
||||
declared="$(declared_path "$tree" | sort -u)"
|
||||
derived="$(derive_path "$tree" | sort -u)"
|
||||
missing="$(comm -13 <(printf '%s\n' "$declared") <(printf '%s\n' "$derived"))"
|
||||
extra="$(comm -23 <(printf '%s\n' "$declared") <(printf '%s\n' "$derived"))"
|
||||
if [ -n "$missing" ]; then
|
||||
printf 'release-path: missing dependency: %s\n' "$missing" >&2
|
||||
fi
|
||||
if [ -n "$extra" ]; then
|
||||
printf 'release-path: stale path: %s\n' "$extra" >&2
|
||||
fi
|
||||
[ -z "$missing" ] && [ -z "$extra" ]
|
||||
}
|
||||
|
||||
fixture() {
|
||||
local name="$1" tree
|
||||
tree="$TMP/$name"
|
||||
mkdir -p "$tree/.github/scripts" "$tree/.github/workflows" "$tree/lib" "$tree/bin"
|
||||
cp "$PATH_SCRIPT" "$tree/.github/scripts/release-path.sh"
|
||||
printf '#!/usr/bin/env bash\n. "$ROOT/lib/changelog.sh"\n' >"$tree/bin/assemble"
|
||||
printf '#!/usr/bin/env bash\n' >"$tree/lib/changelog.sh"
|
||||
printf '#!/usr/bin/env bash\n' >"$tree/lib/decide.sh"
|
||||
printf '#!/usr/bin/env bash\n. "$ROOT/lib/version.sh"\n' >"$tree/lib/facts.sh"
|
||||
printf '#!/usr/bin/env bash\n' >"$tree/lib/version.sh"
|
||||
printf '%s\n' "$tree"
|
||||
}
|
||||
|
||||
# Exact output is the record author's copy-paste source.
|
||||
check "manifest prints the specified ordered release path" 0 \
|
||||
$'.github/workflows/release.yml\nbin/\nlib/version.sh\nlib/decide.sh\nlib/facts.sh\nlib/changelog.sh' \
|
||||
bash "$PATH_SCRIPT"
|
||||
check "real workflow and transitive dependencies match the manifest" 0 "" \
|
||||
path_check "$ROOT"
|
||||
|
||||
# A door growing a dependency must name the missing path (#237 D7).
|
||||
tree="$(fixture missing)"
|
||||
printf 'run: bash "$CEREMONY_DIR/lib/facts.sh"\nrun: bash "$CEREMONY_DIR/lib/decide.sh"\nrun: . "$CEREMONY_DIR/lib/changelog.sh"\nrun: . "$CEREMONY_DIR/lib/version.sh"\nrun: . "$CEREMONY_DIR/lib/ruling.sh"\n' \
|
||||
>"$tree/.github/workflows/release.yml"
|
||||
printf '#!/usr/bin/env bash\n' >"$tree/lib/ruling.sh"
|
||||
check "a new workflow library fails with its missing path" 1 \
|
||||
"missing dependency: lib/ruling.sh" path_check "$tree"
|
||||
|
||||
# A manifest may not rot into a safe-looking superset.
|
||||
tree="$(fixture extra)"
|
||||
printf 'run: bash "$CEREMONY_DIR/lib/facts.sh"\nrun: bash "$CEREMONY_DIR/lib/decide.sh"\nrun: . "$CEREMONY_DIR/lib/changelog.sh"\n' \
|
||||
>"$tree/.github/workflows/release.yml"
|
||||
sed -i 's| lib/changelog.sh$| lib/changelog.sh \\|' \
|
||||
"$tree/.github/scripts/release-path.sh"
|
||||
printf ' lib/ruling.sh\n' >>"$tree/.github/scripts/release-path.sh"
|
||||
printf '#!/usr/bin/env bash\n' >"$tree/lib/ruling.sh"
|
||||
check "a path no door reads fails as stale" 1 "stale path: lib/ruling.sh" \
|
||||
path_check "$tree"
|
||||
|
||||
# Transitive sourcing is part of the derivation, not decoration.
|
||||
tree="$(fixture transitive)"
|
||||
printf 'run: bash "$CEREMONY_DIR/lib/facts.sh"\nrun: bash "$CEREMONY_DIR/lib/decide.sh"\nrun: . "$CEREMONY_DIR/lib/changelog.sh"\n' \
|
||||
>"$tree/.github/workflows/release.yml"
|
||||
: >"$tree/lib/facts.sh"
|
||||
check "removing facts' version source fails as a stale path" 1 \
|
||||
"stale path: lib/version.sh" path_check "$tree"
|
||||
|
||||
summary
|
||||
Loading…
Reference in a new issue