Verify preparation selects the intended module graph
This commit is contained in:
@@ -209,7 +209,7 @@ composition with ordinary build identity and ambient flags.
|
||||
## Stage 4: Override alternate module-file selection during downloads
|
||||
|
||||
**Status:** complete. `go test ./internal/gotool` passes offline. Actual module
|
||||
graph selection evidence remains deferred to Stage 5.
|
||||
graph selection evidence passed in Stage 5.
|
||||
|
||||
**Files:** `internal/gotool/gotool.go`, `internal/gotool/gotool_test.go`,
|
||||
`preparation.go` GoDoc, and `docs/internal/gotool.md`.
|
||||
@@ -234,7 +234,10 @@ graph selection evidence remains deferred to Stage 5.
|
||||
|
||||
## Stage 5: Verify selected-module preparation and build composition
|
||||
|
||||
**Status:** pending. **Depends on:** Stages 3 and 4.
|
||||
**Status:** complete. Local-proxy graph, negative-control, inherited and
|
||||
persistent settings, standalone discovery, and public build identity checks
|
||||
pass offline, as do the focused binary CI and credential-order checks.
|
||||
**Depends on:** Stages 3 and 4.
|
||||
|
||||
**Files:** `preparation_test.go` and small test-local helpers beside it. The
|
||||
existing local module-proxy construction in `cmd/releasekit/woodpecker_test.go`
|
||||
|
||||
164
preparation_graph_test.go
Normal file
164
preparation_graph_test.go
Normal file
@@ -0,0 +1,164 @@
|
||||
package releasekit_test
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
releasekit "go.maximumdirect.net/releasekit"
|
||||
)
|
||||
|
||||
func TestPrepareBuildDependenciesSelectedModuleGraphs(t *testing.T) {
|
||||
repo := t.TempDir()
|
||||
nested := filepath.Join(repo, "nested")
|
||||
child := filepath.Join(nested, "child")
|
||||
if err := os.MkdirAll(child, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
write := func(path, value string) {
|
||||
t.Helper()
|
||||
if err := os.WriteFile(path, []byte(value), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
write(filepath.Join(repo, "go.mod"), "module example.invalid/root\n\ngo 1.25.0\n\nrequire example.invalid/rootdep v1.0.0\n")
|
||||
write(filepath.Join(nested, "go.mod"), "module example.invalid/nested\n\ngo 1.25.0\n\nrequire example.invalid/nesteddep v1.0.0\n")
|
||||
alt := filepath.Join(repo, "alternate.mod")
|
||||
write(alt, "module example.invalid/alternate\n\ngo 1.25.0\n")
|
||||
buildGit(t, repo, "init", "-b", "main")
|
||||
buildGit(t, repo, "config", "user.name", "Test")
|
||||
buildGit(t, repo, "config", "user.email", "test@example.invalid")
|
||||
buildGit(t, repo, "add", ".")
|
||||
buildGit(t, repo, "commit", "-m", "fixture")
|
||||
proxy := t.TempDir()
|
||||
for _, dep := range []string{"rootdep", "nesteddep"} {
|
||||
writePreparationProxy(t, proxy, "example.invalid/"+dep)
|
||||
}
|
||||
t.Setenv("GOPROXY", "file://"+filepath.ToSlash(proxy))
|
||||
t.Setenv("GOSUMDB", "off")
|
||||
t.Setenv("GOPRIVATE", "")
|
||||
t.Setenv("GONOPROXY", "none")
|
||||
t.Setenv("GONOSUMDB", "*")
|
||||
t.Setenv("GOTOOLCHAIN", "local")
|
||||
t.Setenv("GOWORK", "off")
|
||||
realGo, err := exec.LookPath("go")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name, module, dep, modulePath string
|
||||
persistent bool
|
||||
}{
|
||||
{"inherited root", "", "rootdep", "example.invalid/root", false},
|
||||
{"persistent nested", "nested", "nesteddep", "example.invalid/nested", true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cache := filepath.Join(t.TempDir(), "cache")
|
||||
t.Setenv("GOMODCACHE", cache)
|
||||
envFile := filepath.Join(t.TempDir(), "goenv")
|
||||
flags := "-modfile=" + alt + " -modcacherw"
|
||||
if tc.persistent {
|
||||
write(envFile, "GOFLAGS="+flags+"\n")
|
||||
t.Setenv("GOENV", envFile)
|
||||
t.Setenv("GOFLAGS", "")
|
||||
if err := os.Unsetenv("GOFLAGS"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
t.Setenv("GOENV", "off")
|
||||
t.Setenv("GOFLAGS", flags)
|
||||
}
|
||||
// Without the explicit override, the alternate file has no dependencies.
|
||||
negativeCache := filepath.Join(t.TempDir(), "negative")
|
||||
command := exec.Command(realGo, "mod", "download")
|
||||
command.Dir = repo
|
||||
command.Env = append(os.Environ(), "GOMODCACHE="+negativeCache)
|
||||
if out, err := command.CombinedOutput(); err != nil {
|
||||
t.Fatalf("negative control: %v: %s", err, out)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(negativeCache, "cache", "download", "example.invalid", tc.dep)); !os.IsNotExist(err) {
|
||||
t.Fatalf("alternate graph unexpectedly downloaded %s: %v", tc.dep, err)
|
||||
}
|
||||
if err := releasekit.PrepareBuildDependencies(context.Background(), releasekit.DependencyPreparationRequest{RepositoryDir: child, ModuleDir: tc.module}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(cache, "cache", "download", "example.invalid", tc.dep, "@v", "v1.0.0.zip")); err != nil {
|
||||
t.Fatalf("selected dependency missing: %v", err)
|
||||
}
|
||||
other := "rootdep"
|
||||
if tc.dep == other {
|
||||
other = "nesteddep"
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(cache, "cache", "download", "example.invalid", other)); !os.IsNotExist(err) {
|
||||
t.Fatalf("unexpected other graph: %v", err)
|
||||
}
|
||||
info, err := os.Stat(filepath.Join(cache, "example.invalid", tc.dep+"@v1.0.0"))
|
||||
if err != nil || info.Mode().Perm()&0200 == 0 {
|
||||
t.Fatalf("-modcacherw did not take effect: %v, %v", info, err)
|
||||
}
|
||||
result, err := releasekit.Build(context.Background(), releasekit.BuildRequest{RepositoryDir: child, ModuleDir: tc.module, Version: "v1.2.3"})
|
||||
expected := repo
|
||||
if tc.module != "" {
|
||||
expected = nested
|
||||
}
|
||||
if err != nil || result.RepositoryDir != repo || result.ModuleDir != expected || result.ModulePath != tc.modulePath {
|
||||
t.Fatalf("build identity = %#v, error = %v", result, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
// Standalone package subdirectories retain Go's upward module discovery.
|
||||
standalone := t.TempDir()
|
||||
pkg := filepath.Join(standalone, "pkg")
|
||||
if err := os.Mkdir(pkg, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
write(filepath.Join(standalone, "go.mod"), "module example.invalid/standalone\n\ngo 1.25.0\n\nrequire example.invalid/rootdep v1.0.0\n")
|
||||
t.Setenv("GOENV", "off")
|
||||
t.Setenv("GOFLAGS", "-modfile="+alt+" -modcacherw")
|
||||
cache := filepath.Join(t.TempDir(), "cache")
|
||||
t.Setenv("GOMODCACHE", cache)
|
||||
if err := releasekit.PrepareBuildDependencies(context.Background(), releasekit.DependencyPreparationRequest{RepositoryDir: pkg}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(cache, "cache", "download", "example.invalid", "rootdep", "@v", "v1.0.0.zip")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func writePreparationProxy(t *testing.T, proxy, module string) {
|
||||
t.Helper()
|
||||
dir := filepath.Join(proxy, filepath.FromSlash(module), "@v")
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mod := "module " + module + "\n\ngo 1.25.0\n"
|
||||
for name, contents := range map[string]string{"v1.0.0.mod": mod, "v1.0.0.info": `{"Version":"v1.0.0","Time":"2026-01-01T00:00:00Z"}`, "list": "v1.0.0\n"} {
|
||||
if err := os.WriteFile(filepath.Join(dir, name), []byte(contents), 0644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
file, err := os.Create(filepath.Join(dir, "v1.0.0.zip"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
z := zip.NewWriter(file)
|
||||
for name, contents := range map[string]string{"go.mod": mod, "dep.go": "package dep\n"} {
|
||||
w, err := z.Create(fmt.Sprintf("%s@v1.0.0/%s", module, name))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := w.Write([]byte(contents)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := z.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user