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
}

View File

@@ -2,7 +2,7 @@
Date: 2026-10-06.
Reviewed source: `55333b0`.
Status: R1 resolved; R2 requires remediation.
Status: Complete. R1 and R2 are resolved.
## 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
Status: Open.
Status: Resolved.
Category: Test maintainability and development cost.
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
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
The following checks passed on the reviewed source with Go 1.26.8 on
Linux/amd64:
The following checks passed on the reviewed source and after remediation with
Go 1.26.8 on Linux/amd64:
```sh
GOWORK=off GOPROXY=off GONOPROXY=none GOSUMDB=off GOTOOLCHAIN=local \