#!/usr/bin/env bash # scripts/image.sh decides which locally built image is in play, so what is # worth testing is the deciding: which commit a tag names, which of several tags # wins, and what it says when the answer is none of them. # # No daemon is needed and none is available - this runs in the image's test # stage. A stub `docker` on PATH answers the two read-only queries the script # makes from a fixture table of tags, which is also the only way to test it # against a machine holding a known set of images. The git side is real: a # throwaway repository, the same trick tests/shell/test-deploy.sh uses, because # choosing between builds made in the same second is a git question. # # Not covered: `diff` and `--deep`. Those read layers and files out of real # images, which needs a daemon; ./scripts/test.sh is where that would live. set -uo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # Not TMP: this file sources image.sh, which has a TMP of its own. WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT pass=0; fail=0 check() { if "${@:2}"; then echo "ok $1"; pass=$((pass+1)); else echo "FAIL $1"; fail=$((fail+1)); fi; } eq() { [ "$1" = "$2" ] || { echo " want '$2', got '$1'" >&2; return 1; }; } has() { case "$2" in *"$1"*) return 0 ;; esac; echo " no '$1' in the output" >&2; return 1; } nope() { ! "$@"; } # --- a fake repo with the script in it --------------------------------------- mkdir -p "$WORK/arena/scripts" "$WORK/bin" cp "$ROOT/scripts/image.sh" "$WORK/arena/scripts/image.sh" cp "$ROOT/versions.env" "$WORK/arena/versions.env" git -C "$WORK/arena" init --quiet -b main git -C "$WORK/arena" config user.email t@t git -C "$WORK/arena" config user.name t git -C "$WORK/arena" add -A git -C "$WORK/arena" commit --quiet -m "first" C1="$(git -C "$WORK/arena" rev-parse --short=12 HEAD)" git -C "$WORK/arena" commit --quiet --allow-empty -m "second" C2="$(git -C "$WORK/arena" rev-parse --short=12 HEAD)" git -C "$WORK/arena" commit --quiet --allow-empty -m "third and newest" C3="$(git -C "$WORK/arena" rev-parse --short=12 HEAD)" # Stands in for the daemon. Reads tag|id|size|created|revision rows out of # $FIXTURE and answers in the shapes image.sh asks for. Deliberately bash and # coreutils only: the test stage is the build environment plus a few packages, # and a stub needing something it does not have fails for the wrong reason. cat > "$WORK/bin/docker" <<'STUB' #!/usr/bin/env bash row() { local t id size created rev while IFS='|' read -r t id size created rev; do if [ "$t" = "$1" ]; then printf '%s|%s|%s|%s\n' "$id" "$size" "$created" "$rev"; return 0; fi done < "$FIXTURE" return 1 } [ "$1" = image ] || exit 99 shift case "$1" in ls) shift # `image ls -f dangling=true`: this machine has none. if [ "$1" = "-f" ]; then exit 0; fi cut -d'|' -f1 "$FIXTURE" ;; inspect) shift fmt="" if [ "$1" = "--format" ]; then fmt="$2"; shift 2; fi rc=0 for ref in "$@"; do if ! out="$(row "${ref##*:}")"; then echo "Error response from daemon: No such image: $ref" >&2 rc=1 continue fi case "$fmt" in "") ;; # an existence check *Id*) printf 'sha256:%s\n' "$out" ;; *) echo "stub: unhandled --format $fmt" >&2; exit 98 ;; esac done exit "$rc" ;; *) exit 99 ;; esac STUB chmod +x "$WORK/bin/docker" img() { ( cd "$WORK/arena" && PATH="$WORK/bin:$PATH" ./scripts/image.sh "$@" ) } # Three builds one second apart on the clock but not in git, which is the case # that made this script sort for itself: docker gives them one Created value and # lists them in a different order every time. The rows are out of order here on # purpose - nothing may depend on what the daemon happened to print first. SAME="2026-08-07T17:44:54.895042045-04:00" cat > "$WORK/fixture" < "$WORK/fixture-ambiguous" <&1)" check "refuses a sha that names two builds" test $? -ne 0 check " and names the first of them" has "claude-one-deadbeef1234" "$out" check " and the second" has "claude-two-deadbeef1234" "$out" # --- list --------------------------------------------------------------------- out="$(img list 2>&1)" check "six tags over five images are counted as five" has "5 image(s)" "$out" check " with the moving tag named as an alias, not a row" has "also tagged: latest" "$out" check "a commit this repo does not have is called out" \ has "1 image(s) name a commit this repo does not have" "$out" check "an old tag gets its commit from the revision label" has " ${C1:0:7} " "$out" check "marks the row versions.env names" has "* mm0.51.0-sur26.4.7-main-$C3" "$out" out="$(IMAGE_TAG=mm0.51.0-sur26.4.7-main-000000000000 img list 2>&1)" check "says so when versions.env names nothing that is built" has "nothing here is" "$out" # --- verify ------------------------------------------------------------------- out="$(img verify newest 2>&1)" check "verify names the commit's subject" has "third and newest" "$out" check " and lists the other tags on that image" has "also latest" "$out" out="$(img verify deadbeef 2>&1)" check "verify says when nothing can rebuild an image" has "NOT in this repo" "$out" echo echo "$pass passed, $fail failed" [ "$fail" -eq 0 ]