package api
import (
"archive/tar"
"bufio"
"compress/gzip"
"crypto/sha256"
"encoding/hex"
"io"
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strings"
"testing"
)
// releaseStub stands in for build-linux.sh: a quarry that prints its
// version, a zit, and, unless NO_RUNNERS is set, every runner download
// the tree's build script lists with their SHA256SUMS, as the real build
// lays them out. An arm64 build's binaries start with an ELF header for
// aarch64 (x86-64 with STUB_ARM64_WRONG set). Each call is logged to
// RELEASE_STUB_LOG with whether it could see the run token.
const releaseStub = `#!/bin/sh
set -eu
arch=$1 prefix=$2
echo "build $arch runners=${NO_RUNNERS:-yes} token=${QUARRY_TOKEN:-} zitpw=${ZIT_PASSWORD:-}" >>"$RELEASE_STUB_LOG"
mkdir -p "$prefix"
if [ "$arch" = arm64 ]; then
m='\267'; [ -z "${STUB_ARM64_WRONG:-}" ] || m='>'
for b in quarry zit; do
printf '\177ELF\002\001\001\000\000\000\000\000\000\000\000\000\002\000'"$m"'\000 %s %s\n' "$b" "$VERSION" >"$prefix/$b"
done
else
printf '#!/bin/sh\necho "quarry %s"\n' "$VERSION" >"$prefix/quarry"
printf '#!/bin/sh\necho "zit 0.0.0 linux-%s"\n' "$arch" >"$prefix/zit"
fi
chmod 0755 "$prefix/quarry" "$prefix/zit"
[ -z "${NO_RUNNERS:-}" ] || exit 0
mkdir -p "$prefix/runner"
for t in $(sed -n 's/^RUNNER_TARGETS="\(.*\)"$/\1/p' scripts/build-linux.sh); do
os=${t%/*}; a=${t#*/}; ext=; [ "$os" = windows ] && ext=.exe
echo "runner $t $VERSION" >"$prefix/runner/quarry-runner-$os-$a$ext"
done
if command -v sha256sum >/dev/null 2>&1; then s="sha256sum"; else s="shasum -a 256"; fi
(cd "$prefix/runner" && $s quarry-runner-* >SHA256SUMS)
`
// releaseTree is a throwaway quarry-shaped repository at version ver,
// carrying the scripts release.sh runs and tagged version-<ver>; it
// returns the checkout and the tagged check-in.
func releaseTree(t *testing.T, ver string) (string, string) {
t.Helper()
for _, bin := range []string{"zit", "sh", "tar", "gzip"} {
if _, err := exec.LookPath(bin); err != nil {
t.Skipf("%s not on PATH", bin)
}
}
dir := t.TempDir()
zitIn(t, dir, "init")
files := map[string][]byte{
"VERSION": []byte(ver + "\n"),
"CHANGELOG.md": []byte("# Changelog\n\n## " + ver + " — 2026-10-02\n\n- A release.\n\n## 0.1.0\n\n- Before.\n"),
"LICENSE": []byte("license\n"),
}
for _, s := range []string{"release.sh", "version-stamp.sh", "runner-check.sh", "build-linux.sh"} {
raw, err := os.ReadFile(filepath.Join("..", "..", "scripts", s))
if err != nil {
t.Fatal(err)
}
files["scripts/"+s] = raw
}
for name, body := range files {
p := filepath.Join(dir, filepath.FromSlash(name))
if err := os.MkdirAll(filepath.Dir(p), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(p, body, 0o644); err != nil {
t.Fatal(err)
}
zitIn(t, dir, "add", name)
}
zitIn(t, dir, "commit", "-m", "release "+ver)
checkin := strings.SplitN(zitIn(t, dir, "info", "current"), "\n", 2)[0]
zitIn(t, dir, "tag", "add", "version-"+ver, checkin)
return dir, checkin
}
func zitIn(t *testing.T, dir string, args ...string) string {
t.Helper()
cmd := exec.Command("zit", args...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("zit %v: %v\n%s", args, err, out)
}
return string(out)
}
// runRelease runs release.sh in dir with the stub builder, logging its
// builds to log; env comes last, so it overrides.
func runRelease(t *testing.T, dir, log string, env []string, args ...string) (string, error) {
t.Helper()
stub := filepath.Join(t.TempDir(), "build-stub.sh")
if err := os.WriteFile(stub, []byte(releaseStub), 0o755); err != nil {
t.Fatal(err)
}
cmd := exec.Command("sh", append([]string{"scripts/release.sh"}, args...)...)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "RELEASE_BUILD=sh "+stub, "RELEASE_STUB_LOG="+log, "TMPDIR="+t.TempDir(),
"QUARRY_TAG=", "QUARRY_USER=", "QUARRY_TOKEN=", "ZIT_PASSWORD=", "RELEASE_ARM64=")
cmd.Env = append(cmd.Env, env...)
b, err := cmd.CombinedOutput()
return string(b), err
}
func readLog(t *testing.T, p string) string {
t.Helper()
b, err := os.ReadFile(p)
if err != nil && !os.IsNotExist(err) {
t.Fatal(err)
}
return string(b)
}
func fileSum(t *testing.T, p string) (string, int64) {
t.Helper()
raw, err := os.ReadFile(p)
if err != nil {
t.Fatal(err)
}
h := sha256.Sum256(raw)
return hex.EncodeToString(h[:]), int64(len(raw))
}
// RELEASE.SH --DRY-RUN WRITES A RELEASE THE FORGE AND DEPLOY.SH READ
// (RFC 0020 §releases): the manifest parses as the Releases page parses
// it, every sha256 in it is SHA256SUMS' and the bytes', and the tarballs
// are the one-directory layout deploy.sh --release unpacks.
func TestReleaseScriptDryRunWritesWhatTheForgeReads(t *testing.T) {
const ver = "9.8.7"
dir, checkin := releaseTree(t, ver)
out := filepath.Join(t.TempDir(), "out")
builds := filepath.Join(t.TempDir(), "builds")
if log, err := runRelease(t, dir, builds, nil, "--dry-run", "--out", out, ver); err != nil {
t.Fatalf("FAIL: release.sh --dry-run: %v\n%s", err, log)
}
// linux-arm64 is opt-in: one build, amd64, with the runner downloads.
if got := readLog(t, builds); got != "build amd64 runners=yes token= zitpw=\n" {
t.Errorf("FAIL: the builds were\n%s\nwant amd64 alone, with the runners", got)
}
// SHA256SUMS: one sum and one bare name per line, as deploy.sh
// insists, and a line for every file but itself and the manifest.
sums := map[string]string{}
f, err := os.Open(filepath.Join(out, "SHA256SUMS"))
if err != nil {
t.Fatal(err)
}
line := regexp.MustCompile(`^([0-9a-f]{64}) [ *]([A-Za-z0-9_][A-Za-z0-9._-]*)$`)
sc := bufio.NewScanner(f)
for sc.Scan() {
m := line.FindStringSubmatch(sc.Text())
if m == nil {
t.Errorf("FAIL: SHA256SUMS line %q is not a sum and a bare name", sc.Text())
continue
}
sums[m[2]] = m[1]
}
f.Close()
entries, err := os.ReadDir(out)
if err != nil {
t.Fatal(err)
}
var names []string
for _, e := range entries {
n := e.Name()
names = append(names, n)
if n == "SHA256SUMS" || n == "release-manifest.json" {
continue
}
got, _ := fileSum(t, filepath.Join(out, n))
if sums[n] != got {
t.Errorf("FAIL: SHA256SUMS says %s is %q; its bytes are %s", n, sums[n], got)
}
}
sort.Strings(names)
want := []string{
"SHA256SUMS",
"quarry-9.8.7-linux-amd64.tar.gz", "quarry-9.8.7-src.tar.gz",
"quarry-runner-darwin-amd64", "quarry-runner-darwin-arm64", "quarry-runner-linux-amd64",
"quarry-runner-linux-arm64", "quarry-runner-windows-amd64.exe",
"release-manifest.json",
}
if strings.Join(names, " ") != strings.Join(want, " ") {
t.Errorf("FAIL: the release holds\n %v\nwant\n %v", names, want)
}
if len(sums) != len(want)-2 {
t.Errorf("FAIL: SHA256SUMS has %d lines; want %d", len(sums), len(want)-2)
}
// The manifest, through the Releases page's own parser.
raw, err := os.ReadFile(filepath.Join(out, "release-manifest.json"))
if err != nil {
t.Fatal(err)
}
m, err := parseReleaseManifest(raw, ver)
if err != nil {
t.Fatalf("FAIL: the forge refuses the manifest: %v\n%s", err, raw)
}
if m.Version != ver || m.Tag != "version-"+ver || m.Checkin != checkin || m.Date == "" {
t.Errorf("FAIL: manifest release = %q %q %q %q; want %s, version-%s, %s and a date", m.Version, m.Tag, m.Checkin, m.Date, ver, ver, checkin)
}
described := append([]releaseArtifact{}, m.Artifacts...)
if m.Source == nil {
t.Error("FAIL: the manifest names no source archive")
} else {
described = append(described, *m.Source)
}
seen := map[string]bool{}
for _, a := range described {
seen[a.Name] = true
got, size := fileSum(t, filepath.Join(out, a.Name))
if a.SHA256 != got || a.SHA256 != sums[a.Name] || a.Size != size {
t.Errorf("FAIL: manifest says %s is %s, %d bytes; its bytes are %s, %d; SHA256SUMS says %s", a.Name, a.SHA256, a.Size, got, size, sums[a.Name])
}
if a.Target == "" || a.Qualification == "" {
t.Errorf("FAIL: %s has target %q and qualification %q; want both", a.Name, a.Target, a.Qualification)
}
}
for n := range sums {
if !seen[n] {
t.Errorf("FAIL: %s is published and the manifest does not describe it", n)
}
}
omitted := map[string]bool{}
for _, o := range m.Omitted {
omitted[o.Target] = o.Reason != ""
}
for _, t2 := range []string{"linux-arm64", "darwin-arm64", "darwin-amd64", "windows-amd64"} {
if !omitted[t2] {
t.Errorf("FAIL: the manifest does not name %s as omitted, with a reason: %+v", t2, m.Omitted)
}
}
if len(m.Qualification) == 0 {
t.Error("FAIL: the manifest carries no qualification notes")
}
// The layout deploy.sh --release unpacks: one directory, holding
// quarry, zit, CHANGELOG.md and LICENSE, and nothing else.
checkLayout(t, out, ver, "amd64")
t.Log("ok: the dry run's manifest parses, its sums are SHA256SUMS' and the bytes', the tarballs are deploy's layout")
}
// checkLayout holds a forge tarball to the layout deploy.sh --release
// unpacks: one directory, holding quarry, zit, CHANGELOG.md and LICENSE.
func checkLayout(t *testing.T, out, ver, arch string) {
t.Helper()
top := "quarry-" + ver + "-linux-" + arch
members := tarMembers(t, filepath.Join(out, top+".tar.gz"))
want := []string{top + "/", top + "/CHANGELOG.md", top + "/LICENSE", top + "/quarry", top + "/zit"}
if strings.Join(members, " ") != strings.Join(want, " ") {
t.Errorf("FAIL: %s.tar.gz holds %v; want %v", top, members, want)
}
}
func tarMembers(t *testing.T, p string) []string {
t.Helper()
f, err := os.Open(p)
if err != nil {
t.Fatal(err)
}
defer f.Close()
gz, err := gzip.NewReader(f)
if err != nil {
t.Fatal(err)
}
tr := tar.NewReader(gz)
var out []string
for {
h, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatal(err)
}
name := strings.TrimPrefix(h.Name, "./")
if h.Typeflag == tar.TypeDir && !strings.HasSuffix(name, "/") {
name += "/"
}
out = append(out, name)
}
sort.Strings(out)
return out
}
// RELEASE.SH REFUSES WHAT IS NOT RELEASE X.Y.Z: another version than
// VERSION, a check-in that does not carry the tag itself, an untracked
// file, a changed one, a changelog with no section for it, and a run
// started by another tag.
func TestReleaseScriptRefusesWhatIsNotTheRelease(t *testing.T) {
const ver = "9.8.7"
dir, _ := releaseTree(t, ver)
out := filepath.Join(t.TempDir(), "out")
refused := func(why, want string, args ...string) {
t.Helper()
log, err := runRelease(t, dir, filepath.Join(t.TempDir(), "builds"), nil, append([]string{"--dry-run", "--out", out}, args...)...)
if err == nil || !strings.Contains(log, want) {
t.Errorf("FAIL: %s: err %v, output\n%s\nwant a refusal naming %q", why, err, log, want)
}
}
refused("another version", "VERSION holds 9.8.7, not 9.8.8", "9.8.8")
if err := os.WriteFile(filepath.Join(dir, "stray.txt"), []byte("x\n"), 0o644); err != nil {
t.Fatal(err)
}
refused("an untracked file", "untracked files", ver)
os.Remove(filepath.Join(dir, "stray.txt"))
if err := os.WriteFile(filepath.Join(dir, "LICENSE"), []byte("changed\n"), 0o644); err != nil {
t.Fatal(err)
}
refused("a changed file", "has changes", ver)
zitIn(t, dir, "revert")
if err := os.WriteFile(filepath.Join(dir, "CHANGELOG.md"), []byte("# Changelog\n\n## 9.8.70\n\n## 0.1.0-9.8.7\n"), 0o644); err != nil {
t.Fatal(err)
}
refused("no changelog heading", "no \"## 9.8.7\" heading", ver)
zitIn(t, dir, "revert")
cmd := exec.Command("sh", "scripts/release.sh", "--dry-run", ver)
cmd.Dir = dir
cmd.Env = append(os.Environ(), "QUARRY_TAG=version-9.9.9")
if b, err := cmd.CombinedOutput(); err == nil || !strings.Contains(string(b), "started by the tag version-9.9.9") {
t.Errorf("FAIL: a run started by another tag: err %v, output\n%s", err, b)
}
// The next check-in inherits nothing: the tag is its parent's.
if err := os.WriteFile(filepath.Join(dir, "LICENSE"), []byte("next\n"), 0o644); err != nil {
t.Fatal(err)
}
zitIn(t, dir, "commit", "-m", "after the release")
refused("an untagged check-in", "does not carry version-9.8.7", ver)
t.Log("ok: a wrong version, a dirty tree, no changelog section, another tag and an untagged check-in are refused")
}
// LINUX-ARM64 IS OPT-IN: with RELEASE_ARM64=1 the cross toolchain is
// asked for before anything builds, the arm64 build skips the runner
// downloads (built once, with amd64), and its binaries must be ELF for
// aarch64.
func TestReleaseScriptBuildsArm64OnlyWhenAskedAndChecksIt(t *testing.T) {
const ver = "9.8.7"
dir, _ := releaseTree(t, ver)
builds := filepath.Join(t.TempDir(), "builds")
log, err := runRelease(t, dir, builds, []string{"RELEASE_ARM64=1", "ARM64_CC=no-such-cc-for-quarry", "ARM64_CXX=sh"},
"--dry-run", "--out", filepath.Join(t.TempDir(), "out"), ver)
if err == nil || !strings.Contains(log, "no-such-cc-for-quarry") {
t.Errorf("FAIL: a missing ARM64_CC: err %v, output\n%s\nwant a refusal naming it", err, log)
}
if got := readLog(t, builds); got != "" {
t.Errorf("FAIL: a missing cross compiler still built:\n%s", got)
}
builds = filepath.Join(t.TempDir(), "builds")
log, err = runRelease(t, dir, builds, []string{"RELEASE_ARM64=1", "ARM64_CC=sh", "ARM64_CXX=sh", "STUB_ARM64_WRONG=1"},
"--dry-run", "--out", filepath.Join(t.TempDir(), "out"), ver)
if err == nil || !strings.Contains(log, "aarch64") {
t.Errorf("FAIL: an arm64 build that is not aarch64: err %v, output\n%s", err, log)
}
out := filepath.Join(t.TempDir(), "out")
builds = filepath.Join(t.TempDir(), "builds")
if log, err := runRelease(t, dir, builds, []string{"RELEASE_ARM64=1", "ARM64_CC=sh", "ARM64_CXX=sh"}, "--dry-run", "--out", out, ver); err != nil {
t.Fatalf("FAIL: release.sh --dry-run with arm64: %v\n%s", err, log)
}
if got, want := readLog(t, builds), "build amd64 runners=yes token= zitpw=\nbuild arm64 runners=1 token= zitpw=\n"; got != want {
t.Errorf("FAIL: the builds were\n%s\nwant\n%s", got, want)
}
checkLayout(t, out, ver, "arm64")
raw, err := os.ReadFile(filepath.Join(out, "release-manifest.json"))
if err != nil {
t.Fatal(err)
}
m, err := parseReleaseManifest(raw, ver)
if err != nil {
t.Fatal(err)
}
found := false
for _, a := range m.Artifacts {
found = found || (a.Target == "linux-arm64" && a.Name == "quarry-9.8.7-linux-arm64.tar.gz")
}
for _, o := range m.Omitted {
if o.Target == "linux-arm64" {
t.Errorf("FAIL: linux-arm64 was built and is still listed as omitted")
}
}
if !found {
t.Errorf("FAIL: the manifest does not describe the arm64 tarball: %+v", m.Artifacts)
}
t.Log("ok: arm64 is opt-in, its compiler is asked for first, its binaries must be aarch64, the runners build once")
}
// --OUT IS NEVER CLEARED: a directory with something in it is refused
// and left as it was; an empty one is filled.
func TestReleaseScriptNeverDeletesTheOutDirectory(t *testing.T) {
const ver = "9.8.7"
dir, _ := releaseTree(t, ver)
out := t.TempDir()
keep := filepath.Join(out, "mine.txt")
if err := os.WriteFile(keep, []byte("mine\n"), 0o644); err != nil {
t.Fatal(err)
}
log, err := runRelease(t, dir, filepath.Join(t.TempDir(), "builds"), nil, "--dry-run", "--out", out, ver)
if err == nil || !strings.Contains(log, "not empty") {
t.Errorf("FAIL: a non-empty --out: err %v, output\n%s\nwant a refusal", err, log)
}
if b, err := os.ReadFile(keep); err != nil || string(b) != "mine\n" {
t.Errorf("FAIL: the file in --out is gone or changed: %q, %v", b, err)
}
empty := t.TempDir()
if log, err := runRelease(t, dir, filepath.Join(t.TempDir(), "builds"), nil, "--dry-run", "--out", empty, ver); err != nil {
t.Errorf("FAIL: an empty --out: %v\n%s", err, log)
}
if _, err := os.Stat(filepath.Join(empty, "SHA256SUMS")); err != nil {
t.Errorf("FAIL: the empty --out was not filled: %v", err)
}
// The release is assembled beside --out and renamed in whole: nothing
// of the assembly is left beside it, and nothing nested in it.
for _, d := range []string{filepath.Dir(out), filepath.Dir(empty)} {
if left, _ := filepath.Glob(filepath.Join(d, ".quarry-release-*")); len(left) > 0 {
t.Errorf("FAIL: the assembly directory was left behind: %v", left)
}
}
if nested, _ := filepath.Glob(filepath.Join(empty, ".quarry-release-*")); len(nested) > 0 {
t.Errorf("FAIL: the release was nested inside --out: %v", nested)
}
t.Log("ok: a non-empty --out is refused untouched, an empty one is filled")
}
// zitShim is a zit on PATH for the publish path: `zit uv` is answered
// from UV_DIR, a forge's unversioned files as plain files, printing
// `uv list` as zit 0.3.36 does (uvList in zit's src/cmd/sync.zig: the
// live names, sorted, one per line) and writing `uv pull -o DIR` as
// DIR/<name>; every other verb runs the real zit. Each call's argv and
// the token variables it could see go to UV_LOG.
const zitShim = `#!/bin/sh
{
printf 'argv:'; for a in "$@"; do printf ' %s' "$a"; done; printf '\n'
printf 'env: QUARRY_TOKEN=%s ZIT_PASSWORD=%s\n' "${QUARRY_TOKEN:-}" "${ZIT_PASSWORD:-}"
} >>"$UV_LOG"
[ "$1" = uv ] || exec "$REAL_ZIT" "$@"
verb=$2; shift 3
case "$verb" in
list)
(cd "$UV_DIR" && find . -type f | sed 's|^\./||' | LC_ALL=C sort) ;;
pull)
name=$1; o=.; shift
while [ $# -gt 0 ]; do case "$1" in -o) o=$2; shift ;; esac; shift; done
[ -f "$UV_DIR/$name" ] || { echo "zit uv: the server holds no '$name'" >&2; exit 1; }
mkdir -p "$o/$(dirname "$name")" && cp "$UV_DIR/$name" "$o/$name" ;;
push)
file=$1; name=$1; shift
while [ $# -gt 0 ]; do case "$1" in --as) name=$2; shift ;; esac; shift; done
mkdir -p "$UV_DIR/$(dirname "$name")" && cp "$file" "$UV_DIR/$name" ;;
*) exit 2 ;;
esac
`
// THE PUBLISH PATH (RFC 0020, RFC 0021 §runs): every file goes to
// releases/X.Y.Z/ with SHA256SUMS first and the manifest last, as the
// run's login with its token only in ZIT_PASSWORD; no build and no zit
// call outside the uv verbs sees the token. A re-run finding the manifest
// for this check-in publishes nothing and succeeds; one for another
// check-in fails.
func TestReleaseScriptPublishesInOrderWithTheTokenOnlyInZitsEnvironment(t *testing.T) {
const ver = "9.8.7"
const token = "qrt_7_s3cr3t"
dir, checkin := releaseTree(t, ver)
realZit, err := exec.LookPath("zit")
if err != nil {
t.Skip("zit not on PATH")
}
bin, uvDir := t.TempDir(), t.TempDir()
if err := os.WriteFile(filepath.Join(bin, "zit"), []byte(zitShim), 0o755); err != nil {
t.Fatal(err)
}
// Another release's manifest, whose name a loose match would take for this one's.
decoy := filepath.Join(uvDir, "releases", "9.8.70", "release-manifest.json")
if err := os.MkdirAll(filepath.Dir(decoy), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(decoy, []byte("{}"), 0o644); err != nil {
t.Fatal(err)
}
uvLog := filepath.Join(t.TempDir(), "uv.log")
env := []string{"PATH=" + bin + string(os.PathListSeparator) + os.Getenv("PATH"), "REAL_ZIT=" + realZit,
"UV_DIR=" + uvDir, "UV_LOG=" + uvLog, "QUARRY_USER=run-7", "QUARRY_TOKEN=" + token,
"RELEASE_URL=http://forge.test/r/quarry", "QUARRY_TAG=version-" + ver}
builds := filepath.Join(t.TempDir(), "builds")
out := filepath.Join(t.TempDir(), "out")
if log, err := runRelease(t, dir, builds, env, "--out", out, ver); err != nil {
t.Fatalf("FAIL: release.sh publish: %v\n%s", err, log)
}
if got := readLog(t, builds); strings.Contains(got, token) {
t.Errorf("FAIL: a build saw the token:\n%s", got)
}
// Every call: argv never holds the token; QUARRY_TOKEN is gone from
// every zit; ZIT_PASSWORD is the token on the uv verbs alone.
var pushed []string
lines := strings.Split(strings.TrimSpace(readLog(t, uvLog)), "\n")
for i := 0; i+1 < len(lines); i += 2 {
argv, envLine := lines[i], lines[i+1]
if strings.Contains(argv, token) {
t.Errorf("FAIL: the token is in argv: %s", argv)
}
uv := strings.HasPrefix(argv, "argv: uv ")
want := "env: QUARRY_TOKEN= ZIT_PASSWORD="
if uv {
want += token
if !strings.Contains(argv, " --user run-7") || !strings.Contains(argv, " http://forge.test/r/quarry") {
t.Errorf("FAIL: a uv call is not to the forge as run-7: %s", argv)
}
}
if envLine != want {
t.Errorf("FAIL: %s ran with %q; want %q", argv, envLine, want)
}
if f := strings.Fields(argv); uv && f[2] == "push" {
for j, w := range f {
if w == "--as" && j+1 < len(f) {
pushed = append(pushed, f[j+1])
}
}
}
}
entries, err := os.ReadDir(out)
if err != nil {
t.Fatal(err)
}
if len(pushed) != len(entries) {
t.Errorf("FAIL: pushed %d files; the release has %d: %v", len(pushed), len(entries), pushed)
}
if len(pushed) < 2 || pushed[0] != "releases/9.8.7/SHA256SUMS" || pushed[len(pushed)-1] != "releases/9.8.7/release-manifest.json" {
t.Errorf("FAIL: the push order was %v; want SHA256SUMS first and the manifest last", pushed)
}
for _, e := range entries {
want, _ := fileSum(t, filepath.Join(out, e.Name()))
got, _ := fileSum(t, filepath.Join(uvDir, "releases", ver, e.Name()))
if got != want {
t.Errorf("FAIL: the forge's %s is not the release's", e.Name())
}
}
// Re-run: the forge holds this check-in's manifest; nothing is built or pushed.
before := readLog(t, uvLog)
builds2 := filepath.Join(t.TempDir(), "builds")
log, err := runRelease(t, dir, builds2, env, "--out", filepath.Join(t.TempDir(), "out"), ver)
if err != nil || !strings.Contains(log, "already holds") {
t.Errorf("FAIL: a re-run against the published release: err %v, output\n%s", err, log)
}
if strings.Contains(strings.TrimPrefix(readLog(t, uvLog), before), "uv push") || readLog(t, builds2) != "" {
t.Errorf("FAIL: a re-run built or pushed again")
}
// The forge's manifest names another check-in: refused, nothing pushed.
held := filepath.Join(uvDir, "releases", ver, "release-manifest.json")
raw, err := os.ReadFile(held)
if err != nil {
t.Fatal(err)
}
other := strings.Repeat("ab", len(checkin)/2)
if err := os.WriteFile(held, []byte(strings.ReplaceAll(string(raw), checkin, other)), 0o644); err != nil {
t.Fatal(err)
}
before = readLog(t, uvLog)
log, err = runRelease(t, dir, filepath.Join(t.TempDir(), "builds"), env, "--out", filepath.Join(t.TempDir(), "out"), ver)
if err == nil || !strings.Contains(log, other[:12]) {
t.Errorf("FAIL: a held manifest for another check-in: err %v, output\n%s\nwant a refusal naming %s", err, log, other[:12])
}
if strings.Contains(strings.TrimPrefix(readLog(t, uvLog), before), "uv push") {
t.Errorf("FAIL: a mismatched release was pushed over")
}
t.Log("ok: SHA256SUMS first, manifest last, the token only in the uv calls' ZIT_PASSWORD; a re-run is a no-op, a mismatch fails")
}