#!/usr/bin/env bash # test-xceadmin-build-install-version.sh — build-time-versioning task 6.3 # (.kiro/specs/build-time-versioning/tasks.md, Requirements 1.1, 1.2, 2.1, # 4.1, 4.2) # # What this proves that tasks 6.1/6.2 do not: those two scripts exercise the # C-daemon (xce_scheduler) and Go-binary (xce_apiserver) version paths. # xceadmin (Python) has a DIFFERENT failure mode neither of those can catch, # because it is the only component of this feature with two materially # different install paths that package DIFFERENT copies of its version # module: # - a real build (`meson-build.sh`, task 5.1) generates a fresh # `_build_info.py` with real GIT_COMMIT/BUILD_TIME into an out-of-tree # MIRRORED copy of the package, and only that mirrored copy is what # gets `pip wheel`'d. # - the checked-in `src/xceadmin/src/xceadmin/_build_info.py` (task 5.2) # is a static "unknown"/"unknown" fallback stub, committed so a bare # `pip install -e .` editable install (which imports straight from the # live source tree, never touching the mirror) still works. # A regression here would not look like a crash or a missing field -- it # would look like the wheel install silently shipping the SAME "unknown" # fallback as the editable install (e.g. meson-build.sh's generation step # silently no-ops, or the wheel accidentally packages the wrong tree), which # only a check that runs BOTH install paths from the SAME build and diffs # their build-info fields can catch. Neither 6.1 nor 6.2 builds xceadmin at # all, and neither performs two separate installs of the same artifact. # # What it does: # 1. Runs a REAL `/xce-build`-equivalent invocation: `nix develop # --command bash src/tools/xce-build.sh xceadmin ` (src/xceadmin/meson.build's custom_target, wrapping # meson-build.sh -- schema sync, pinned build-dir venv install, pytest, # then an installable wheel built from that same schema-synced tree). # Profile is accepted but has no effect on xceadmin (pure Python, no # ASAN/debug/release distinction -- see the xce-build skill's own # documented xceadmin case); `release` is used here only because it is # the fastest of the three to configure. # 2. Installs the resulting wheel into a fresh, throwaway venv and runs # `xceadmin --version` there. # 3. Separately, in a second fresh throwaway venv, does an EDITABLE # install (`pip install -e src/xceadmin`, task 5.2's own validated # install path -- see bug-1459 in .wolf/buglog.json for why this must # run inside a venv, not directly against the nix-provided python3: # PEP 668's externally-managed-environment guard rejects a bare `pip # install -e .` there) and runs `xceadmin --version` there too. # 4. Extracts the "(, )" suffix `cli.py`'s # `_compose_version()` appends to both outputs, and asserts: # - the wheel install's fields are BOTH non-empty and NOT the # literal "unknown" fallback: # - the editable install's fields are BOTH exactly "unknown": # - the two installs' fields DIFFER -- this is the actual # observable this task exists to prove: the generated module # genuinely replaces the fallback, it is not merely # present-but-unused or (worse) accidentally identical to it. # 5. Runs a self-contained DEMONSTRATION (see # demonstrate_detection_capability below) proving the comparison logic # itself would catch each failure mode: a synthetic wheel-install # output showing "unknown" when it should not, a synthetic # editable-install output showing a real-looking hash when it should # not, and two synthetic outputs fed to the differ-assertion that are # identical when they should not be. # # Both scratch venvs, and any pip install/import of the venv's own # `xceadmin` entry point, run inside `nix develop --command` -- NOT # bare on the host. Confirmed live: the prebuilt PyPI `pycapnp` wheel this # project's `pyproject.toml` depends on dynamically links `libstdc++.so.6`, # which this sandbox's bare host has no system-wide copy of at all (`import # capnp` fails with "cannot open shared object file" outside any nix # context); the nix devShell's environment resolves it correctly. A plain # (non-`--system-site-packages`) venv is used for both installs, NOT # `--system-site-packages` -- confirmed via the xce-build skill's own # documented caveat that this nix devShell injects click/pycapnp/PyYAML/ # jsonschema via $PYTHONPATH rather than into the base python3 # interpreter's own site-packages, so a `--system-site-packages` venv # cannot see them and pip would then try (and, without network, silently # fail) to resolve them itself; a plain venv's own `pip install` resolving # straight from PyPI (confirmed reachable in this sandbox) has no such # dependency and is also the exact install method # `.claude/skills/xce-build/SKILL.md`'s own "Installing the xceadmin wheel # elsewhere" section documents as the blessed elsewhere-install path. # # Usage: # bash src/tools/test-xceadmin-build-install-version.sh [--keep-artifacts] # # Exit code is the sole pass/fail signal (0 = pass, non-zero = a real # defect or a demonstration inconsistency). One real meson-driven build # (schema sync + fresh build-dir venv + pip install + pytest + wheel build) # plus two more scratch-venv pip installs happen -- budget several minutes # of real wall-clock time, most of it pycapnp's own install (no prebuilt # wheel step is skipped either time; both scratch installs pull it fresh # from PyPI). # # Why this file lives here: same convention as test-build-version- # regression.sh / test-version-startup-consistency.sh / test-commit- # durability.sh / test-heartbeat-tolerance.sh -- a standalone, CLI-runnable, # real-build-and-real-install check for a cross-cutting concern, # deliberately NOT a meson test() (a real meson build plus two more real # pip installs is far too expensive for every `meson test` run). # # Revalidation triggers: # - VERSION_LINE_ANCHOR below must match click's rendered # ", version " prefix (click's default "%(prog)s, version # %(version)s" message template, with `prog_name="xceadmin"` as passed # to `@click.version_option()` in cli.py). # - The "(, )" trailing-parenthesis format below must # match `cli.py`'s `_compose_version()` f-string # (`f"{pkg_version} ({_build_info.GIT_COMMIT}, {_build_info.BUILD_TIME})"`). # If that format ever changes, update extract_build_info_fields here too. # - UNKNOWN_LITERAL below must match the literal "unknown" string both # `src/xceadmin/src/xceadmin/_build_info.py` (task 5.2) and # `meson-build.sh`'s own empty-value fallback (task 5.1) use. set -euo pipefail # ── paths ─────────────────────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" WORKTREE="$(cd "$SCRIPT_DIR/../.." && pwd)" SRC_DIR="$WORKTREE/src" XCEADMIN_DIR="$SRC_DIR/xceadmin" UNKNOWN_LITERAL="unknown" VERSION_LINE_ANCHOR="xceadmin, version" BUILD_DIR_NAME="xceadminversion.build" KEEP_ARTIFACTS=0 for arg in "$@"; do case "$arg" in --keep-artifacts) KEEP_ARTIFACTS=1 ;; *) echo "Unknown argument: $arg" >&2 echo "Usage: $0 [--keep-artifacts]" >&2 exit 2 ;; esac done # ── colour / pass-fail helpers (matches test-build-version-regression.sh / # test-version-startup-consistency.sh / test-heartbeat-tolerance.sh) ──────── green() { printf '\033[32m%s\033[0m\n' "$*"; } red() { printf '\033[31m%s\033[0m\n' "$*"; } yellow() { printf '\033[33m%s\033[0m\n' "$*"; } trap 'red "FATAL: command failed (line $LINENO). See output above."' ERR WORKDIR="" cleanup() { if [ "$KEEP_ARTIFACTS" = 1 ]; then yellow "--keep-artifacts given: leaving $WORKDIR (scratch venvs) and $SRC_DIR/$BUILD_DIR_NAME (meson build dir) in place." else [ -n "$WORKDIR" ] && [ -d "$WORKDIR" ] && rm -rf "$WORKDIR" [ -d "$SRC_DIR/$BUILD_DIR_NAME" ] && rm -rf "$SRC_DIR/$BUILD_DIR_NAME" fi # `pip install -e` (the editable install below) necessarily writes # setuptools build byproducts (egg-info/, __pycache__/) directly into # the real, checked-in src/xceadmin tree it points at -- unlike the # wheel path, there is no out-of-tree mirror for an editable install to # write into instead. Always swept, even with --keep-artifacts, so this # script never leaves the live source checkout dirty regardless of how # it exits. find "$XCEADMIN_DIR" -iname "*.egg-info" -type d -prune -exec rm -rf {} + 2>/dev/null || true find "$XCEADMIN_DIR" -name "__pycache__" -type d -prune -exec rm -rf {} + 2>/dev/null || true find "$XCEADMIN_DIR" -maxdepth 2 -name ".pytest_cache" -type d -prune -exec rm -rf {} + 2>/dev/null || true } trap cleanup EXIT WORKDIR="$(mktemp -d "${TMPDIR:-/tmp}/xce-xceadmin-version-check.XXXXXX")" WHEEL_VENV="$WORKDIR/wheel-venv" EDIT_VENV="$WORKDIR/editable-venv" # ── field extraction ───────────────────────────────────────────────────── # extract_build_info_fields TEXT # TEXT may be a raw --version capture that ALSO contains unrelated noise # around the actual version line -- every invocation below runs the # installed CLI through `nix develop --command` (required for # pycapnp's libstdc++ resolution, see header comment), whose own "Git tree # ... is dirty" warning lands on stderr and gets folded into the same # captured text this function receives via `2>&1`. This first isolates the # actual "xceadmin, version (, )" line (cli.py's # _compose_version(), rendered via click's default "%(prog)s, version # %(version)s" message template) before parsing its trailing parenthesized # suffix, rather than searching the whole raw capture for the last "(" -- # a stray parenthesis in surrounding wrapper noise could otherwise silently # corrupt the parse. Prints "\t" on stdout; returns 1 if # no such line is present at all, or the line has no parenthesized suffix -- # both stronger failures than a merely-empty field. extract_build_info_fields() { local text="$1" line inner commit build_time line="$(printf '%s\n' "$text" | grep -F -- "$VERSION_LINE_ANCHOR" | tail -1)" if [ -z "$line" ]; then return 1 fi if [[ "$line" != *"("* ]] || [[ "$line" != *")"* ]]; then return 1 fi inner="${line##*(}" inner="${inner%)*}" inner="$(printf '%s' "$inner" | tr -d '\r\n')" commit="${inner%%, *}" build_time="${inner#*, }" printf '%s\t%s\n' "$commit" "$build_time" } # check_build_info LABEL ACTUAL_VERSION_OUTPUT MODE # MODE "real": both fields must be non-empty and NOT the literal "unknown" # fallback (the wheel-install path -- Requirements 1.1, 1.2, 2.1). # MODE "unknown": both fields must be EXACTLY "unknown" (the # editable-install path -- proving it never picked up a stale/leftover # generated module, task 5.2's committed-fallback guarantee). This single # function is exercised by both the real per-install checks below AND # demonstrate_detection_capability(), so the demonstration genuinely proves # this exact logic's behaviour. check_build_info() { local label="$1" actual="$2" mode="$3" fields commit build_time if ! fields="$(extract_build_info_fields "$actual")"; then red "FAIL [$label]: --version output has no parenthesized (commit, build_time) suffix -- raw output: [$actual]" return 1 fi commit="${fields%%$'\t'*}" build_time="${fields#*$'\t'}" if [ -z "$commit" ] || [ -z "$build_time" ]; then red "FAIL [$label]: commit id or build time is EMPTY -- raw output: [$actual]" return 1 fi case "$mode" in real) if [ "$commit" = "$UNKNOWN_LITERAL" ] || [ "$build_time" = "$UNKNOWN_LITERAL" ]; then red "FAIL [$label]: wheel install must carry REAL build-info (non-'$UNKNOWN_LITERAL') -- got commit='$commit' build_time='$build_time'. This is exactly the regression this task guards against: the generated module (task 5.1) silently not replacing the committed fallback (task 5.2). Raw output: [$actual]" return 1 fi ;; unknown) if [ "$commit" != "$UNKNOWN_LITERAL" ] || [ "$build_time" != "$UNKNOWN_LITERAL" ]; then red "FAIL [$label]: editable install must carry the committed '$UNKNOWN_LITERAL'/'$UNKNOWN_LITERAL' fallback stub (task 5.2) -- got commit='$commit' build_time='$build_time'. This would mean the editable install accidentally picked up a stale generated/mirrored module instead of the checked-in stub. Raw output: [$actual]" return 1 fi ;; *) red "FAIL [$label]: internal error -- unknown mode '$mode'" return 1 ;; esac green "PASS [$label]: commit='$commit' build_time='$build_time' (mode=$mode)" return 0 } # assert_outputs_differ WHEEL_ACTUAL EDIT_ACTUAL # The actual observable this task exists to prove (see task 6.3's own # "Observable" line): the wheel install's build-info fields must differ # from the editable install's -- not merely each independently pass its own # real/unknown check, which alone would not catch e.g. a wheel and an # editable install that both happened to embed the identical (wrong) value. assert_outputs_differ() { local wheel_actual="$1" edit_actual="$2" wheel_fields edit_fields if ! wheel_fields="$(extract_build_info_fields "$wheel_actual")"; then red "FAIL [differ-check]: wheel-install output has no parenthesized suffix -- raw: [$wheel_actual]" return 1 fi if ! edit_fields="$(extract_build_info_fields "$edit_actual")"; then red "FAIL [differ-check]: editable-install output has no parenthesized suffix -- raw: [$edit_actual]" return 1 fi if [ "$wheel_fields" = "$edit_fields" ]; then red "FAIL [differ-check]: wheel-install and editable-install build-info fields are IDENTICAL ('$wheel_fields') -- the generated module did not genuinely replace the fallback." return 1 fi green "PASS [differ-check]: wheel-install build-info ('$wheel_fields') differs from editable-install build-info ('$edit_fields') -- the generated module genuinely replaces the fallback." return 0 } # Proves the comparison logic itself would catch each failure mode this # task exists to guard against, since (like task 6.2) there is no prior # broken implementation to revert against for a real RED phase -- 5.1/5.2 # already guarantee the wheel carries real values and the editable install # carries the committed stub. Instead, feed the SAME functions # deliberately-wrong synthetic outputs and confirm each is rejected, plus a # sanity check that the genuinely-real captures from this run are still # accepted. demonstrate_detection_capability() { local real_wheel_output="$1" real_edit_output="$2" yellow "=== DEMONSTRATION: proving the comparison logic catches each failure mode this task guards against ===" local synthetic_wheel_unknown="xceadmin, version 0.1.0 ($UNKNOWN_LITERAL, $UNKNOWN_LITERAL)" yellow "--- (a) SYNTHETIC wheel-install --version output showing '$UNKNOWN_LITERAL'/'$UNKNOWN_LITERAL' (the exact regression this task exists to catch: the generated module silently not replacing the fallback) ---" if check_build_info "DEMO-wheel-shows-unknown" "$synthetic_wheel_unknown" real; then red "DEMONSTRATION FAILED: incorrectly PASSED a wheel install reporting '$UNKNOWN_LITERAL' build-info." exit 1 fi green "Confirmed: a wheel install reporting '$UNKNOWN_LITERAL'/'$UNKNOWN_LITERAL' is correctly rejected." local synthetic_edit_real="xceadmin, version 0.1.0 (deadbeef, 2026-01-01T00:00:00Z)" yellow "--- (b) SYNTHETIC editable-install --version output showing a REAL-looking hash (would mean it accidentally picked up a stale generated module) ---" if check_build_info "DEMO-editable-shows-real" "$synthetic_edit_real" unknown; then red "DEMONSTRATION FAILED: incorrectly PASSED an editable install reporting non-'$UNKNOWN_LITERAL' build-info." exit 1 fi green "Confirmed: an editable install reporting non-'$UNKNOWN_LITERAL' build-info is correctly rejected." yellow "--- (c) SYNTHETIC identical wheel/editable outputs (both '$UNKNOWN_LITERAL'/'$UNKNOWN_LITERAL') fed to the differ-assertion ---" if assert_outputs_differ "$synthetic_wheel_unknown" "$synthetic_wheel_unknown"; then red "DEMONSTRATION FAILED: incorrectly PASSED two identical build-info outputs as 'differing'." exit 1 fi green "Confirmed: two identical build-info outputs are correctly rejected as not differing." yellow "--- (d) sanity: this run's REAL wheel output (mode=real) and REAL editable output (mode=unknown) must each PASS, and must be reported as differing ---" if ! check_build_info "DEMO-sanity-real-wheel" "$real_wheel_output" real; then red "DEMONSTRATION FAILED: incorrectly FAILED against genuinely real wheel-install output." exit 1 fi if ! check_build_info "DEMO-sanity-real-editable" "$real_edit_output" unknown; then red "DEMONSTRATION FAILED: incorrectly FAILED against genuinely '$UNKNOWN_LITERAL' editable-install output." exit 1 fi if ! assert_outputs_differ "$real_wheel_output" "$real_edit_output"; then red "DEMONSTRATION FAILED: incorrectly reported genuinely differing real outputs as identical." exit 1 fi green "=== DEMONSTRATION complete: synthetic 'unknown'-in-wheel / 'real'-in-editable / identical-outputs are all rejected; the genuine real/unknown split from this run is accepted ===" } # ══════════════════════════════════════════════════════════════════════════ # 1. real build (meson-driven, task 5.1) # ══════════════════════════════════════════════════════════════════════════ yellow "=== [build] nix develop $SRC_DIR --command bash $SRC_DIR/tools/xce-build.sh xceadmin release $BUILD_DIR_NAME ===" nix develop "$SRC_DIR" --command bash "$SRC_DIR/tools/xce-build.sh" xceadmin release "$BUILD_DIR_NAME" WHEEL_FILE="$(ls "$SRC_DIR/$BUILD_DIR_NAME"/xceadmin/dist/xceadmin-*.whl 2>/dev/null || true)" if [ -z "$WHEEL_FILE" ] || [ ! -f "$WHEEL_FILE" ]; then red "FATAL: no xceadmin-*.whl found under $SRC_DIR/$BUILD_DIR_NAME/xceadmin/dist/ after a successful-looking build." exit 1 fi yellow "[build] wheel produced: $WHEEL_FILE" overall_pass=1 # ══════════════════════════════════════════════════════════════════════════ # 2. wheel install (fresh scratch venv, must carry REAL build-info) # ══════════════════════════════════════════════════════════════════════════ yellow "=== [wheel] creating scratch venv and installing $WHEEL_FILE ===" nix develop "$SRC_DIR" --command bash -c " set -euo pipefail python3 -m venv '$WHEEL_VENV' '$WHEEL_VENV/bin/pip' install --quiet --disable-pip-version-check '$WHEEL_FILE' " if [ ! -x "$WHEEL_VENV/bin/xceadmin" ]; then red "FAIL [wheel]: expected console-script $WHEEL_VENV/bin/xceadmin not found/executable after a successful-looking pip install." overall_pass=0 wheel_actual="" else wheel_actual="$(nix develop "$SRC_DIR" --command "$WHEEL_VENV/bin/xceadmin" --version 2>&1)" yellow "[wheel] xceadmin --version raw output: [$wheel_actual]" if ! check_build_info "wheel-install" "$wheel_actual" real; then overall_pass=0 fi fi # ══════════════════════════════════════════════════════════════════════════ # 3. editable install (fresh scratch venv, must carry the committed # "unknown"/"unknown" fallback stub -- task 5.2) # ══════════════════════════════════════════════════════════════════════════ yellow "=== [editable] creating scratch venv and pip install -e $XCEADMIN_DIR ===" nix develop "$SRC_DIR" --command bash -c " set -euo pipefail python3 -m venv '$EDIT_VENV' '$EDIT_VENV/bin/pip' install --quiet --disable-pip-version-check -e '$XCEADMIN_DIR' " if [ ! -x "$EDIT_VENV/bin/xceadmin" ]; then red "FAIL [editable]: expected console-script $EDIT_VENV/bin/xceadmin not found/executable after a successful-looking pip install." overall_pass=0 edit_actual="" else edit_actual="$(nix develop "$SRC_DIR" --command "$EDIT_VENV/bin/xceadmin" --version 2>&1)" yellow "[editable] xceadmin --version raw output: [$edit_actual]" if ! check_build_info "editable-install" "$edit_actual" unknown; then overall_pass=0 fi fi # ══════════════════════════════════════════════════════════════════════════ # 4. the core observable: the two installs' build-info must DIFFER # ══════════════════════════════════════════════════════════════════════════ if [ -n "$wheel_actual" ] && [ -n "$edit_actual" ]; then if ! assert_outputs_differ "$wheel_actual" "$edit_actual"; then overall_pass=0 fi else red "FAIL [differ-check]: skipped -- one or both installs failed to produce usable --version output above." overall_pass=0 fi # ── demonstration (discriminating-power proof) ────────────────────────────── if [ -n "$wheel_actual" ] && [ -n "$edit_actual" ]; then demonstrate_detection_capability "$wheel_actual" "$edit_actual" else red "FATAL: neither install produced usable real values to demonstrate the comparison logic against." exit 1 fi if [ "$overall_pass" = 1 ]; then green "=== ALL CHECKS PASS: xceadmin's wheel install carries real (non-'$UNKNOWN_LITERAL') build-info, its editable install carries the committed '$UNKNOWN_LITERAL' fallback, and the two genuinely differ ===" exit 0 else red "=== REGRESSION DETECTED: see FAIL line(s) above ===" exit 1 fi