Skip to content

Instantly share code, notes, and snippets.

@ipl31
Created February 26, 2026 03:56
Show Gist options
  • Select an option

  • Save ipl31/61f4ba62d0bbd6eb773cafa40adc0509 to your computer and use it in GitHub Desktop.

Select an option

Save ipl31/61f4ba62d0bbd6eb773cafa40adc0509 to your computer and use it in GitHub Desktop.
docker-tag-test.sh
#!/usr/bin/env bash
# Unit tests for the Docker image tag resolution logic used in
# .github/workflows/docker-release.yml
#
# Run: bash scripts/test-docker-tag-resolution.sh
set -euo pipefail
IMAGE="ghcr.io/openclaw/openclaw"
passed=0
failed=0
# Arithmetic increment helpers — avoid ((var++)) which returns exit code 1
# when var transitions from 0, causing failures under set -e.
pass() { passed=$((passed + 1)); }
fail() { failed=$((failed + 1)); }
# ---------- extract the tag-resolution logic under test ----------
# This function mirrors the "Resolve image tags" step in docker-release.yml.
# ARCH is the architecture suffix (e.g. "amd64", "arm64") or empty for the
# multi-platform manifest tags.
resolve_tags() {
local github_ref="$1"
local arch="${2:-}"
local suffix=""
[[ -n "$arch" ]] && suffix="-${arch}"
local -a tags=()
if [[ "${github_ref}" == "refs/heads/main" ]]; then
tags+=("${IMAGE}:main${suffix}")
fi
if [[ "${github_ref}" == refs/tags/v* ]]; then
local version="${github_ref#refs/tags/v}"
tags+=("${IMAGE}:${version}${suffix}")
# Tag stable (non-prerelease) releases as latest
# Use explicit suffix checks so patch releases like v2026.2.6-1 still get latest
if [[ "$version" != *-beta* && "$version" != *-rc* && "$version" != *-alpha* ]]; then
tags+=("${IMAGE}:latest${suffix}")
fi
fi
# Return tags, one per line
if [[ ${#tags[@]} -gt 0 ]]; then
printf "%s\n" "${tags[@]}"
fi
}
# ---------- tiny test harness ----------
# assert_tags LABEL GITHUB_REF ARCH EXPECTED_TAG...
# ARCH can be "" for manifest tags.
assert_tags() {
local label="$1"
local github_ref="$2"
local arch="$3"
shift 3
local -a expected=("$@")
local actual
actual=$(resolve_tags "$github_ref" "$arch")
local expected_sorted actual_sorted
expected_sorted=$(printf "%s\n" "${expected[@]}" | sort)
actual_sorted=$(echo "$actual" | sort)
if [[ "$expected_sorted" == "$actual_sorted" ]]; then
echo " PASS: $label"
pass
else
echo " FAIL: $label"
echo " expected: $(printf "%s\n" "${expected[@]}")"
echo " actual: $actual"
fail
fi
}
# assert_no_latest LABEL GITHUB_REF ARCH
assert_no_latest() {
local label="$1"
local github_ref="$2"
local arch="$3"
local actual
actual=$(resolve_tags "$github_ref" "$arch")
if echo "$actual" | grep -q ":latest"; then
echo " FAIL: $label (unexpected latest tag)"
echo " actual: $actual"
fail
else
echo " PASS: $label"
pass
fi
}
# assert_has_latest LABEL GITHUB_REF ARCH
assert_has_latest() {
local label="$1"
local github_ref="$2"
local arch="$3"
local actual
actual=$(resolve_tags "$github_ref" "$arch")
local suffix=""
[[ -n "$arch" ]] && suffix="-${arch}"
if echo "$actual" | grep -q ":latest${suffix}$"; then
echo " PASS: $label"
pass
else
echo " FAIL: $label (missing latest${suffix} tag)"
echo " actual: $actual"
fail
fi
}
# ---------- tests ----------
echo "=== Stable releases should get latest ==="
assert_tags "stable release (manifest)" \
"refs/tags/v2026.2.25" "" \
"${IMAGE}:2026.2.25" "${IMAGE}:latest"
assert_tags "stable release (amd64)" \
"refs/tags/v2026.2.25" "amd64" \
"${IMAGE}:2026.2.25-amd64" "${IMAGE}:latest-amd64"
assert_tags "stable release (arm64)" \
"refs/tags/v2026.2.25" "arm64" \
"${IMAGE}:2026.2.25-arm64" "${IMAGE}:latest-arm64"
echo ""
echo "=== Patch/hotfix releases should get latest ==="
assert_has_latest "patch release v2026.2.6-1 (manifest)" \
"refs/tags/v2026.2.6-1" ""
assert_has_latest "patch release v2026.2.6-1 (amd64)" \
"refs/tags/v2026.2.6-1" "amd64"
assert_has_latest "patch release v2026.2.6-2 (manifest)" \
"refs/tags/v2026.2.6-2" ""
assert_has_latest "patch release v2026.2.6-3 (arm64)" \
"refs/tags/v2026.2.6-3" "arm64"
echo ""
echo "=== Pre-release tags should NOT get latest ==="
assert_no_latest "beta release (manifest)" \
"refs/tags/v2026.2.25-beta.1" ""
assert_no_latest "beta release (amd64)" \
"refs/tags/v2026.2.25-beta.1" "amd64"
assert_no_latest "beta release (arm64)" \
"refs/tags/v2026.2.25-beta.1" "arm64"
assert_no_latest "rc release (manifest)" \
"refs/tags/v2026.2.25-rc.1" ""
assert_no_latest "rc release (amd64)" \
"refs/tags/v2026.2.25-rc.1" "amd64"
assert_no_latest "alpha release (manifest)" \
"refs/tags/v2026.2.25-alpha.1" ""
assert_no_latest "alpha release (amd64)" \
"refs/tags/v2026.2.25-alpha.1" "amd64"
echo ""
echo "=== Pre-releases still get versioned tags ==="
assert_tags "beta has version tag only (manifest)" \
"refs/tags/v2026.2.25-beta.1" "" \
"${IMAGE}:2026.2.25-beta.1"
assert_tags "rc has version tag only (amd64)" \
"refs/tags/v2026.2.25-rc.1" "amd64" \
"${IMAGE}:2026.2.25-rc.1-amd64"
assert_tags "alpha has version tag only (arm64)" \
"refs/tags/v2026.2.25-alpha.1" "arm64" \
"${IMAGE}:2026.2.25-alpha.1-arm64"
echo ""
echo "=== Main branch builds ==="
assert_tags "main branch (manifest)" \
"refs/heads/main" "" \
"${IMAGE}:main"
assert_tags "main branch (amd64)" \
"refs/heads/main" "amd64" \
"${IMAGE}:main-amd64"
assert_tags "main branch (arm64)" \
"refs/heads/main" "arm64" \
"${IMAGE}:main-arm64"
echo ""
echo "=== Edge cases ==="
assert_has_latest "legacy patch v2026.2.6-1 gets latest" \
"refs/tags/v2026.2.6-1" ""
assert_no_latest "beta.N style excluded" \
"refs/tags/v2026.2.25-beta.3" ""
assert_no_latest "legacy beta dot style excluded" \
"refs/tags/v2026.2.25-beta1" ""
assert_no_latest "main branch never gets latest" \
"refs/heads/main" ""
echo ""
echo "================================"
echo "Results: ${passed} passed, ${failed} failed"
echo "================================"
[[ $failed -eq 0 ]] && exit 0 || exit 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment