239 lines
8.6 KiB
Bash
Executable file
239 lines
8.6 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
|
|
set -euo pipefail
|
|
|
|
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd)
|
|
repo_root=$(CDPATH= cd "$script_dir/.." && pwd)
|
|
sync_source="$script_dir/sync-version.sh"
|
|
reader_source="$script_dir/workspace-version.sh"
|
|
tmp_base=${TMPDIR:-/tmp}
|
|
tmp_dir=$(mktemp -d "${tmp_base%/}/sync-version.XXXXXX")
|
|
trap 'rm -rf "$tmp_dir"' 0 HUP INT TERM
|
|
|
|
test_count=0
|
|
case_status=0
|
|
case_output=
|
|
|
|
fail() {
|
|
printf 'not ok %s - %s\n' "$test_count" "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
pass() {
|
|
test_count=$((test_count + 1))
|
|
printf 'ok %s - %s\n' "$test_count" "$1"
|
|
}
|
|
|
|
assert_status() {
|
|
expected=$1
|
|
label=$2
|
|
if [ "$case_status" -ne "$expected" ]; then
|
|
printf '%s\n' "$case_output" >&2
|
|
fail "$label: expected status $expected, got $case_status"
|
|
fi
|
|
}
|
|
|
|
assert_contains() {
|
|
needle=$1
|
|
label=$2
|
|
case "$case_output" in
|
|
*"$needle"*) ;;
|
|
*)
|
|
printf '%s\n' "$case_output" >&2
|
|
fail "$label: missing output: $needle"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
assert_file_contains() {
|
|
file=$1
|
|
needle=$2
|
|
label=$3
|
|
if ! grep -Fq -- "$needle" "$file"; then
|
|
fail "$label: $file does not contain: $needle"
|
|
fi
|
|
}
|
|
|
|
assert_file_not_matches() {
|
|
file=$1
|
|
pattern=$2
|
|
label=$3
|
|
if grep -Eq -- "$pattern" "$file"; then
|
|
fail "$label: $file unexpectedly matches: $pattern"
|
|
fi
|
|
}
|
|
|
|
cargo() {
|
|
printf 'cargo:%s:%s\n' "$PWD" "$*" >> "$SYNC_TEST_LOG"
|
|
if [ -e "$PWD/.fail-cargo" ]; then
|
|
printf 'fake cargo failure\n' >&2
|
|
return 31
|
|
fi
|
|
version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' "$PWD/Cargo.toml")
|
|
printf 'lock-version=%s\n' "$version" > "$PWD/Cargo.lock"
|
|
printf '%s\n' '{"packages":[]}'
|
|
}
|
|
|
|
bun() {
|
|
printf 'bun:%s:%s\n' "$PWD" "$*" >> "$SYNC_TEST_LOG"
|
|
repo_root=$(CDPATH= cd "$(dirname "$PWD")" && pwd)
|
|
if [ -e "$repo_root/.fail-bun" ]; then
|
|
printf 'fake bun failure\n' >&2
|
|
return 32
|
|
fi
|
|
version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' "$repo_root/Cargo.toml")
|
|
printf '{"version":"%s"}\n' "$version" > "$PWD/package.json"
|
|
}
|
|
|
|
export -f cargo bun
|
|
|
|
new_repo() {
|
|
name=$1
|
|
repo="$tmp_dir/$name"
|
|
mkdir -p "$repo/scripts" "$repo/tools" "$repo/packages" "$repo/outside"
|
|
ln -s "$sync_source" "$repo/scripts/sync-version.sh"
|
|
ln -s "$reader_source" "$repo/scripts/workspace-version.sh"
|
|
printf '%s\n' \
|
|
'[workspace]' \
|
|
'members = []' \
|
|
'' \
|
|
'[workspace.package]' \
|
|
'version = "2.3.4"' > "$repo/Cargo.toml"
|
|
printf '%s\n' 'lock-version=1.0.0' > "$repo/Cargo.lock"
|
|
printf '%s\n' '{"version":"1.0.0"}' > "$repo/packages/package.json"
|
|
|
|
cat > "$repo/tools/check-version-sync.sh" <<'SCRIPT'
|
|
#!/bin/sh
|
|
set -eu
|
|
repo_root=$(CDPATH= cd "$(dirname "$0")/.." && pwd)
|
|
printf 'guard:%s\n' "$PWD" >> "$SYNC_TEST_LOG"
|
|
if [ -e "$repo_root/.fail-guard" ]; then
|
|
printf 'fake guard failure\n' >&2
|
|
exit 33
|
|
fi
|
|
SCRIPT
|
|
chmod +x "$repo/tools/check-version-sync.sh"
|
|
printf '%s\n' "$repo"
|
|
}
|
|
|
|
run_sync() {
|
|
repo=$1
|
|
shift
|
|
case_status=0
|
|
case_output=$(cd "$repo/outside" && env SYNC_TEST_LOG="$repo/calls.log" \
|
|
bash "$repo/scripts/sync-version.sh" "$@" 2>&1) || case_status=$?
|
|
}
|
|
|
|
case_status=0
|
|
case_output=$("$sync_source" 2.3.4 2>&1) || case_status=$?
|
|
assert_status 2 'argument rejection'
|
|
assert_contains 'usage: scripts/sync-version.sh' 'argument rejection'
|
|
pass 'sync entrypoint rejects positional versions with actionable usage'
|
|
|
|
repo=$(new_repo reader_failure)
|
|
printf '%s\n' \
|
|
'[workspace]' \
|
|
'members = []' \
|
|
'' \
|
|
'[workspace.package]' \
|
|
'version = "not-semver"' > "$repo/Cargo.toml"
|
|
run_sync "$repo"
|
|
assert_status 1 'canonical reader failure'
|
|
assert_contains 'sync-version: canonical version read failed; set a valid SemVer at [workspace.package].version in Cargo.toml' \
|
|
'canonical reader failure'
|
|
if [[ -e "$repo/calls.log" ]]; then
|
|
printf '%s\n' "$(cat "$repo/calls.log")" >&2
|
|
fail 'canonical reader failure invoked Cargo, Bun, or the version guard'
|
|
fi
|
|
pass 'canonical reader failures stop before Cargo, Bun, and the version guard'
|
|
|
|
repo=$(new_repo external_cwd)
|
|
run_sync "$repo"
|
|
assert_status 0 'external cwd'
|
|
assert_file_contains "$repo/calls.log" "cargo:$repo:metadata --no-deps --format-version 1" \
|
|
'external cwd'
|
|
assert_file_contains "$repo/calls.log" "bun:$repo/packages:run sync-version" 'external cwd'
|
|
assert_file_contains "$repo/calls.log" "guard:$repo" 'external cwd'
|
|
assert_file_contains "$repo/Cargo.lock" 'lock-version=2.3.4' 'external cwd'
|
|
assert_file_contains "$repo/packages/package.json" '"version":"2.3.4"' 'external cwd'
|
|
pass 'sync entrypoint locates the repository and runs every stage from an external cwd'
|
|
|
|
first_hash=$(shasum "$repo/Cargo.lock" "$repo/packages/package.json")
|
|
run_sync "$repo"
|
|
assert_status 0 'idempotent rerun'
|
|
second_hash=$(shasum "$repo/Cargo.lock" "$repo/packages/package.json")
|
|
if [ "$first_hash" != "$second_hash" ]; then
|
|
fail 'idempotent rerun changed managed file hashes'
|
|
fi
|
|
pass 'running synchronization twice leaves managed file hashes unchanged'
|
|
|
|
repo=$(new_repo cargo_failure)
|
|
touch "$repo/.fail-cargo"
|
|
run_sync "$repo"
|
|
assert_status 1 'Cargo stage failure'
|
|
assert_contains 'sync-version: Cargo lock refresh failed' 'Cargo stage failure'
|
|
pass 'Cargo refresh failures identify the failing synchronization stage'
|
|
|
|
repo=$(new_repo bun_failure)
|
|
touch "$repo/.fail-bun"
|
|
run_sync "$repo"
|
|
assert_status 1 'package stage failure'
|
|
assert_contains 'sync-version: package version synchronization failed' 'package stage failure'
|
|
pass 'package synchronization failures identify the failing stage'
|
|
|
|
repo=$(new_repo guard_failure)
|
|
touch "$repo/.fail-guard"
|
|
run_sync "$repo"
|
|
assert_status 1 'guard stage failure'
|
|
assert_contains 'sync-version: version consistency check failed' 'guard stage failure'
|
|
pass 'guard failures identify the final verification stage'
|
|
|
|
hook="$repo_root/.githooks/pre-commit"
|
|
assert_file_contains "$hook" 'tools/check-version-sync.sh' 'pre-commit guard'
|
|
assert_file_not_matches "$hook" 'rev-parse --abbrev-ref|grep -oE.*[0-9]|git add|jq --arg|\.tmp.*mv' \
|
|
'pre-commit detect-only policy'
|
|
guard_line=$(grep -n 'tools/check-version-sync.sh' "$hook" | head -n 1 | cut -d: -f1)
|
|
fmt_line=$(grep -n 'cargo fmt' "$hook" | head -n 1 | cut -d: -f1)
|
|
if [ -z "$guard_line" ] || [ -z "$fmt_line" ] || [ "$guard_line" -ge "$fmt_line" ]; then
|
|
fail 'pre-commit guard must run before Rust format and lint checks'
|
|
fi
|
|
pass 'pre-commit is detect-only and runs the version guard before Rust checks'
|
|
|
|
workflow="$repo_root/.github/workflows/version-sync.yml"
|
|
assert_file_contains "$workflow" 'pull_request:' 'version sync workflow trigger'
|
|
assert_file_contains "$workflow" 'push:' 'version sync workflow trigger'
|
|
assert_file_contains "$workflow" 'submodules: recursive' 'version sync checkout'
|
|
assert_file_contains "$workflow" 'dtolnay/rust-toolchain@stable' 'version sync Rust setup'
|
|
assert_file_contains "$workflow" 'oven-sh/setup-bun@v2' 'version sync Bun setup'
|
|
assert_file_contains "$workflow" 'sudo apt-get install --yes ripgrep' 'version sync ripgrep setup'
|
|
assert_file_contains "$workflow" 'bun install --frozen-lockfile' 'version sync dependency install'
|
|
assert_file_contains "$workflow" 'packages/bun.lock' 'version sync managed paths'
|
|
assert_file_contains "$workflow" 'packages/scripts/sync-version.mjs' 'version sync implementation paths'
|
|
assert_file_not_matches "$workflow" 'packages/\*\*' 'version sync focused path filters'
|
|
for trigger in pull_request push; do
|
|
trigger_block=$(awk -v trigger="$trigger" '
|
|
$0 == " " trigger ":" { in_trigger = 1; next }
|
|
in_trigger && ($0 ~ /^ [a-z_]+:$/ || $0 ~ /^[^ ]/) { exit }
|
|
in_trigger { print }
|
|
' "$workflow")
|
|
rust_source_filter_count=$(grep -Fc -- ' - "crates/**/*.rs"' <<< "$trigger_block" || true)
|
|
if [[ "$rust_source_filter_count" -ne 1 ]]; then
|
|
fail "version sync workflow $trigger paths must include crates/**/*.rs exactly once (found $rust_source_filter_count)"
|
|
fi
|
|
done
|
|
assert_file_contains "$workflow" 'scripts/workspace-version.test.sh' 'version reader tests'
|
|
assert_file_contains "$workflow" 'scripts/sync-version.test.sh' 'version sync tests'
|
|
assert_file_contains "$workflow" 'tools/check-version-sync.test.sh' 'version guard tests'
|
|
assert_file_contains "$workflow" 'tools/check-version-sync.sh' 'version guard'
|
|
pass 'dedicated CI installs prerequisites and runs the focused version checks'
|
|
|
|
old_guard_name='check-version-'"fixtures"
|
|
if rg -n "$old_guard_name" "$repo_root" --hidden \
|
|
--glob '!.git/**' --glob '!vendor/**' > "$tmp_dir/old-paths"; then
|
|
sed 's/^/ /' "$tmp_dir/old-paths" >&2
|
|
fail 'old version guard paths remain in the repository'
|
|
fi
|
|
pass 'repository no longer references the retired fixture-only guard name'
|
|
|
|
printf '1..%s\n' "$test_count"
|