From f2124b3b7b54e6ea8640963c37398268c08f5111 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Thu, 8 Oct 2026 14:30:08 +0000 Subject: [PATCH] Isolate synthetic module downloads in workflow tests --- cmd/releasekit/woodpecker_test.go | 42 ++++++++++++++++++++-------- docs/roadmap/promptkit-bug-report.md | 26 +++++++++++------ 2 files changed, 49 insertions(+), 19 deletions(-) diff --git a/cmd/releasekit/woodpecker_test.go b/cmd/releasekit/woodpecker_test.go index f0ec512..2fea8a7 100644 --- a/cmd/releasekit/woodpecker_test.go +++ b/cmd/releasekit/woodpecker_test.go @@ -150,6 +150,19 @@ func TestWoodpeckerTagDeliveryTemplate(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) contents, err := os.ReadFile(filepath.Join(root, "examples", "woodpecker", "tag-delivery.yml")) if err != nil { @@ -185,7 +198,7 @@ func TestWoodpeckerTagDeliveryUsesTaggedModuleVersion(t *testing.T) { moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod")) commit := oldCommit 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") env["RELEASEKIT_SUM_LOG"] = sumLog output := runWoodpeckerBlock(t, command, repository, env) @@ -207,7 +220,7 @@ func TestWoodpeckerTagDeliveryUsesTaggedModuleVersion(t *testing.T) { commit := pushTaggedCLIRepository(t, repository) moduleBefore := readWoodpeckerFixtureFile(t, filepath.Join(repository, "go.mod")) 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") env["RELEASEKIT_SUM_LOG"] = sumLog output := runWoodpeckerBlock(t, command, repository, env) @@ -245,21 +258,15 @@ func TestWoodpeckerSourceDeliveryTemplateSelfBootstraps(t *testing.T) { 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, "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{ - "GOPROXY": proxy.URL, "GOSUMDB": "off", "GONOPROXY": "none", "GOPRIVATE": "", - "GOWORK": "off", "GOTOOLCHAIN": "local", "GOENV": "off", "GOFLAGS": "-modcacherw", - "GOMODCACHE": t.TempDir(), - } + preparationEnv := map[string]string{} + useSyntheticModuleProxy(t, preparationEnv, proxy.URL) runWoodpeckerBlock(t, "go mod download "+dependency, repository, preparationEnv) cliGitRun(t, repository, "add", "cmd/releasekit/main.go", "go.mod", "go.sum") cliGitRun(t, repository, "commit", "-m", "add the self-bootstrapped command") commit := pushTaggedCLIRepository(t, repository) goDir, goLog := installGoBootstrapShim(t) env := templateEnvironment(repository, commit, goDir, goLog) - for key, value := range preparationEnv { - env[key] = value - } - env["GOMODCACHE"] = t.TempDir() + useSyntheticModuleProxy(t, env, proxy.URL) proxyRequests.Store(0) env["CI_COMMIT_SHA"] = strings.Repeat("0", len(commit)) if output, err := executeWoodpeckerBlock(command, repository, env); err == nil { @@ -426,6 +433,19 @@ esac 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 { realGo, _ := exec.LookPath("go") return map[string]string{ diff --git a/docs/roadmap/promptkit-bug-report.md b/docs/roadmap/promptkit-bug-report.md index 939d063..4034cdd 100644 --- a/docs/roadmap/promptkit-bug-report.md +++ b/docs/roadmap/promptkit-bug-report.md @@ -1,7 +1,7 @@ # PromptKit Bug Report: v0.2.0 Module Checksum Mismatch -**Status:** Stale local module metadata confirmed; cache recovery and test -isolation follow-up remain pending. +**Status:** Stale local module metadata confirmed; test isolation fixed. +Shared-cache recovery and PromptKit's adoption retry remain pending. ## Summary @@ -95,22 +95,32 @@ Recovery was verified in a copied cache; PromptKit's adoption retry remains pend ### Related Test-Isolation Defect 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 from a local module proxy. Running `TestWoodpeckerTagDeliveryUsesTaggedModuleVersion` with a disposable 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 -developer's module cache. It establishes a related defect; attribution of the +metadata in that cache. This confirmed that the tests could contaminate a +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. +### 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 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. -2. Give synthetic module-proxy tests a temporary `GOMODCACHE` owned by the test - fixture, with focused regression coverage for cache isolation. -3. Record the actual cache recovery and consumer retry outcomes here. Preserve +2. Record the actual cache recovery and consumer retry outcomes here. Preserve the published v0.2.0 tag and checksums; this incident requires no replacement release.