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) { 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" { if runtime.GOOS == "windows" {
t.Skip("POSIX release entry point") t.Skip("POSIX release entry point")
} }
@@ -25,56 +22,53 @@ func TestReleaseWrapperPreservesNativeExitStatuses(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
controller, err := os.ReadFile(filepath.Join(root, "cmd/releasekit/main.go")) sources := releaseWrapperSources(t, root)
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)
}
for _, mode := range []struct { for _, mode := range []struct {
name string name string
status int status int
version string version string
}{{"success", 0, "v1.2.3"}, {"invalid", 2, "invalid"}, {"rejected", 1, "v1.2.3"}, {"unknown", 3, "v1.2.3"}} { }{{"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.Run(mode.name, func(t *testing.T) {
t.Setenv("RELEASEKIT_SKIP_WRAPPER_TEST", "1")
repository := filepath.Join(t.TempDir(), "source") repository := filepath.Join(t.TempDir(), "source")
clone := exec.Command(realGit, "clone", "--local", "--no-hardlinks", root, repository) for path, contents := range sources {
if output, err := clone.CombinedOutput(); err != nil { name := filepath.Join(repository, path)
t.Fatalf("clone: %v %s", err, output) 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.name", "ReleaseKit Test")
cliGitRun(t, repository, "config", "user.email", "fixture@example.test") cliGitRun(t, repository, "config", "user.email", "fixture@example.test")
cliGitRun(t, repository, "config", "commit.gpgsign", "false") 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) 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 { if err := os.MkdirAll(filepath.Join(repository, "docs/releases"), 0700); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -99,7 +93,7 @@ func TestReleaseWrapperPreservesNativeExitStatuses(t *testing.T) {
cliGitRun(t, repository, "commit", "-m", "prepare wrapper fixture") cliGitRun(t, repository, "commit", "-m", "prepare wrapper fixture")
remote := filepath.Join(t.TempDir(), "origin.git") remote := filepath.Join(t.TempDir(), "origin.git")
cliGitRun(t, repository, "init", "--bare", remote) 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") cliGitRun(t, repository, "push", "origin", "HEAD:refs/heads/main")
fetch := filepath.Join(t.TempDir(), "fetch.git") fetch := filepath.Join(t.TempDir(), "fetch.git")
cliGitRun(t, repository, "init", "--bare", fetch) 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_PUSH_MARKER", marker)
t.Setenv("RELEASEKIT_TEST_MOVED_ENDPOINT", fetch) t.Setenv("RELEASEKIT_TEST_MOVED_ENDPOINT", fetch)
t.Setenv("RELEASEKIT_TEST_ARM_GIT_SHIM", armMarker) t.Setenv("RELEASEKIT_TEST_ARM_GIT_SHIM", armMarker)
t.Setenv("RELEASEKIT_SKIP_WRAPPER_TEST", "1")
} }
command := exec.Command("./scripts/release", mode.version) command := exec.Command("./scripts/release", mode.version)
command.Dir = repository command.Dir = repository
@@ -154,6 +147,14 @@ exec "$RELEASEKIT_TEST_REAL_GIT" "$@"
if status != mode.status { if status != mode.status {
t.Fatalf("status=%d want=%d output=%s", status, mode.status, output) 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 mode.name == "unknown" {
if !strings.Contains(string(output), fmt.Sprintf("endpoint=%q", remote)) { if !strings.Contains(string(output), fmt.Sprintf("endpoint=%q", remote)) {
t.Fatalf("captured endpoint missing: %s", output) 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
}

View File

@@ -2,7 +2,7 @@
Date: 2026-10-06. Date: 2026-10-06.
Reviewed source: `55333b0`. Reviewed source: `55333b0`.
Status: R1 resolved; R2 requires remediation. Status: Complete. R1 and R2 are resolved.
## Scope and assessment ## Scope and assessment
@@ -76,7 +76,7 @@ fix. Go-environment, candidate-validation, and build regression checks pass.
## R2 — Medium: Wrapper regression testing repeats the project suite inside itself ## R2 — Medium: Wrapper regression testing repeats the project suite inside itself
Status: Open. Status: Resolved.
Category: Test maintainability and development cost. Category: Test maintainability and development cost.
Introduced by: `3e9b5c1`. Introduced by: `3e9b5c1`.
@@ -114,10 +114,33 @@ intended object with separate fetch and push repositories. The outer default
suite supplies the project's broader behavioral evidence once. Use test scope suite supplies the project's broader behavioral evidence once. Use test scope
and command observations for verification rather than a wall-clock gate. and command observations for verification rather than a wall-clock gate.
**Resolution (2026-10-07):** Wrapper fixtures capture the current nonignored
source, including tracked and untracked worktree changes, through one inventory.
They exclude the project's test files and add one small candidate test with an
external validation log. The manually maintained overlay and recursion skip
flag are removed. The real source-bootstrap wrapper preserves statuses 0, 1,
2, and 3; the validation log confirms one suite execution for each valid
candidate and none for invalid input. Captured-endpoint recovery and URL-rewrite
guards still pass with separate fetch and push repositories.
The focused wrapper regression passes in 7.964 seconds on this host. The final
uncached offline suite passes, with the CLI package taking 22.842 seconds.
These measurements record the observed reduction in duplicated work and add
no duration requirement.
## Completion verification
Both fixes are implemented with focused regression evidence. The complete
offline suite and compiled API/dependency checker pass with Go 1.26.8 on
Linux/amd64 using the commands below. Documentation link and whitespace checks
pass. Each remediation unit is committed separately with a plain English
message. Production changes are confined to Go configuration discovery; the
wrapper change preserves the existing publication and recovery contracts.
## Validation and remaining scope ## Validation and remaining scope
The following checks passed on the reviewed source with Go 1.26.8 on The following checks passed on the reviewed source and after remediation with
Linux/amd64: Go 1.26.8 on Linux/amd64:
```sh ```sh
GOWORK=off GOPROXY=off GONOPROXY=none GOSUMDB=off GOTOOLCHAIN=local \ GOWORK=off GOPROXY=off GONOPROXY=none GOSUMDB=off GOTOOLCHAIN=local \