Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
id: bump
run: |
set -euo pipefail
LATEST=$(git tag --list --merged HEAD --sort=-v:refname | grep -E -m1 '^v[0-9]+\.[0-9]+\.[0-9]+$' || true)
LATEST=$(scripts/release-latest-tag.sh)
RANGE=${LATEST:+$LATEST..}HEAD
echo "Latest tag: ${LATEST:-none}; range: $RANGE"
git log -z --format=%B "$RANGE" | scripts/release-version.sh "$LATEST" | tee -a "$GITHUB_OUTPUT"
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
QUINT ?= $(shell command -v quint 2>/dev/null || echo node $$HOME/.hermes/node/lib/node_modules/@informalsystems/quint/dist/src/cli.js)
SPEC ?= spec/docker_socket_policy.qnt
LISTENER_SPEC ?= spec/listener.qnt
ROUTER_SPEC := spec/router.qnt
ROUTER_SPEC ?= spec/router.qnt
BACKEND ?=

.PHONY: build clean test lint verify typecheck test-spec validate ci-verify release-verify test-release
Expand Down Expand Up @@ -162,6 +162,7 @@ validate: typecheck verify lint-go test-go

test-release:
bash scripts/release-version_test.sh
bash scripts/release-latest-tag_test.sh

# ─── Reproducible build verification ─────────────────

Expand Down
4 changes: 2 additions & 2 deletions deploy/test-sock.sh
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ echo ""
echo "--- proxy-granted (GID 2001, has group access) ---"

S=$(get_status "$GRANTED_SOCK" "$URL/_ping")
check "GET /_ping -> 200" "200" "$S"
check "GET /_ping -> 200 (granted)" "200" "$S"

S=$(get_status "$GRANTED_SOCK" "$URL/version")
check "GET /version -> 200" "200" "$S"
Expand All @@ -211,7 +211,7 @@ echo ""
echo "--- proxy-default-group (GID 65532, supplementary 2001) ---"

S=$(get_status "$DEFAULT_SOCK" "$URL/_ping")
check "GET /_ping -> 200" "200" "$S"
check "GET /_ping -> 200 (default group)" "200" "$S"

# ─── proxy-denied: should return 403 ──────────────────

Expand Down
2 changes: 2 additions & 0 deletions docs/release-versioning.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,7 @@ Other examples: `fix!: x` on v1.4.2 gives v2.0.0. `feat: x` on v1.4.2 gives v1.5
|---|---|
| `scripts/release-version.sh` | Reads NUL-separated commit messages on stdin. Takes the latest tag (or empty) as its argument. Prints `bump=…` and `tag=vX.Y.Z`. |
| `scripts/release-notes.sh` | Reads the same input and prints the breaking-changes section, or nothing. |
| `scripts/release-latest-tag.sh` | Prints the newest strict `vX.Y.Z` tag merged into a ref (default `HEAD`), or nothing if there is none. Fails if git fails. |
| `scripts/release-version_test.sh` | One test case per row of the rule table, plus edge cases. |
| `scripts/release-latest-tag_test.sh` | Builds throwaway repos: no tags, non-strict tags, version sort, pre-releases, unmerged tags, an explicit ref, a git failure, and a ref that does not resolve. |
| `make test-release` | Runs the tests. The tests also run in CI (`release-scripts` job). |
9 changes: 9 additions & 0 deletions scripts/release-latest-tag.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
# Prints the newest strict vX.Y.Z tag merged into REF (default HEAD), or nothing if
# there is none. Fails if git fails. Rules: docs/release-versioning.md
set -euo pipefail

REF=${1:-HEAD}

tags=$(git tag --list --merged "$REF" --sort=-v:refname)
printf '%s\n' "$tags" | grep -E -m1 '^v[0-9]+\.[0-9]+\.[0-9]+$' || true
184 changes: 184 additions & 0 deletions scripts/release-latest-tag_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#!/usr/bin/env bash
# Tests for release-latest-tag.sh: the newest strict vX.Y.Z tag merged into a ref.
# Plain bash, portable to macOS /bin/bash 3.2.
set -u

DIR=$(cd "$(dirname "$0")" && pwd)
SCRIPT="$DIR/release-latest-tag.sh"

TMP=$(mktemp -d "${TMPDIR:-/tmp}/release-latest-tag-test.XXXXXX")
trap 'rm -rf "$TMP"' EXIT

# Keep the user's and the system's git config (tag.sort, versionsort.suffix, signing,
# init.defaultBranch) out of the throwaway repos and out of the script under test.
export GIT_CONFIG_NOSYSTEM=1
export GIT_CONFIG_GLOBAL=/dev/null
# Never walk up from $TMP into an enclosing repo.
export GIT_CEILING_DIRECTORIES="$TMP"

passed=0
failed=0

pass() {
passed=$((passed + 1))
echo "PASS: $1"
}

fail() {
failed=$((failed + 1))
echo "FAIL: $1 ($2)"
}

# unrunnable — prints why the script cannot be run, or nothing. Checked up front so a
# missing script never counts as the non-zero exit the failure case is looking for.
unrunnable() {
if [ ! -f "$SCRIPT" ]; then
echo "script not found: $SCRIPT"
elif [ ! -x "$SCRIPT" ]; then
echo "script not executable: $SCRIPT"
fi
}

# gitc ARGS... — git with a fixed identity and no signing or hooks, so it runs in CI.
gitc() {
git -c user.name=t -c user.email=t@t -c commit.gpgSign=false -c tag.gpgSign=false \
-c core.hooksPath=/dev/null "$@"
}

# mkrepo NAME — creates an empty repo on branch main and prints its path.
mkrepo() {
local repo="$TMP/$1"
gitc init -q "$repo" && gitc -C "$repo" symbolic-ref HEAD refs/heads/main && echo "$repo"
}

# commit_tag REPO TAG... — makes an empty commit in REPO and puts every TAG on it.
commit_tag() {
local repo=$1 t
shift
gitc -C "$repo" commit -q --allow-empty -m "c" || return 1
for t in "$@"; do
gitc -C "$repo" tag "$t" || return 1
done
}

# run DIR ARGS... — runs the script (directly, so the executable bit and shebang are
# exercised) from DIR; sets rc, out, err.
run() {
local dir=$1
shift
(cd "$dir" && "$SCRIPT" "$@") >"$TMP/out" 2>"$TMP/err"
rc=$?
out=$(cat "$TMP/out")
err=$(cat "$TMP/err")
}

# expect NAME WANT DIR ARGS... — exits 0 and prints exactly WANT (empty = nothing).
expect() {
local name=$1 want=$2 dir=$3 why
shift 3
why=$(unrunnable)
if [ -n "$why" ]; then
fail "$name" "want '$want', got $why"
return
fi
run "$dir" "$@"
if [ "$rc" -ne 0 ]; then
fail "$name" "want '$want', got exit $rc: $err"
elif [ "$out" != "$want" ]; then
fail "$name" "want '$want', got '$out'"
else
pass "$name"
fi
}

# setup_failed NAME — records a case whose throwaway repo could not be built.
setup_failed() {
fail "$1" "want a throwaway git repo, got git failure"
}

# ─── no-tags ─────────────────────────────────────────
if r=$(mkrepo no-tags) && commit_tag "$r"; then
expect no-tags "" "$r"
else
setup_failed no-tags
fi

# ─── strict-only ─────────────────────────────────────
if r=$(mkrepo strict-only) && commit_tag "$r" v1.0.0-rc1 vfoo 1.2.3; then
expect strict-only "" "$r"
else
setup_failed strict-only
fi

# ─── version-sort ────────────────────────────────────
if r=$(mkrepo version-sort) && commit_tag "$r" v0.2.0 v0.10.0 v0.9.1; then
expect version-sort v0.10.0 "$r"
else
setup_failed version-sort
fi

# ─── ignores-prerelease ──────────────────────────────
if r=$(mkrepo ignores-prerelease) && commit_tag "$r" v0.3.0 && commit_tag "$r" v0.4.0-rc1; then
expect ignores-prerelease v0.3.0 "$r"
else
setup_failed ignores-prerelease
fi

# ─── merged-only ─────────────────────────────────────
if r=$(mkrepo merged-only) && commit_tag "$r" v0.1.0 &&
gitc -C "$r" checkout -q -b side && commit_tag "$r" v9.9.9 &&
gitc -C "$r" checkout -q main; then
expect merged-only v0.1.0 "$r"
else
setup_failed merged-only
fi

# ─── explicit-ref ────────────────────────────────────
# HEAD (main) sees only v0.1.0; the side branch also has v9.9.9.
if r=$(mkrepo explicit-ref) && commit_tag "$r" v0.1.0 &&
gitc -C "$r" checkout -q -b side && commit_tag "$r" v9.9.9 &&
gitc -C "$r" checkout -q main && commit_tag "$r" v0.2.0; then
expect explicit-ref/side v9.9.9 "$r" side
expect explicit-ref/older-tag v0.1.0 "$r" v0.1.0
else
setup_failed explicit-ref
fi

# ─── git-failure ─────────────────────────────────────
# Outside any repo: git fails, so the script must fail rather than print nothing.
why=$(unrunnable)
if [ -n "$why" ]; then
fail git-failure "want non-zero exit, got $why"
elif ! mkdir "$TMP/not-a-repo"; then
fail git-failure "want an empty directory, got mkdir failure"
else
GIT_DIR="$TMP/nonexistent" run "$TMP/not-a-repo"
if [ "$rc" -eq 0 ]; then
fail git-failure "want non-zero exit, got exit 0: '$out'"
elif [ -n "$out" ]; then
fail git-failure "want empty stdout, got '$out'"
else
pass git-failure
fi
fi

# ─── bad-ref ─────────────────────────────────────────
# A REF that does not resolve: git fails, so the script must fail rather than print nothing.
if r=$(mkrepo bad-ref) && commit_tag "$r" v0.1.0; then
run "$r" nonexistent-ref
if [ -n "$(unrunnable)" ]; then
fail bad-ref "want non-zero exit, got $(unrunnable)"
elif [ "$rc" -eq 0 ]; then
fail bad-ref "want non-zero exit, got exit 0: '$out'"
elif [ -n "$out" ]; then
fail bad-ref "want empty stdout, got '$out'"
else
pass bad-ref
fi
else
setup_failed bad-ref
fi

echo
echo "$passed passed, $failed failed"
[ "$failed" -eq 0 ]
Loading