Use focused candidate fixtures for release wrapper tests

This commit is contained in:
2026-10-07 00:48:00 +00:00
parent e6def4ed7b
commit dab4fdf2b3
2 changed files with 97 additions and 46 deletions

View File

@@ -12,9 +12,6 @@ import (
)
func TestReleaseWrapperPreservesNativeExitStatuses(t *testing.T) {
if os.Getenv("RELEASEKIT_SKIP_WRAPPER_TEST") == "1" {
t.Skip("release wrapper is already running candidate validation")
}
if runtime.GOOS == "windows" {
t.Skip("POSIX release entry point")
}
@@ -25,56 +22,53 @@ func TestReleaseWrapperPreservesNativeExitStatuses(t *testing.T) {
if err != nil {
t.Fatal(err)
}
controller, err := os.ReadFile(filepath.Join(root, "cmd/releasekit/main.go"))
if err != nil {
t.Fatal(err)
}
updatedSources := map[string][]byte{}
for _, path := range []string{
"check.go", "publish.go", "delivery.go", "internal/candidate/check.go", "internal/delivery/delivery.go", "internal/publish/publish.go",
"build_test.go", "publish_test.go", "internal/candidate/check_test.go",
"internal/gitea/upload.go", "internal/gitea/upload_test.go",
"delivery_test.go", "cmd/releasekit/main_test.go", "cmd/releasekit/release_wrapper_test.go",
"cmd/releasekit/internal/cli/config_test.go", "examples/releasekit.json",
"cmd/releasekit/internal/cli/config.go",
} {
contents, err := os.ReadFile(filepath.Join(root, path))
if err != nil {
t.Fatal(err)
}
updatedSources[path] = contents
}
wrapper, err := os.ReadFile(filepath.Join(root, "scripts/release"))
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) {
t.Setenv("RELEASEKIT_SKIP_WRAPPER_TEST", "1")
repository := filepath.Join(t.TempDir(), "source")
clone := exec.Command(realGit, "clone", "--local", "--no-hardlinks", root, repository)
if output, err := clone.CombinedOutput(); err != nil {
t.Fatalf("clone: %v %s", err, output)
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")
if err := os.WriteFile(filepath.Join(repository, "scripts/release"), wrapper, 0755); err != nil {
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)
}
// Exercise the current controller, including changes awaiting this commit.
if err := os.WriteFile(filepath.Join(repository, "cmd/releasekit/main.go"), controller, 0600); err != nil {
t.Fatal(err)
}
for path, contents := range updatedSources {
if err := os.WriteFile(filepath.Join(repository, filepath.FromSlash(path)), contents, 0600); err != nil {
t.Fatalf("write current source %s: %v", path, err)
}
}
if err := os.MkdirAll(filepath.Join(repository, "docs/releases"), 0700); err != nil {
t.Fatal(err)
}
@@ -99,7 +93,7 @@ func TestReleaseWrapperPreservesNativeExitStatuses(t *testing.T) {
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", "set-url", "origin", 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)
@@ -138,7 +132,6 @@ exec "$RELEASEKIT_TEST_REAL_GIT" "$@"
t.Setenv("RELEASEKIT_TEST_PUSH_MARKER", marker)
t.Setenv("RELEASEKIT_TEST_MOVED_ENDPOINT", fetch)
t.Setenv("RELEASEKIT_TEST_ARM_GIT_SHIM", armMarker)
t.Setenv("RELEASEKIT_SKIP_WRAPPER_TEST", "1")
}
command := exec.Command("./scripts/release", mode.version)
command.Dir = repository
@@ -154,6 +147,14 @@ exec "$RELEASEKIT_TEST_REAL_GIT" "$@"
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)
@@ -190,3 +191,30 @@ exec "$RELEASEKIT_TEST_REAL_GIT" "$@"
})
}
}
// 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
}