package main import ( "encoding/json" "fmt" "os" "os/exec" "path/filepath" "runtime" "strings" "testing" ) func TestReleaseWrapperPreservesNativeExitStatuses(t *testing.T) { if runtime.GOOS == "windows" { t.Skip("POSIX release entry point") } _, source, _, _ := runtime.Caller(0) root := filepath.Clean(filepath.Join(filepath.Dir(source), "../..")) originalPath := os.Getenv("PATH") realGit, err := exec.LookPath("git") if err != nil { t.Fatal(err) } sources := releaseWrapperSources(t, root) for _, mode := range []struct { name string status int version string }{{"success", 0, "v1.2.3"}, {"invalid", 2, "invalid"}, {"rejected", 1, "v1.2.3"}, {"unknown", 3, "v1.2.3"}} { t.Run(mode.name, func(t *testing.T) { repository := filepath.Join(t.TempDir(), "source") for path, contents := range sources { name := filepath.Join(repository, path) if err := os.MkdirAll(filepath.Dir(name), 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(name, contents, 0600); err != nil { t.Fatal(err) } } if err := os.Chmod(filepath.Join(repository, "scripts/release"), 0755); err != nil { t.Fatal(err) } cliGitRun(t, repository, "init", "--initial-branch=main") cliGitRun(t, repository, "config", "user.name", "ReleaseKit Test") cliGitRun(t, repository, "config", "user.email", "fixture@example.test") cliGitRun(t, repository, "config", "commit.gpgsign", "false") validationLog := filepath.Join(t.TempDir(), "validation.log") t.Setenv("RELEASEKIT_TEST_CANDIDATE_LOG", validationLog) candidateTest := `package releasekit import ( "os" "testing" ) func TestReleaseCandidate(t *testing.T) { f, err := os.OpenFile(os.Getenv("RELEASEKIT_TEST_CANDIDATE_LOG"), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600) if err != nil { t.Fatal(err) } defer f.Close() if _, err := f.WriteString("validated\n"); err != nil { t.Fatal(err) } } ` if err := os.WriteFile(filepath.Join(repository, "candidate_test.go"), []byte(candidateTest), 0600); err != nil { t.Fatal(err) } if err := os.MkdirAll(filepath.Join(repository, "docs/releases"), 0700); err != nil { t.Fatal(err) } if err := os.WriteFile(filepath.Join(repository, "docs/releases/v1.2.3.md"), []byte("# ReleaseKit v1.2.3\n"), 0600); err != nil { t.Fatal(err) } profile := map[string]any{ "product": "ReleaseKit", "checks": []map[string]any{{"args": []string{"sh", "-c", "if [ -n \"$RELEASEKIT_TEST_ARM_GIT_SHIM\" ]; then : > \"$RELEASEKIT_TEST_ARM_GIT_SHIM\"; fi"}}}, "delivery": map[string]any{ "base_url": "https://gitea.maximumdirect.net", "owner": "eric", "repository": "ReleaseKit", "token_env": "RELEASEKIT_GITEA_TOKEN", }, } content, _ := json.Marshal(profile) if err := os.WriteFile(filepath.Join(repository, "releasekit.json"), content, 0600); err != nil { t.Fatal(err) } cliGitRun(t, repository, "add", "--all") cliGitRun(t, repository, "commit", "-m", "prepare wrapper fixture") remote := filepath.Join(t.TempDir(), "origin.git") cliGitRun(t, repository, "init", "--bare", remote) cliGitRun(t, repository, "remote", "add", "origin", remote) cliGitRun(t, repository, "push", "origin", "HEAD:refs/heads/main") fetch := filepath.Join(t.TempDir(), "fetch.git") cliGitRun(t, repository, "init", "--bare", fetch) cliGitRun(t, repository, "remote", "set-url", "origin", fetch) cliGitRun(t, repository, "remote", "set-url", "--push", "origin", remote) if mode.name == "rejected" { if err := os.WriteFile(filepath.Join(remote, "hooks/pre-receive"), []byte("#!/bin/sh\nexit 1\n"), 0755); err != nil { t.Fatal(err) } } if mode.name == "unknown" { bin := t.TempDir() marker := filepath.Join(bin, "push-started") armMarker := filepath.Join(bin, "arm-git-shim") shim := `#!/bin/sh if [ -f "$RELEASEKIT_TEST_ARM_GIT_SHIM" ]; then for arg do if [ "$arg" = push ]; then "$RELEASEKIT_TEST_REAL_GIT" "$@" || exit 1 "$RELEASEKIT_TEST_REAL_GIT" remote set-url --push origin "$RELEASEKIT_TEST_MOVED_ENDPOINT" : > "$RELEASEKIT_TEST_PUSH_MARKER" exit 1 fi done fi if [ -f "$RELEASEKIT_TEST_ARM_GIT_SHIM" ] && [ -f "$RELEASEKIT_TEST_PUSH_MARKER" ]; then for arg do if [ "$arg" = ls-remote ]; then exit 128; fi; done fi exec "$RELEASEKIT_TEST_REAL_GIT" "$@" ` if err := os.WriteFile(filepath.Join(bin, "git"), []byte(shim), 0755); err != nil { t.Fatal(err) } t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH")) t.Setenv("RELEASEKIT_TEST_REAL_GIT", realGit) t.Setenv("RELEASEKIT_TEST_PUSH_MARKER", marker) t.Setenv("RELEASEKIT_TEST_MOVED_ENDPOINT", fetch) t.Setenv("RELEASEKIT_TEST_ARM_GIT_SHIM", armMarker) } command := exec.Command("./scripts/release", mode.version) command.Dir = repository output, err := command.CombinedOutput() status := 0 if err != nil { exit, ok := err.(*exec.ExitError) if !ok { t.Fatal(err) } status = exit.ExitCode() } if status != mode.status { t.Fatalf("status=%d want=%d output=%s", status, mode.status, output) } validated, readErr := os.ReadFile(validationLog) if mode.name == "invalid" { if !os.IsNotExist(readErr) { t.Fatalf("invalid input ran candidate validation: %q, %v", validated, readErr) } } else if readErr != nil || string(validated) != "validated\n" { t.Fatalf("candidate suite must run once: %q, %v", validated, readErr) } if mode.name == "unknown" { if !strings.Contains(string(output), fmt.Sprintf("endpoint=%q", remote)) { t.Fatalf("captured endpoint missing: %s", output) } t.Setenv("PATH", originalPath) // Execute the runbook's guarded recovery commands verbatim. recovery := `test "$(git ls-remote --get-url -- "$releasekit_endpoint")" = "$releasekit_endpoint" && git ls-remote --refs -- "$releasekit_endpoint" "refs/tags/$releasekit_version"` query := exec.Command("sh", "-c", recovery) query.Dir = repository query.Env = append(os.Environ(), "releasekit_endpoint="+remote, "releasekit_version=v1.2.3") found, err := query.Output() if err != nil || len(found) == 0 { t.Fatalf("captured endpoint recovery failed: %q %v", found, err) } if !strings.Contains(string(found), cliGitOutput(t, repository, "rev-parse", "refs/tags/v1.2.3")) { t.Fatalf("wrong recovered tag: %s", found) } cliGitRun(t, repository, "config", "url."+fetch+".insteadOf", remote) blocked := exec.Command("sh", "-c", recovery) blocked.Dir = repository blocked.Env = query.Env if output, err := blocked.Output(); err == nil || len(output) != 0 { t.Fatalf("rewritten endpoint should block query: %q %v", output, err) } fetchQuery := exec.Command(realGit, "ls-remote", "--refs", "origin", "refs/tags/v1.2.3") fetchQuery.Dir = repository stale, err := fetchQuery.Output() if err != nil || len(stale) != 0 { t.Fatalf("fetch fixture unexpectedly found tag: %q %v", stale, err) } } }) } } // Capture current, nonignored source once, including worktree changes. The // candidate gets one small test instead of the project's full test suite. func releaseWrapperSources(t *testing.T, root string) map[string][]byte { t.Helper() list := exec.Command("git", "ls-files", "-z", "--cached", "--others", "--exclude-standard") list.Dir = root paths, err := list.Output() if err != nil { t.Fatal(err) } sources := make(map[string][]byte) for _, path := range strings.Split(string(paths), "\x00") { if path == "" || strings.HasSuffix(path, "_test.go") { continue } contents, err := os.ReadFile(filepath.Join(root, path)) if os.IsNotExist(err) { continue // Tracked files removed from the current worktree. } if err != nil { t.Fatal(err) } sources[path] = contents } return sources }