#!/bin/bash # # execs.bump end-to-end verification — the manifest version-bump hook. # # Builds USM, then drives scratch fixture packages through the whole # bump-hook story. Every fixture lives OUTSIDE the checkout: the bump # verb rewrites the CWD's MANIFEST.usm and creates git tags, so it must # never run inside the USM source tree. # # Assertions (each printed PASS/FAIL, exit non-zero on any failure): # a. a version bump runs the hook as ` ` (the hook # records its arguments), MANIFEST.usm's version is rewritten, and # the version line the hook rewrites in a fixture meson.build is # updated # b. a failing hook (non-zero exit) aborts the bump with exit 251, # leaving MANIFEST.usm and the fixture meson.build untouched # c. an explicit bump to the version the manifest already carries # does NOT run the hook # d. a flags-only bump (--rebuild-dependants, version unchanged) does # NOT run the hook, and the flag IS written to the manifest # e. a bare --git release (no version argument, no version change) # does NOT run the hook, and still commits + tags # f. --git with a hook that edits a file: the release commit contains # both MANIFEST.usm and the hook-edited file, and tag v # exists # g. a data-package manifest (flags: ["dataPackage"], acquire + bump # execs) may declare the hook; it runs and the version bumps # # Environment quirks this script works around (keep in mind when # running it elsewhere): # * The freshly built usm binary links libusm-0.1.so by SONAME, and # the host carries an older /opt/usm/lib/libusm-0.1.so — every # invocation needs LD_LIBRARY_PATH=/lib or it silently # talks to the stale library. # * meson only finds the Invercargill libraries through # PKG_CONFIG_PATH=/opt/usm/lib/pkgconfig. # * The hook is spawned through GLib, which PATH-searches a path # with no slash in it: a manifest declaring "bump": "bump.sh" # fails to spawn even with the file executable in the CWD. Real # manifests place scripts under usm-scripts/, whose slash makes # the path CWD-relative — the fixtures do the same. # * usm manifest bump --git commits through plain git: without a # configured identity it dies with "unable to auto-detect email # address". The GIT_AUTHOR_*/GIT_COMMITTER_* exports below give # both usm's internal commits and ours one. # # Usage: scripts/bump-e2e.sh [build-dir] # build-dir defaults to a fresh temp directory; pass an existing meson # build directory to skip the (slow) compile. set -e set -u set -o pipefail SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd) SOURCE_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd) WORK=$(mktemp -d /tmp/usm-bump-e2e.XXXXXX) trap 'rm -rf "$WORK"' EXIT PASS_COUNT=0 FAIL_COUNT=0 report() { if [ "$1" = 0 ]; then printf 'PASS %s\n' "$2" PASS_COUNT=$((PASS_COUNT + 1)) else printf 'FAIL %s\n' "$2" FAIL_COUNT=$((FAIL_COUNT + 1)) fi } # Asserts against : the command succeeding is # a PASS, failing is a FAIL (set -e must not abort on a failed # assertion — the failure is the observation, reported like any other). try() { local description=$1 shift if "$@" >/dev/null 2>&1; then report 0 "$description" else report 1 "$description" fi } # --------------------------------------------------------------------------- # 0. Build # --------------------------------------------------------------------------- BUILD_DIR=${1:-} if [ -z "$BUILD_DIR" ]; then BUILD_DIR="$WORK/usm-build" printf '== Building USM into %s\n' "$BUILD_DIR" PKG_CONFIG_PATH=/opt/usm/lib/pkgconfig meson setup "$BUILD_DIR" "$SOURCE_DIR/src" >/dev/null 2>&1 \ || { echo "meson setup failed"; exit 1; } PKG_CONFIG_PATH=/opt/usm/lib/pkgconfig ninja -C "$BUILD_DIR" >/dev/null 2>&1 \ || { echo "ninja failed"; exit 1; } fi USM_BIN="$BUILD_DIR/cli/usm" [ -x "$USM_BIN" ] || { echo "no usm binary at $USM_BIN"; exit 1; } # The stale-host-library workaround described in the header export LD_LIBRARY_PATH="$BUILD_DIR/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" # One identity for our git calls and usm's internal release commits export GIT_AUTHOR_NAME="USM Bump E2E" export GIT_AUTHOR_EMAIL="bump-e2e@example.invalid" export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME" export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL" # --------------------------------------------------------------------------- # 1. Fixtures # --------------------------------------------------------------------------- # Runs the built usm against one fixture directory; the exit code lands # in BUMP_EXIT (never aborts the script — a non-zero bump IS the test). BUMP_EXIT=0 bump() { BUMP_EXIT=0 (cd "$1" && "$USM_BIN" manifest bump "${@:2}") || BUMP_EXIT=$? } # A code-package fixture: a manifest declaring the bump hook, the hook # itself (records " ", rewrites the meson.build version line), # a meson.build carrying the version, and the provided resource. make_fixture() { # make_fixture (hook: record | fail) local dir=$1 name=$2 version=$3 hook=$4 rm -rf "$dir" mkdir -p "$dir/usm-scripts" cat > "$dir/MANIFEST.usm" < "$dir/hello.txt" printf "project('%s', 'c', version: '%s')\n" "$name" "$version" > "$dir/meson.build" if [ "$hook" = fail ]; then cat > "$dir/usm-scripts/bump.sh" <<'EOF' #!/bin/bash # Records its arguments, then fails — the bump must abort. echo "$1 $2" > hook-args.txt echo "deliberate bump-hook failure" >&2 exit 1 EOF else cat > "$dir/usm-scripts/bump.sh" <<'EOF' #!/bin/bash # Records " ", then rewrites the meson.build project version. echo "$1 $2" > hook-args.txt sed -i "s/version: '$1'/version: '$2'/" meson.build EOF fi chmod +x "$dir/usm-scripts/bump.sh" } # Wraps a fixture into a throwaway git repository with everything # committed, so --git releases have something to tag. git_init_fixture() { (cd "$1" && git init -q && git add -A && git commit -qm "fixture base") } FX_A="$WORK/fixture-a" FX_B="$WORK/fixture-b" FX_C="$WORK/fixture-c" FX_E="$WORK/fixture-e" FX_F="$WORK/fixture-f" FX_G="$WORK/fixture-g" make_fixture "$FX_A" bump-e2e-demo 1.0.0 record make_fixture "$FX_B" bump-e2e-fail 1.0.0 fail make_fixture "$FX_C" bump-e2e-same 1.0.1 record make_fixture "$FX_E" bump-e2e-release 1.0.1 record make_fixture "$FX_F" bump-e2e-git 1.0.1 record git_init_fixture "$FX_E" git_init_fixture "$FX_F" # The data-package fixture: the DATA_MANIFEST shape (acquire + bump are # the maintainer-side execs a data package may declare) with a VERSION # file in place of meson.build for the hook to rewrite. mkdir -p "$FX_G/usm-scripts" cat > "$FX_G/MANIFEST.usm" <<'EOF' { "name": "bump-e2e-fonts", "version": "1.0.0", "summary": "Example fonts", "licences": [], "flags": ["dataPackage"], "provides": { "res:bump-e2e-fonts/Example.ttf": "source:Example.ttf" }, "depends": { "runtime": [], "build": [], "manage": ["bin:bash"] }, "execs": { "acquire": "usm-scripts/acquire.sh", "bump": "usm-scripts/bump.sh" } } EOF printf 'stub font payload\n' > "$FX_G/Example.ttf" printf "data-version: '1.0.0'\n" > "$FX_G/VERSION" printf '#!/bin/bash\nexit 0\n' > "$FX_G/usm-scripts/acquire.sh" cat > "$FX_G/usm-scripts/bump.sh" <<'EOF' #!/bin/bash echo "$1 $2" > hook-args.txt sed -i "s/data-version: '$1'/data-version: '$2'/" VERSION EOF chmod +x "$FX_G/usm-scripts/acquire.sh" "$FX_G/usm-scripts/bump.sh" # --------------------------------------------------------------------------- # 2. (a) A version bump runs the hook and rewrites every version line # --------------------------------------------------------------------------- printf '== (a) version bump runs the hook\n' bump "$FX_A" 1.0.1 try "(a) usm manifest bump 1.0.1 exits 0" test "$BUMP_EXIT" = 0 try '(a) hook received the arguments "1.0.0 1.0.1"' \ test "$(cat "$FX_A/hook-args.txt" 2>/dev/null || true)" = "1.0.0 1.0.1" try "(a) MANIFEST.usm version rewritten to 1.0.1" \ grep -Eq '"version"[[:space:]]*:[[:space:]]*"1\.0\.1"' "$FX_A/MANIFEST.usm" try "(a) meson.build version line rewritten to 1.0.1" \ grep -q "version: '1.0.1'" "$FX_A/meson.build" # --------------------------------------------------------------------------- # 3. (b) A failing hook aborts the bump, leaving the tree untouched # --------------------------------------------------------------------------- printf '== (b) failing hook aborts the bump\n' bump "$FX_B" 1.0.1 try "(b) bump exits 251 when the hook fails" test "$BUMP_EXIT" = 251 try '(b) the hook did run (its arguments were recorded)' \ test "$(cat "$FX_B/hook-args.txt" 2>/dev/null || true)" = "1.0.0 1.0.1" try "(b) MANIFEST.usm untouched (still 1.0.0)" \ grep -Eq '"version"[[:space:]]*:[[:space:]]*"1\.0\.0"' "$FX_B/MANIFEST.usm" try "(b) meson.build untouched (still 1.0.0)" \ grep -q "version: '1.0.0'" "$FX_B/meson.build" # --------------------------------------------------------------------------- # 4. (c)/(d) No version change — no hook # --------------------------------------------------------------------------- printf '== (c) explicit same-version bump\n' bump "$FX_C" 1.0.1 try "(c) bump 1.0.1 on a 1.0.1 manifest exits 0" test "$BUMP_EXIT" = 0 try "(c) the hook did NOT run" test ! -e "$FX_C/hook-args.txt" try "(c) MANIFEST.usm still carries 1.0.1" \ grep -Eq '"version"[[:space:]]*:[[:space:]]*"1\.0\.1"' "$FX_C/MANIFEST.usm" printf '== (d) flags-only bump\n' # A flags-only invocation needs the version argument to match the # manifest (no version and no --git is a usage error), so this bump # changes nothing but the flags array. bump "$FX_C" 1.0.1 --rebuild-dependants try "(d) bump 1.0.1 --rebuild-dependants exits 0" test "$BUMP_EXIT" = 0 try "(d) the hook did NOT run" test ! -e "$FX_C/hook-args.txt" try "(d) the rebuildDependants flag IS in the manifest" \ grep -q '"rebuildDependants"' "$FX_C/MANIFEST.usm" # --------------------------------------------------------------------------- # 5. (e) A bare --git release does not run the hook # --------------------------------------------------------------------------- printf '== (e) bare --git release\n' bump "$FX_E" --git try "(e) usm manifest bump --git exits 0" test "$BUMP_EXIT" = 0 try "(e) the hook did NOT run" test ! -e "$FX_E/hook-args.txt" try "(e) tag v1.0.1 exists" test -n "$(git -C "$FX_E" tag -l "v1.0.1")" try "(e) meson.build untouched (still 1.0.1)" \ grep -q "version: '1.0.1'" "$FX_E/meson.build" # --------------------------------------------------------------------------- # 6. (f) --git with a hook: the release commit carries the hook's edits # --------------------------------------------------------------------------- printf '== (f) --git release after a hook run\n' bump "$FX_F" patch --git try "(f) usm manifest bump patch --git exits 0" test "$BUMP_EXIT" = 0 try '(f) hook received the arguments "1.0.1 1.0.2"' \ test "$(cat "$FX_F/hook-args.txt" 2>/dev/null || true)" = "1.0.1 1.0.2" try "(f) tag v1.0.2 exists" test -n "$(git -C "$FX_F" tag -l "v1.0.2")" try "(f) the release commit contains MANIFEST.usm" \ test -n "$(git -C "$FX_F" show --name-only --format= "v1.0.2" -- MANIFEST.usm)" try "(f) the release commit contains the hook-edited meson.build" \ test -n "$(git -C "$FX_F" show --name-only --format= "v1.0.2" -- meson.build)" try "(f) the tagged meson.build carries the new version" \ test -n "$(git -C "$FX_F" show "v1.0.2":meson.build | grep "version: '1.0.2'")" # --------------------------------------------------------------------------- # 7. (g) A data package may declare and run the bump hook # --------------------------------------------------------------------------- printf '== (g) data-package bump hook\n' bump "$FX_G" 1.0.1 try "(g) data-package bump 1.0.1 exits 0" test "$BUMP_EXIT" = 0 try '(g) hook received the arguments "1.0.0 1.0.1"' \ test "$(cat "$FX_G/hook-args.txt" 2>/dev/null || true)" = "1.0.0 1.0.1" try "(g) MANIFEST.usm version rewritten to 1.0.1" \ grep -Eq '"version"[[:space:]]*:[[:space:]]*"1\.0\.1"' "$FX_G/MANIFEST.usm" try "(g) the hook's VERSION file rewritten to 1.0.1" \ grep -q "data-version: '1.0.1'" "$FX_G/VERSION" # --------------------------------------------------------------------------- printf '\n%d passed, %d failed\n' "$PASS_COUNT" "$FAIL_COUNT" if [ "$FAIL_COUNT" -gt 0 ]; then exit 1 fi exit 0