Use focused candidate fixtures for release wrapper tests
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user