Isolate synthetic module downloads in workflow tests

This commit is contained in:
2026-10-08 14:30:08 +00:00
parent ae0e4a0263
commit f2124b3b7b
2 changed files with 49 additions and 19 deletions

View File

@@ -150,6 +150,19 @@ func TestWoodpeckerTagDeliveryTemplate(t *testing.T) {
} }
func TestWoodpeckerTagDeliveryUsesTaggedModuleVersion(t *testing.T) { func TestWoodpeckerTagDeliveryUsesTaggedModuleVersion(t *testing.T) {
// Synthetic downloads must leave the caller's module cache untouched.
callerCache := t.TempDir()
t.Setenv("GOMODCACHE", callerCache)
t.Cleanup(func() {
entries, err := os.ReadDir(callerCache)
if err != nil {
t.Fatal(err)
}
if len(entries) != 0 {
t.Errorf("synthetic module downloads modified the caller's cache: %v", entries)
}
})
root := cliRepositoryRoot(t) root := cliRepositoryRoot(t)
contents, err := os.ReadFile(filepath.Join(root, "examples", "woodpecker", "tag-delivery.yml")) contents, err := os.ReadFile(filepath.Join(root, "examples", "woodpecker", "tag-delivery.yml"))
if err != nil { if err != nil {
@@ -185,7 +198,7 @@ func TestWoodpeckerTagDeliveryUsesTaggedModuleVersion(t *testing.T) {
moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod")) moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod"))
commit := oldCommit commit := oldCommit
env := templateEnvironment(repository, commit, goDir, goLog) env := templateEnvironment(repository, commit, goDir, goLog)
env["GOPROXY"] = "file://" + filepath.ToSlash(proxy) useSyntheticModuleProxy(t, env, "file://"+filepath.ToSlash(proxy))
sumLog := filepath.Join(t.TempDir(), "tool-only-sums") sumLog := filepath.Join(t.TempDir(), "tool-only-sums")
env["RELEASEKIT_SUM_LOG"] = sumLog env["RELEASEKIT_SUM_LOG"] = sumLog
output := runWoodpeckerBlock(t, command, repository, env) output := runWoodpeckerBlock(t, command, repository, env)
@@ -207,7 +220,7 @@ func TestWoodpeckerTagDeliveryUsesTaggedModuleVersion(t *testing.T) {
commit := pushTaggedCLIRepository(t, repository) commit := pushTaggedCLIRepository(t, repository)
moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod")) moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod"))
env := templateEnvironment(repository, commit, goDir, goLog) env := templateEnvironment(repository, commit, goDir, goLog)
env["GOPROXY"] = "file://" + filepath.ToSlash(proxy) useSyntheticModuleProxy(t, env, "file://"+filepath.ToSlash(proxy))
sumLog := filepath.Join(t.TempDir(), "tool-only-sums") sumLog := filepath.Join(t.TempDir(), "tool-only-sums")
env["RELEASEKIT_SUM_LOG"] = sumLog env["RELEASEKIT_SUM_LOG"] = sumLog
output := runWoodpeckerBlock(t, command, repository, env) output := runWoodpeckerBlock(t, command, repository, env)
@@ -245,21 +258,15 @@ func TestWoodpeckerSourceDeliveryTemplateSelfBootstraps(t *testing.T) {
defer proxy.Close() defer proxy.Close()
writeRepositoryFile(t, repository, "go.mod", "module example.com/cli-candidate\n\ngo 1.25.0\n\nrequire "+dependency+" v1.0.0\n") writeRepositoryFile(t, repository, "go.mod", "module example.com/cli-candidate\n\ngo 1.25.0\n\nrequire "+dependency+" v1.0.0\n")
writeRepositoryFile(t, repository, "cmd/releasekit/main.go", "package main\n\nimport (\"fmt\"; \"os\"; helper \""+dependency+"\")\n\nfunc main() {\n\tfmt.Println(\"self-bootstrap \" + helper.Message)\n\tfmt.Printf(\"tool-args=%q\\n\", os.Args[1:])\n}\n") writeRepositoryFile(t, repository, "cmd/releasekit/main.go", "package main\n\nimport (\"fmt\"; \"os\"; helper \""+dependency+"\")\n\nfunc main() {\n\tfmt.Println(\"self-bootstrap \" + helper.Message)\n\tfmt.Printf(\"tool-args=%q\\n\", os.Args[1:])\n}\n")
preparationEnv := map[string]string{ preparationEnv := map[string]string{}
"GOPROXY": proxy.URL, "GOSUMDB": "off", "GONOPROXY": "none", "GOPRIVATE": "", useSyntheticModuleProxy(t, preparationEnv, proxy.URL)
"GOWORK": "off", "GOTOOLCHAIN": "local", "GOENV": "off", "GOFLAGS": "-modcacherw",
"GOMODCACHE": t.TempDir(),
}
runWoodpeckerBlock(t, "go mod download "+dependency, repository, preparationEnv) runWoodpeckerBlock(t, "go mod download "+dependency, repository, preparationEnv)
cliGitRun(t, repository, "add", "cmd/releasekit/main.go", "go.mod", "go.sum") cliGitRun(t, repository, "add", "cmd/releasekit/main.go", "go.mod", "go.sum")
cliGitRun(t, repository, "commit", "-m", "add the self-bootstrapped command") cliGitRun(t, repository, "commit", "-m", "add the self-bootstrapped command")
commit := pushTaggedCLIRepository(t, repository) commit := pushTaggedCLIRepository(t, repository)
goDir, goLog := installGoBootstrapShim(t) goDir, goLog := installGoBootstrapShim(t)
env := templateEnvironment(repository, commit, goDir, goLog) env := templateEnvironment(repository, commit, goDir, goLog)
for key, value := range preparationEnv { useSyntheticModuleProxy(t, env, proxy.URL)
env[key] = value
}
env["GOMODCACHE"] = t.TempDir()
proxyRequests.Store(0) proxyRequests.Store(0)
env["CI_COMMIT_SHA"] = strings.Repeat("0", len(commit)) env["CI_COMMIT_SHA"] = strings.Repeat("0", len(commit))
if output, err := executeWoodpeckerBlock(command, repository, env); err == nil { if output, err := executeWoodpeckerBlock(command, repository, env); err == nil {
@@ -426,6 +433,19 @@ esac
return shimDir, goLog return shimDir, goLog
} }
func useSyntheticModuleProxy(t *testing.T, environment map[string]string, proxyURL string) {
t.Helper()
environment["GOMODCACHE"] = t.TempDir()
environment["GOPROXY"] = proxyURL
environment["GOSUMDB"] = "off"
environment["GONOPROXY"] = "none"
environment["GOPRIVATE"] = ""
environment["GOWORK"] = "off"
environment["GOTOOLCHAIN"] = "local"
environment["GOENV"] = "off"
environment["GOFLAGS"] = "-modcacherw"
}
func templateEnvironment(repository, commit, shimDir, goLog string) map[string]string { func templateEnvironment(repository, commit, shimDir, goLog string) map[string]string {
realGo, _ := exec.LookPath("go") realGo, _ := exec.LookPath("go")
return map[string]string{ return map[string]string{

View File

@@ -1,7 +1,7 @@
# PromptKit Bug Report: v0.2.0 Module Checksum Mismatch # PromptKit Bug Report: v0.2.0 Module Checksum Mismatch
**Status:** Stale local module metadata confirmed; cache recovery and test **Status:** Stale local module metadata confirmed; test isolation fixed.
isolation follow-up remain pending. Shared-cache recovery and PromptKit's adoption retry remain pending.
## Summary ## Summary
@@ -95,22 +95,32 @@ Recovery was verified in a copied cache; PromptKit's adoption retry remains pend
### Related Test-Isolation Defect ### Related Test-Isolation Defect
The workflow tests' [environment helper](../../cmd/releasekit/woodpecker_test.go) The workflow tests' [environment helper](../../cmd/releasekit/woodpecker_test.go)
disables checksum verification for synthetic fixtures and inherits the caller's disabled checksum verification for synthetic fixtures and inherited the caller's
module cache. The version-selection test serves synthetic ReleaseKit versions module cache. The version-selection test serves synthetic ReleaseKit versions
from a local module proxy. from a local module proxy.
Running `TestWoodpeckerTagDeliveryUsesTaggedModuleVersion` with a disposable Running `TestWoodpeckerTagDeliveryUsesTaggedModuleVersion` with a disposable
inherited `GOMODCACHE` passed and left synthetic `v0.2.197` and `v0.2.198` inherited `GOMODCACHE` passed and left synthetic `v0.2.197` and `v0.2.198`
metadata in that cache. This confirms that the tests can contaminate a metadata in that cache. This confirmed that the tests could contaminate a
developer's module cache. It establishes a related defect; attribution of the developer's module cache. It established a related defect; attribution of the
stale `v0.2.0` entry to a particular test or command remains unconfirmed. stale `v0.2.0` entry to a particular test or command remains unconfirmed.
### Test-Isolation Resolution
Both synthetic-proxy test families now use a shared environment helper that
assigns a fresh test-owned `GOMODCACHE` and scopes proxy/checksum settings to
their Go subprocesses. Source-bootstrap preparation and execution retain
separate caches. Tests that build real ReleaseKit source continue to use their
prepared dependencies.
The existing tagged-version test now supplies a disposable caller cache and
asserts that it remains empty after fixture execution. This regression failed
before the fix and passed afterward. All Woodpecker workflow tests passed.
## Recommended Follow-Up ## Recommended Follow-Up
1. Quarantine the affected local `v0.2.0.mod` entry and retry PromptKit's adoption 1. Quarantine the affected local `v0.2.0.mod` entry and retry PromptKit's adoption
with normal checksum verification. Retain the quarantined bytes as evidence. with normal checksum verification. Retain the quarantined bytes as evidence.
2. Give synthetic module-proxy tests a temporary `GOMODCACHE` owned by the test 2. Record the actual cache recovery and consumer retry outcomes here. Preserve
fixture, with focused regression coverage for cache isolation.
3. Record the actual cache recovery and consumer retry outcomes here. Preserve
the published v0.2.0 tag and checksums; this incident requires no replacement the published v0.2.0 tag and checksums; this incident requires no replacement
release. release.