bump-e2e.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. #!/bin/bash
  2. #
  3. # execs.bump end-to-end verification — the manifest version-bump hook.
  4. #
  5. # Builds USM, then drives scratch fixture packages through the whole
  6. # bump-hook story. Every fixture lives OUTSIDE the checkout: the bump
  7. # verb rewrites the CWD's MANIFEST.usm and creates git tags, so it must
  8. # never run inside the USM source tree.
  9. #
  10. # Assertions (each printed PASS/FAIL, exit non-zero on any failure):
  11. # a. a version bump runs the hook as `<path> <old> <new>` (the hook
  12. # records its arguments), MANIFEST.usm's version is rewritten, and
  13. # the version line the hook rewrites in a fixture meson.build is
  14. # updated
  15. # b. a failing hook (non-zero exit) aborts the bump with exit 251,
  16. # leaving MANIFEST.usm and the fixture meson.build untouched
  17. # c. an explicit bump to the version the manifest already carries
  18. # does NOT run the hook
  19. # d. a flags-only bump (--rebuild-dependants, version unchanged) does
  20. # NOT run the hook, and the flag IS written to the manifest
  21. # e. a bare --git release (no version argument, no version change)
  22. # does NOT run the hook, and still commits + tags
  23. # f. --git with a hook that edits a file: the release commit contains
  24. # both MANIFEST.usm and the hook-edited file, and tag v<version>
  25. # exists
  26. # g. a data-package manifest (flags: ["dataPackage"], acquire + bump
  27. # execs) may declare the hook; it runs and the version bumps
  28. #
  29. # Environment quirks this script works around (keep in mind when
  30. # running it elsewhere):
  31. # * The freshly built usm binary links libusm-0.1.so by SONAME, and
  32. # the host carries an older /opt/usm/lib/libusm-0.1.so — every
  33. # invocation needs LD_LIBRARY_PATH=<build>/lib or it silently
  34. # talks to the stale library.
  35. # * meson only finds the Invercargill libraries through
  36. # PKG_CONFIG_PATH=/opt/usm/lib/pkgconfig.
  37. # * The hook is spawned through GLib, which PATH-searches a path
  38. # with no slash in it: a manifest declaring "bump": "bump.sh"
  39. # fails to spawn even with the file executable in the CWD. Real
  40. # manifests place scripts under usm-scripts/, whose slash makes
  41. # the path CWD-relative — the fixtures do the same.
  42. # * usm manifest bump --git commits through plain git: without a
  43. # configured identity it dies with "unable to auto-detect email
  44. # address". The GIT_AUTHOR_*/GIT_COMMITTER_* exports below give
  45. # both usm's internal commits and ours one.
  46. #
  47. # Usage: scripts/bump-e2e.sh [build-dir]
  48. # build-dir defaults to a fresh temp directory; pass an existing meson
  49. # build directory to skip the (slow) compile.
  50. set -e
  51. set -u
  52. set -o pipefail
  53. SCRIPT_DIR=$(cd -- "$(dirname -- "$0")" && pwd)
  54. SOURCE_DIR=$(cd -- "$SCRIPT_DIR/.." && pwd)
  55. WORK=$(mktemp -d /tmp/usm-bump-e2e.XXXXXX)
  56. trap 'rm -rf "$WORK"' EXIT
  57. PASS_COUNT=0
  58. FAIL_COUNT=0
  59. report() {
  60. if [ "$1" = 0 ]; then
  61. printf 'PASS %s\n' "$2"
  62. PASS_COUNT=$((PASS_COUNT + 1))
  63. else
  64. printf 'FAIL %s\n' "$2"
  65. FAIL_COUNT=$((FAIL_COUNT + 1))
  66. fi
  67. }
  68. # Asserts <description> against <command...>: the command succeeding is
  69. # a PASS, failing is a FAIL (set -e must not abort on a failed
  70. # assertion — the failure is the observation, reported like any other).
  71. try() {
  72. local description=$1
  73. shift
  74. if "$@" >/dev/null 2>&1; then
  75. report 0 "$description"
  76. else
  77. report 1 "$description"
  78. fi
  79. }
  80. # ---------------------------------------------------------------------------
  81. # 0. Build
  82. # ---------------------------------------------------------------------------
  83. BUILD_DIR=${1:-}
  84. if [ -z "$BUILD_DIR" ]; then
  85. BUILD_DIR="$WORK/usm-build"
  86. printf '== Building USM into %s\n' "$BUILD_DIR"
  87. PKG_CONFIG_PATH=/opt/usm/lib/pkgconfig meson setup "$BUILD_DIR" "$SOURCE_DIR/src" >/dev/null 2>&1 \
  88. || { echo "meson setup failed"; exit 1; }
  89. PKG_CONFIG_PATH=/opt/usm/lib/pkgconfig ninja -C "$BUILD_DIR" >/dev/null 2>&1 \
  90. || { echo "ninja failed"; exit 1; }
  91. fi
  92. USM_BIN="$BUILD_DIR/cli/usm"
  93. [ -x "$USM_BIN" ] || { echo "no usm binary at $USM_BIN"; exit 1; }
  94. # The stale-host-library workaround described in the header
  95. export LD_LIBRARY_PATH="$BUILD_DIR/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
  96. # One identity for our git calls and usm's internal release commits
  97. export GIT_AUTHOR_NAME="USM Bump E2E"
  98. export GIT_AUTHOR_EMAIL="bump-e2e@example.invalid"
  99. export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
  100. export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
  101. # ---------------------------------------------------------------------------
  102. # 1. Fixtures
  103. # ---------------------------------------------------------------------------
  104. # Runs the built usm against one fixture directory; the exit code lands
  105. # in BUMP_EXIT (never aborts the script — a non-zero bump IS the test).
  106. BUMP_EXIT=0
  107. bump() {
  108. BUMP_EXIT=0
  109. (cd "$1" && "$USM_BIN" manifest bump "${@:2}") || BUMP_EXIT=$?
  110. }
  111. # A code-package fixture: a manifest declaring the bump hook, the hook
  112. # itself (records "<old> <new>", rewrites the meson.build version line),
  113. # a meson.build carrying the version, and the provided resource.
  114. make_fixture() {
  115. # make_fixture <dir> <name> <version> <hook> (hook: record | fail)
  116. local dir=$1 name=$2 version=$3 hook=$4
  117. rm -rf "$dir"
  118. mkdir -p "$dir/usm-scripts"
  119. cat > "$dir/MANIFEST.usm" <<EOF
  120. {
  121. "name": "$name",
  122. "version": "$version",
  123. "summary": "execs.bump end-to-end fixture",
  124. "licences": [],
  125. "flags": [],
  126. "provides": { "res:$name/hello.txt": "source:hello.txt" },
  127. "depends": { "runtime": [], "build": [], "manage": ["bin:bash"] },
  128. "execs": { "bump": "usm-scripts/bump.sh" }
  129. }
  130. EOF
  131. printf 'fixture payload\n' > "$dir/hello.txt"
  132. printf "project('%s', 'c', version: '%s')\n" "$name" "$version" > "$dir/meson.build"
  133. if [ "$hook" = fail ]; then
  134. cat > "$dir/usm-scripts/bump.sh" <<'EOF'
  135. #!/bin/bash
  136. # Records its arguments, then fails — the bump must abort.
  137. echo "$1 $2" > hook-args.txt
  138. echo "deliberate bump-hook failure" >&2
  139. exit 1
  140. EOF
  141. else
  142. cat > "$dir/usm-scripts/bump.sh" <<'EOF'
  143. #!/bin/bash
  144. # Records "<old> <new>", then rewrites the meson.build project version.
  145. echo "$1 $2" > hook-args.txt
  146. sed -i "s/version: '$1'/version: '$2'/" meson.build
  147. EOF
  148. fi
  149. chmod +x "$dir/usm-scripts/bump.sh"
  150. }
  151. # Wraps a fixture into a throwaway git repository with everything
  152. # committed, so --git releases have something to tag.
  153. git_init_fixture() {
  154. (cd "$1" && git init -q && git add -A && git commit -qm "fixture base")
  155. }
  156. FX_A="$WORK/fixture-a"
  157. FX_B="$WORK/fixture-b"
  158. FX_C="$WORK/fixture-c"
  159. FX_E="$WORK/fixture-e"
  160. FX_F="$WORK/fixture-f"
  161. FX_G="$WORK/fixture-g"
  162. make_fixture "$FX_A" bump-e2e-demo 1.0.0 record
  163. make_fixture "$FX_B" bump-e2e-fail 1.0.0 fail
  164. make_fixture "$FX_C" bump-e2e-same 1.0.1 record
  165. make_fixture "$FX_E" bump-e2e-release 1.0.1 record
  166. make_fixture "$FX_F" bump-e2e-git 1.0.1 record
  167. git_init_fixture "$FX_E"
  168. git_init_fixture "$FX_F"
  169. # The data-package fixture: the DATA_MANIFEST shape (acquire + bump are
  170. # the maintainer-side execs a data package may declare) with a VERSION
  171. # file in place of meson.build for the hook to rewrite.
  172. mkdir -p "$FX_G/usm-scripts"
  173. cat > "$FX_G/MANIFEST.usm" <<'EOF'
  174. {
  175. "name": "bump-e2e-fonts",
  176. "version": "1.0.0",
  177. "summary": "Example fonts",
  178. "licences": [],
  179. "flags": ["dataPackage"],
  180. "provides": { "res:bump-e2e-fonts/Example.ttf": "source:Example.ttf" },
  181. "depends": { "runtime": [], "build": [], "manage": ["bin:bash"] },
  182. "execs": { "acquire": "usm-scripts/acquire.sh", "bump": "usm-scripts/bump.sh" }
  183. }
  184. EOF
  185. printf 'stub font payload\n' > "$FX_G/Example.ttf"
  186. printf "data-version: '1.0.0'\n" > "$FX_G/VERSION"
  187. printf '#!/bin/bash\nexit 0\n' > "$FX_G/usm-scripts/acquire.sh"
  188. cat > "$FX_G/usm-scripts/bump.sh" <<'EOF'
  189. #!/bin/bash
  190. echo "$1 $2" > hook-args.txt
  191. sed -i "s/data-version: '$1'/data-version: '$2'/" VERSION
  192. EOF
  193. chmod +x "$FX_G/usm-scripts/acquire.sh" "$FX_G/usm-scripts/bump.sh"
  194. # ---------------------------------------------------------------------------
  195. # 2. (a) A version bump runs the hook and rewrites every version line
  196. # ---------------------------------------------------------------------------
  197. printf '== (a) version bump runs the hook\n'
  198. bump "$FX_A" 1.0.1
  199. try "(a) usm manifest bump 1.0.1 exits 0" test "$BUMP_EXIT" = 0
  200. try '(a) hook received the arguments "1.0.0 1.0.1"' \
  201. test "$(cat "$FX_A/hook-args.txt" 2>/dev/null || true)" = "1.0.0 1.0.1"
  202. try "(a) MANIFEST.usm version rewritten to 1.0.1" \
  203. grep -Eq '"version"[[:space:]]*:[[:space:]]*"1\.0\.1"' "$FX_A/MANIFEST.usm"
  204. try "(a) meson.build version line rewritten to 1.0.1" \
  205. grep -q "version: '1.0.1'" "$FX_A/meson.build"
  206. # ---------------------------------------------------------------------------
  207. # 3. (b) A failing hook aborts the bump, leaving the tree untouched
  208. # ---------------------------------------------------------------------------
  209. printf '== (b) failing hook aborts the bump\n'
  210. bump "$FX_B" 1.0.1
  211. try "(b) bump exits 251 when the hook fails" test "$BUMP_EXIT" = 251
  212. try '(b) the hook did run (its arguments were recorded)' \
  213. test "$(cat "$FX_B/hook-args.txt" 2>/dev/null || true)" = "1.0.0 1.0.1"
  214. try "(b) MANIFEST.usm untouched (still 1.0.0)" \
  215. grep -Eq '"version"[[:space:]]*:[[:space:]]*"1\.0\.0"' "$FX_B/MANIFEST.usm"
  216. try "(b) meson.build untouched (still 1.0.0)" \
  217. grep -q "version: '1.0.0'" "$FX_B/meson.build"
  218. # ---------------------------------------------------------------------------
  219. # 4. (c)/(d) No version change — no hook
  220. # ---------------------------------------------------------------------------
  221. printf '== (c) explicit same-version bump\n'
  222. bump "$FX_C" 1.0.1
  223. try "(c) bump 1.0.1 on a 1.0.1 manifest exits 0" test "$BUMP_EXIT" = 0
  224. try "(c) the hook did NOT run" test ! -e "$FX_C/hook-args.txt"
  225. try "(c) MANIFEST.usm still carries 1.0.1" \
  226. grep -Eq '"version"[[:space:]]*:[[:space:]]*"1\.0\.1"' "$FX_C/MANIFEST.usm"
  227. printf '== (d) flags-only bump\n'
  228. # A flags-only invocation needs the version argument to match the
  229. # manifest (no version and no --git is a usage error), so this bump
  230. # changes nothing but the flags array.
  231. bump "$FX_C" 1.0.1 --rebuild-dependants
  232. try "(d) bump 1.0.1 --rebuild-dependants exits 0" test "$BUMP_EXIT" = 0
  233. try "(d) the hook did NOT run" test ! -e "$FX_C/hook-args.txt"
  234. try "(d) the rebuildDependants flag IS in the manifest" \
  235. grep -q '"rebuildDependants"' "$FX_C/MANIFEST.usm"
  236. # ---------------------------------------------------------------------------
  237. # 5. (e) A bare --git release does not run the hook
  238. # ---------------------------------------------------------------------------
  239. printf '== (e) bare --git release\n'
  240. bump "$FX_E" --git
  241. try "(e) usm manifest bump --git exits 0" test "$BUMP_EXIT" = 0
  242. try "(e) the hook did NOT run" test ! -e "$FX_E/hook-args.txt"
  243. try "(e) tag v1.0.1 exists" test -n "$(git -C "$FX_E" tag -l "v1.0.1")"
  244. try "(e) meson.build untouched (still 1.0.1)" \
  245. grep -q "version: '1.0.1'" "$FX_E/meson.build"
  246. # ---------------------------------------------------------------------------
  247. # 6. (f) --git with a hook: the release commit carries the hook's edits
  248. # ---------------------------------------------------------------------------
  249. printf '== (f) --git release after a hook run\n'
  250. bump "$FX_F" patch --git
  251. try "(f) usm manifest bump patch --git exits 0" test "$BUMP_EXIT" = 0
  252. try '(f) hook received the arguments "1.0.1 1.0.2"' \
  253. test "$(cat "$FX_F/hook-args.txt" 2>/dev/null || true)" = "1.0.1 1.0.2"
  254. try "(f) tag v1.0.2 exists" test -n "$(git -C "$FX_F" tag -l "v1.0.2")"
  255. try "(f) the release commit contains MANIFEST.usm" \
  256. test -n "$(git -C "$FX_F" show --name-only --format= "v1.0.2" -- MANIFEST.usm)"
  257. try "(f) the release commit contains the hook-edited meson.build" \
  258. test -n "$(git -C "$FX_F" show --name-only --format= "v1.0.2" -- meson.build)"
  259. try "(f) the tagged meson.build carries the new version" \
  260. test -n "$(git -C "$FX_F" show "v1.0.2":meson.build | grep "version: '1.0.2'")"
  261. # ---------------------------------------------------------------------------
  262. # 7. (g) A data package may declare and run the bump hook
  263. # ---------------------------------------------------------------------------
  264. printf '== (g) data-package bump hook\n'
  265. bump "$FX_G" 1.0.1
  266. try "(g) data-package bump 1.0.1 exits 0" test "$BUMP_EXIT" = 0
  267. try '(g) hook received the arguments "1.0.0 1.0.1"' \
  268. test "$(cat "$FX_G/hook-args.txt" 2>/dev/null || true)" = "1.0.0 1.0.1"
  269. try "(g) MANIFEST.usm version rewritten to 1.0.1" \
  270. grep -Eq '"version"[[:space:]]*:[[:space:]]*"1\.0\.1"' "$FX_G/MANIFEST.usm"
  271. try "(g) the hook's VERSION file rewritten to 1.0.1" \
  272. grep -q "data-version: '1.0.1'" "$FX_G/VERSION"
  273. # ---------------------------------------------------------------------------
  274. printf '\n%d passed, %d failed\n' "$PASS_COUNT" "$FAIL_COUNT"
  275. if [ "$FAIL_COUNT" -gt 0 ]; then
  276. exit 1
  277. fi
  278. exit 0