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