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) } }