From 6facee4b9b07203008b0e91d4503a80d2970a09e Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Sun, 12 Apr 2026 10:51:42 +1000 Subject: [PATCH 1/2] fix: remove stale MinIO container before starting When the MinIO test container crashes, `docker run` fails with "already in use" but the old code silently ignored this, causing waitForReady to timeout. Now we unconditionally `docker rm -f` before starting. Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/s3client/s3clienttest/s3clienttest.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/internal/s3client/s3clienttest/s3clienttest.go b/internal/s3client/s3clienttest/s3clienttest.go index 866696b..0f22bea 100644 --- a/internal/s3client/s3clienttest/s3clienttest.go +++ b/internal/s3client/s3clienttest/s3clienttest.go @@ -121,6 +121,9 @@ func Client(t *testing.T) *minio.Client { func startContainer(t *testing.T) { t.Helper() + // Remove any stale container (e.g. from a previous crashed run) before starting. + //nolint:errcheck,gosec // Best-effort removal of a possibly non-existent container. + exec.CommandContext(t.Context(), "docker", "rm", "-f", containerName).Run() cmd := exec.CommandContext(t.Context(), "docker", "run", "-d", "--name", containerName, "-p", Port+":9000", @@ -129,9 +132,7 @@ func startContainer(t *testing.T) { "minio/minio", "server", "/data", ) if output, err := cmd.CombinedOutput(); err != nil { - if !strings.Contains(string(output), "already in use") { - t.Fatalf("failed to start minio container: %v\n%s", err, output) - } + t.Fatalf("failed to start minio container: %v\n%s", err, output) } } From 7ee28d3b350d5d3b368dda86c174767541f38336 Mon Sep 17 00:00:00 2001 From: Alec Thomas Date: Sun, 12 Apr 2026 16:15:23 +1000 Subject: [PATCH 2/2] fix: restart stopped MinIO container instead of removing When the container exists but is stopped, use `docker start` to restart it rather than `docker rm -f` which could race with other test packages using the same container in CI. Co-Authored-By: Claude Opus 4.6 (1M context) --- BUILD.bit | 64 +++++++++++++++++++ .../s3client/s3clienttest/s3clienttest.go | 13 ++-- 2 files changed, 73 insertions(+), 4 deletions(-) create mode 100644 BUILD.bit diff --git a/BUILD.bit b/BUILD.bit new file mode 100644 index 0000000..9b88534 --- /dev/null +++ b/BUILD.bit @@ -0,0 +1,64 @@ +# Enable verbose mode +param verbose : bool = false + +# Build client and server binaries +cachew = go.exe { + package = "./cmd/cachew" + output = "dist/cachew" +} + +cachewd = go.exe { + package = "./cmd/cachewd" + output = "dist/cachewd" +} + +# Cross-compile Linux binaries for Docker +cachew-linux = go.exe { + package = "./cmd/cachew" + output = "dist/cachew-linux-arm64" + cgo = false + goos = "linux" + goarch = "amd64" +} + +cachewd-linux = go.exe { + package = "./cmd/cachewd" + output = "dist/cachewd-linux-arm64" + cgo = false + goos = "linux" + goarch = "arm64" +} + +# Tests +test = go.test { + package = "./..." + flags = ["-timeout", "30s", "-race"] + verbose = verbose +} + +# Lint +lint = go.lint {} + +# Docker image +image = docker.image { + tag = "cachew:latest" + context = "." + dockerfile = "docker/Dockerfile" + depends_on = [cachew-linux, cachewd-linux] +} + +container = docker.container { + image = image.ref + name = "cachew" + ports = ["8080:8080"] + healthcheck = "curl -sf http://localhost:8080/_readiness" +} + +# Build binaries +target build = [cachew, cachewd] + +# Run tests and linting +target test = [test, lint] + +# Build and run Docker container +target docker = [container] diff --git a/internal/s3client/s3clienttest/s3clienttest.go b/internal/s3client/s3clienttest/s3clienttest.go index 0f22bea..71ecb69 100644 --- a/internal/s3client/s3clienttest/s3clienttest.go +++ b/internal/s3client/s3clienttest/s3clienttest.go @@ -121,9 +121,6 @@ func Client(t *testing.T) *minio.Client { func startContainer(t *testing.T) { t.Helper() - // Remove any stale container (e.g. from a previous crashed run) before starting. - //nolint:errcheck,gosec // Best-effort removal of a possibly non-existent container. - exec.CommandContext(t.Context(), "docker", "rm", "-f", containerName).Run() cmd := exec.CommandContext(t.Context(), "docker", "run", "-d", "--name", containerName, "-p", Port+":9000", @@ -131,9 +128,17 @@ func startContainer(t *testing.T) { "-e", "MINIO_ROOT_PASSWORD="+Password, "minio/minio", "server", "/data", ) - if output, err := cmd.CombinedOutput(); err != nil { + output, err := cmd.CombinedOutput() + if err == nil { + return + } + if !strings.Contains(string(output), "already in use") { t.Fatalf("failed to start minio container: %v\n%s", err, output) } + // Container exists but may be stopped — try restarting it. + if restartOut, restartErr := exec.CommandContext(t.Context(), "docker", "start", containerName).CombinedOutput(); restartErr != nil { + t.Fatalf("failed to restart existing minio container: %v\n%s", restartErr, restartOut) + } } func isHealthy(t *testing.T) bool {