#!/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"