package testenv import ( "context" "fmt" "os/exec" "path/filepath" "runtime" "sync" "testing" "time" ) const ( BackendImage = "galaxy/backend:integration" GatewayImage = "galaxy/gateway:integration" GameImage = "galaxy/game:integration" ) var ( backendOnce sync.Once backendErr error gatewayOnce sync.Once gatewayErr error gameOnce sync.Once gameErr error ) // EnsureBackendImage builds galaxy/backend:integration once per // process. Subsequent calls reuse the result. func EnsureBackendImage(t *testing.T) { t.Helper() backendOnce.Do(func() { backendErr = buildImage(BackendImage, "backend/Dockerfile") }) if backendErr != nil { t.Skipf("build %s: %v", BackendImage, backendErr) } } // EnsureGatewayImage builds galaxy/gateway:integration once per // process. func EnsureGatewayImage(t *testing.T) { t.Helper() gatewayOnce.Do(func() { gatewayErr = buildImage(GatewayImage, "gateway/Dockerfile") }) if gatewayErr != nil { t.Skipf("build %s: %v", GatewayImage, gatewayErr) } } // EnsureGameImage builds galaxy/game:integration once per process. func EnsureGameImage(t *testing.T) { t.Helper() gameOnce.Do(func() { gameErr = buildImage(GameImage, "game/Dockerfile") }) if gameErr != nil { t.Skipf("build %s: %v", GameImage, gameErr) } } // integrationImageLabel is the docker label stamped onto every image // built from `integration/testenv/images.go`. The pre-clean script // (`integration/scripts/preclean.sh`) keys off this label to wipe // stale builds without touching testcontainers-pulled service images // (postgres, redis, ryuk, mailpit) which we want to keep cached. const integrationImageLabel = "galaxy.test.kind=integration-image" func buildImage(tag, dockerfile string) error { root, err := workspaceRoot() if err != nil { return err } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() cmd := exec.CommandContext(ctx, "docker", "build", "-t", tag, "-f", filepath.Join(root, dockerfile), "--label", integrationImageLabel, root, ) out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("docker build %s: %v\n%s", tag, err, string(out)) } return nil } func workspaceRoot() (string, error) { _, file, _, ok := runtime.Caller(0) if !ok { return "", fmt.Errorf("runtime.Caller failed") } // integration/testenv/images.go → workspace root return filepath.Dir(filepath.Dir(filepath.Dir(file))), nil }