The Go fetch takes zero-version requirements from the module graph and proves them with a shut-off load; mesh-build-image and -node name nodebuildgo (issue 1966) #654
@@ -13,6 +13,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -179,6 +180,11 @@ func (m *Module) fetchInto(ctx context.Context, req sdkstep.FetchRequest, id str
|
||||
return
|
||||
}
|
||||
var more []TakenVersion
|
||||
// Then what only a module the directory replaces names, which the download would not miss (pins.go).
|
||||
more, errs[i] = m.takeReplacedZeroPins(ctx, root, repository, filepath.Join(tree, d), d, req.Packages, taken[i], &pins)
|
||||
if taken[i] = append(taken[i], more...); errs[i] != nil {
|
||||
return
|
||||
}
|
||||
more, errs[i] = m.downloadModules(ctx, root, repository, filepath.Join(tree, d), d, &pins, req.Packages)
|
||||
taken[i] = append(taken[i], more...)
|
||||
}()
|
||||
@@ -207,7 +213,9 @@ func (m *Module) downloadModules(ctx context.Context, root, repository, dir, d s
|
||||
for {
|
||||
failure := m.downloadAndVerify(ctx, dir, d)
|
||||
if failure == nil {
|
||||
return taken, nil
|
||||
// The proof that what was downloaded is all the shut-off steps load: else the fetch fails here, naming the
|
||||
// package, and not a vet, a test or a build later with less to say (issue 1966).
|
||||
return taken, m.shutOffLoad(ctx, m.goEnvFor(dir), dir, d)
|
||||
}
|
||||
// A requirement at the zero version names no commit: it is taken from the asked versions or the trunk's latest.
|
||||
took, err := m.takeZeroPins(ctx, root, repository, dir, d, failure.Error(), asked, pins)
|
||||
@@ -227,6 +235,27 @@ func (m *Module) downloadModules(ctx context.Context, root, repository, dir, d s
|
||||
}
|
||||
}
|
||||
|
||||
// shutOffLoadFormat is what go list -e says of each package it loads: nothing, or the package and Go's words of its error.
|
||||
const shutOffLoadFormat = `{{with .Error}}{{$.ImportPath}}: {{.Err}}{{"\n"}}{{end}}`
|
||||
|
||||
// shutOffLoad loads the packages of dir, with their tests and every package they import, as a shut-off step does
|
||||
// (GOPROXY=off, the step's own environment env), and fails naming each package whose load would reach the network, in
|
||||
// Go's words. A package that fails to load for another cause (a compile error, an import of a package of the module
|
||||
// that does not exist) is the shut-off steps' to say, with their own verdict lines, and is not the fetch's.
|
||||
func (m *Module) shutOffLoad(ctx context.Context, env []string, dir, d string) error {
|
||||
r := m.Run(ctx, append(env, "GOPROXY=off"), "go", "-C", dir, "list", "-e", "-deps", "-test", "-f", shutOffLoadFormat, "./...")
|
||||
var lost []string
|
||||
for _, l := range strings.Split(r.Stdout+"\n"+r.Stderr, "\n") {
|
||||
if l = strings.TrimSpace(l); strings.Contains(l, "GOPROXY=off") && !slices.Contains(lost, l) {
|
||||
lost = append(lost, l)
|
||||
}
|
||||
}
|
||||
if len(lost) > 0 {
|
||||
return fmt.Errorf("the packages of %s load shut off only with what the fetch did not download: %s", d, strings.Join(lost, "; "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyLocks hold one mutex per digest of a go.mod and go.sum, so two directories with the same pair verify once.
|
||||
var verifyLocks sync.Map
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -211,22 +212,54 @@ func TestNoCommandOfTheStepSeesAPasswordOrAToken(t *testing.T) {
|
||||
|
||||
// 1485/AC3: go mod verify failing ends the step failed naming the directory and the first line of its output.
|
||||
func TestAVerifyThatFailsEndsTheStepNamingTheDirectoryAndTheLine(t *testing.T) {
|
||||
f := &fakeStep{gomods: "go.mod\ntools/go.mod\n", goRule: func(line string) forgekey.Ran {
|
||||
if strings.HasSuffix(line, "tools mod verify") || strings.Contains(line, "/tools mod verify") {
|
||||
return forgekey.Ran{Status: 1, Stdout: "\nexample.org/x v1.0.0: dir has been modified\nsecond line\n"}
|
||||
}
|
||||
return forgekey.Ran{}
|
||||
}}
|
||||
m, states := fetchModule(t, f)
|
||||
if _, err := fetchTool(t, m)(fetchArgs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st := states.waitEnd(t)
|
||||
if st.State != sdkstep.StepFailed {
|
||||
t.Fatalf("the step ended %s, want failed", st.State)
|
||||
}
|
||||
if !strings.Contains(st.Reason, "tools") || !strings.Contains(st.Reason, "example.org/x v1.0.0: dir has been modified") || strings.Contains(st.Reason, "second line") {
|
||||
t.Errorf("reason = %q", st.Reason)
|
||||
const seatPkg = "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
|
||||
for _, c := range []struct {
|
||||
name, ran, out string
|
||||
want []string
|
||||
notWant string
|
||||
}{
|
||||
{name: "go mod verify fails", ran: "tools mod verify", out: "\nexample.org/x v1.0.0: dir has been modified\nsecond line\n",
|
||||
want: []string{"tools", "example.org/x v1.0.0: dir has been modified"}, notWant: "second line"},
|
||||
// 1966/AC178-2: what the fetch downloaded misses a package the directory's code imports: the shut-off package load
|
||||
// fails the fetch naming the package in Go's words, not a shut-off step later.
|
||||
{name: "the shut-off package load would reach the network", ran: "tools list -e -deps -test",
|
||||
out: "fixture/tools/cmd: \n" + seatPkg + ": module lookup disabled by GOPROXY=off\n",
|
||||
want: []string{"tools", "shut off", seatPkg + ": module lookup disabled by GOPROXY=off"}, notWant: "fixture/tools/cmd"},
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
f := &fakeStep{gomods: "go.mod\ntools/go.mod\n", goRule: func(line string) forgekey.Ran {
|
||||
if strings.Contains(line, "/"+c.ran) {
|
||||
if strings.Contains(line, " list ") {
|
||||
return forgekey.Ran{Stdout: c.out} // go list -e says a package's error and exits 0
|
||||
}
|
||||
return forgekey.Ran{Status: 1, Stdout: c.out}
|
||||
}
|
||||
return forgekey.Ran{}
|
||||
}}
|
||||
m, states := fetchModule(t, f)
|
||||
if _, err := fetchTool(t, m)(fetchArgs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
st := states.waitEnd(t)
|
||||
if st.State != sdkstep.StepFailed {
|
||||
t.Fatalf("the step ended %s, want failed", st.State)
|
||||
}
|
||||
for _, w := range c.want {
|
||||
if !strings.Contains(st.Reason, w) {
|
||||
t.Errorf("reason = %q, want it to hold %q", st.Reason, w)
|
||||
}
|
||||
}
|
||||
if strings.Contains(st.Reason, c.notWant) {
|
||||
t.Errorf("reason = %q holds %q", st.Reason, c.notWant)
|
||||
}
|
||||
if c.ran == "tools list -e -deps -test" {
|
||||
for _, call := range f.calls {
|
||||
if strings.Contains(call.line, " list -e -deps") && !slices.Contains(call.env, "GOPROXY=off") {
|
||||
t.Errorf("the package load ran with the network: %s", call.env)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -259,16 +259,117 @@ func absoluteReplaces(body []byte, dir string) []byte {
|
||||
return []byte(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
// takeZeroPins takes, for each requirement of dir's go.mod at the zero version that a go failure names, the version it is
|
||||
// built against (ADR 0573 §2, §4: whatever go.mod names): the commit the ask names for the package; else the head of
|
||||
// the package's repository among the asked versions (the check's or try's members: another package of the same
|
||||
// repository names it); else the latest published version, the trunk's head. It never looks for the commit
|
||||
// 000000000000 among the pull request heads. It answers what it took; none when no zero requirement is named.
|
||||
func (m *Module) takeZeroPins(ctx context.Context, root, repository, dir, d, failure string, asked sdkstep.PackageVersions, pins *sync.Mutex) ([]TakenVersion, error) {
|
||||
// zeroRequirement is a requirement at the zero version in the module graph of a directory: by is "" when the directory's
|
||||
// own go.mod names it, else the directory (relative to dir) of a module the directory replaces by a directory of the
|
||||
// tree, whose go.mod names it.
|
||||
type zeroRequirement struct {
|
||||
pin
|
||||
by string
|
||||
}
|
||||
|
||||
// zeroRequirementsOf are the requirements at the zero version of dir's module graph as Go builds it (ADR 0573 §2:
|
||||
// whatever go.mod names): those of dir's go.mod, then those of each module dir's go.mod replaces by a directory
|
||||
// (./x, ../x), each module once, own first. A module dir replaces is left out, since its replace decides it; a replaced
|
||||
// module's own replaces are not followed, since Go reads replaces only of the directory it builds (issue 1966: buildstep,
|
||||
// replaced by ../mesh-build-go/buildstep, imports a seat package mesh-build-image's go.mod did not name).
|
||||
func zeroRequirementsOf(dir string) []zeroRequirement {
|
||||
raw, err := os.ReadFile(filepath.Join(dir, "go.mod"))
|
||||
if err != nil || !strings.Contains(failure, zeroShort) {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
_, replaced := goModRequirements(string(raw))
|
||||
seen := map[string]bool{}
|
||||
var out []zeroRequirement
|
||||
add := func(gomod, by string) {
|
||||
for _, p := range forgePins(gomod) {
|
||||
if p.short != zeroShort || seen[p.module] || (by != "" && replaced[p.module]) {
|
||||
continue
|
||||
}
|
||||
seen[p.module] = true
|
||||
out = append(out, zeroRequirement{pin: p, by: by})
|
||||
}
|
||||
}
|
||||
add(string(raw), "")
|
||||
local := localReplaces(string(raw))
|
||||
for _, module := range slices.Sorted(maps.Keys(local)) {
|
||||
by := local[module]
|
||||
b, err := os.ReadFile(filepath.Join(dir, filepath.FromSlash(by), "go.mod"))
|
||||
if err != nil {
|
||||
continue // a replace to a directory with no go.mod: Go says so in its own words
|
||||
}
|
||||
add(string(b), by)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// localReplaces are the replaces of a go.mod to a directory (., .., ./x, ../x), by module: the directory as written.
|
||||
func localReplaces(gomod string) map[string]string {
|
||||
out := map[string]string{}
|
||||
inBlock := false
|
||||
for _, line := range strings.Split(gomod, "\n") {
|
||||
if i := strings.Index(line, "//"); i >= 0 {
|
||||
line = line[:i]
|
||||
}
|
||||
f := strings.Fields(line)
|
||||
switch {
|
||||
case len(f) == 0:
|
||||
continue
|
||||
case inBlock && f[0] == ")":
|
||||
inBlock = false
|
||||
continue
|
||||
case !inBlock && f[0] == "replace" && len(f) == 2 && f[1] == "(":
|
||||
inBlock = true
|
||||
continue
|
||||
case !inBlock && f[0] != "replace":
|
||||
continue
|
||||
case !inBlock:
|
||||
f = f[1:]
|
||||
}
|
||||
at := slices.Index(f, "=>")
|
||||
if at < 1 || at+1 >= len(f) {
|
||||
continue
|
||||
}
|
||||
target := strings.Trim(f[at+1], `"`)
|
||||
if target == "." || target == ".." || strings.HasPrefix(target, "./") || strings.HasPrefix(target, "../") {
|
||||
out[strings.Trim(f[0], `"`)] = filepath.ToSlash(filepath.Clean(target))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// takeZeroPins takes, for each requirement at the zero version of dir's module graph (zeroRequirementsOf) when a go
|
||||
// failure names the zero version, the version it is built against (takeZeroRequirements). It answers what it took; none
|
||||
// when no zero requirement is named.
|
||||
func (m *Module) takeZeroPins(ctx context.Context, root, repository, dir, d, failure string, asked sdkstep.PackageVersions, pins *sync.Mutex) ([]TakenVersion, error) {
|
||||
if !strings.Contains(failure, zeroShort) {
|
||||
return nil, nil
|
||||
}
|
||||
return m.takeZeroRequirements(ctx, root, repository, dir, d, zeroRequirementsOf(dir), asked, pins)
|
||||
}
|
||||
|
||||
// takeReplacedZeroPins takes, before anything is downloaded, the requirements at the zero version that only a module
|
||||
// dir replaces by a directory names (zeroRequirementsOf): go mod download loads only what dir's own go.mod names, so it
|
||||
// passes without them, and the shut-off steps would look them up on the network (issue 1966). One the asked versions
|
||||
// took (before, takeVersions) is left as it is.
|
||||
func (m *Module) takeReplacedZeroPins(ctx context.Context, root, repository, dir, d string, asked sdkstep.PackageVersions, before []TakenVersion, pins *sync.Mutex) ([]TakenVersion, error) {
|
||||
var replaced []zeroRequirement
|
||||
for _, r := range zeroRequirementsOf(dir) {
|
||||
if r.by != "" && !slices.ContainsFunc(before, func(tv TakenVersion) bool { return tv.Package == r.module }) {
|
||||
replaced = append(replaced, r)
|
||||
}
|
||||
}
|
||||
if len(replaced) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
return m.takeZeroRequirements(ctx, root, repository, dir, d, replaced, asked, pins)
|
||||
}
|
||||
|
||||
// takeZeroRequirements takes, for each requirement at the zero version, the version it is built against (ADR 0573 §2,
|
||||
// §4: whatever go.mod names): the commit the ask names for the package; else the head of the package's repository among
|
||||
// the asked versions (the check's or try's members: another package of the same repository names it); else the latest
|
||||
// published version, the trunk's head. It never looks for the commit 000000000000 among the pull request heads. One
|
||||
// taken before (dir's modfile requires or replaces it) is left as it is.
|
||||
func (m *Module) takeZeroRequirements(ctx context.Context, root, repository, dir, d string, reqs []zeroRequirement, asked sdkstep.PackageVersions, pins *sync.Mutex) ([]TakenVersion, error) {
|
||||
resolved := map[string]string{}
|
||||
if modfile, ok := m.pinModfile(dir); ok {
|
||||
if b, err := os.ReadFile(modfile); err == nil {
|
||||
@@ -281,14 +382,16 @@ func (m *Module) takeZeroPins(ctx context.Context, root, repository, dir, d, fai
|
||||
}
|
||||
}
|
||||
var taken []TakenVersion
|
||||
for _, p := range forgePins(string(raw)) {
|
||||
if p.short != zeroShort {
|
||||
continue
|
||||
}
|
||||
for _, r := range reqs {
|
||||
p := r.pin
|
||||
if v := resolved[p.module]; v != "" && !strings.HasSuffix(v, zeroShort) {
|
||||
continue // taken before
|
||||
}
|
||||
named := "v0.0.0-00010101000000-" + zeroShort
|
||||
namedBy := ""
|
||||
if r.by != "" {
|
||||
namedBy = "; " + namedBySaid(r.by)
|
||||
}
|
||||
if "novox/"+p.repo == repository {
|
||||
// A module of the checked repository itself: it is in the fetched tree and nowhere else, since nothing of the
|
||||
// repository is published before its merge. Every module of the tree goes in, as one may require another.
|
||||
@@ -299,7 +402,7 @@ func (m *Module) takeZeroPins(ctx context.Context, root, repository, dir, d, fai
|
||||
if !slices.Contains(own, p.module) {
|
||||
return taken, fmt.Errorf("the requirement of %s at the zero version: no module of that path is in the fetched tree of %s, and nothing of a repository is published before its merge", p.module, repository)
|
||||
}
|
||||
tv := TakenVersion{Dir: d, Package: p.module, Named: named, Built: "the fetched tree", Said: "a module of the checked repository: built from its tree"}
|
||||
tv := TakenVersion{Dir: d, Package: p.module, Named: named, Built: "the fetched tree", Said: "a module of the checked repository: built from its tree" + namedBy}
|
||||
if m.Say != nil {
|
||||
m.Say("%s: %s names no commit; built from the fetched tree of %s", d, p.module, repository)
|
||||
}
|
||||
@@ -316,7 +419,7 @@ func (m *Module) takeZeroPins(ctx context.Context, root, repository, dir, d, fai
|
||||
if err := m.takeVersion(ctx, root, dir, &tv, pins); err != nil {
|
||||
return taken, fmt.Errorf("the requirement of %s at the zero version: %w", p.module, err)
|
||||
}
|
||||
tv.Said = strings.TrimPrefix(tv.Said+"; "+why, "; ")
|
||||
tv.Said = strings.TrimPrefix(tv.Said+"; "+why+namedBy, "; ")
|
||||
if m.Say != nil {
|
||||
m.Say("%s: %s names no commit; built at %s (%s)", d, p.module, tv.Built, why)
|
||||
}
|
||||
@@ -325,6 +428,9 @@ func (m *Module) takeZeroPins(ctx context.Context, root, repository, dir, d, fai
|
||||
return taken, nil
|
||||
}
|
||||
|
||||
// namedBySaid says that a module dir replaces by the directory by names a package, and dir's go.mod does not.
|
||||
func namedBySaid(by string) string { return "named by " + by + ", not by this go.mod" }
|
||||
|
||||
// askedFor is the commit the ask names for the package p: its own, else that of the first package of the same repository
|
||||
// in order (the members' heads are by repository), else none.
|
||||
func askedFor(asked sdkstep.PackageVersions, p pin) string {
|
||||
|
||||
@@ -305,6 +305,9 @@ func TestARequirementAtTheZeroVersionIsTakenFromTheAskOrTheTrunk(t *testing.T) {
|
||||
download string // what go mod download -json answers
|
||||
want string // the version asked of go
|
||||
head bool // the asked commit is on no trunk: only a pull request head carries it
|
||||
// replaced: the directory's go.mod names no mesh package; a module it replaces by a directory of the tree requires
|
||||
// it at the zero version, so go mod download passes and only a shut-off package load would look it up (1966/AC178-1)
|
||||
replaced bool
|
||||
}{
|
||||
{name: "the ask names the package", packages: map[string]any{sdk: trunk}, want: sdk + "@" + trunk,
|
||||
download: `{"Version":"v0.1.15-0.20261101000000-aaaaaaaaaaaa","Sum":"h1:zip="}`},
|
||||
@@ -316,13 +319,31 @@ func TestARequirementAtTheZeroVersionIsTakenFromTheAskOrTheTrunk(t *testing.T) {
|
||||
{name: "the ask names nothing of its repository", packages: map[string]any{"git.novox.be/novox/mesh-tools/go": trunk}, want: sdk + "@latest",
|
||||
download: `{"Version":"v0.1.20-0.20261104000000-bbbbbbbbbbbb","Sum":"h1:zip="}`},
|
||||
{name: "no ask", want: sdk + "@latest", download: `{"Version":"v0.1.20-0.20261104000000-bbbbbbbbbbbb","Sum":"h1:zip="}`},
|
||||
{name: "named only by a module the directory replaces, the ask names it", packages: map[string]any{sdk: trunk}, want: sdk + "@" + trunk, replaced: true,
|
||||
download: `{"Version":"v0.1.15-0.20261101000000-aaaaaaaaaaaa","Sum":"h1:zip="}`},
|
||||
{name: "named only by a module the directory replaces, no ask", want: sdk + "@latest", replaced: true,
|
||||
download: `{"Version":"v0.1.20-0.20261104000000-bbbbbbbbbbbb","Sum":"h1:zip="}`},
|
||||
} {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
h, states := newPinHarness(t, zeroMod, false, pullList)
|
||||
gomod := zeroMod
|
||||
if c.replaced {
|
||||
gomod = "module fixture/member\n\ngo 1.26\n\nrequire fixture/sib v0.0.0-00010101000000-" + zeroShort + "\n\nreplace fixture/sib => ./sib\n"
|
||||
}
|
||||
h, states := newPinHarness(t, gomod, false, pullList)
|
||||
base := h.m.Run
|
||||
h.m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||
line := name + " " + strings.Join(args, " ")
|
||||
switch {
|
||||
case c.replaced && name == "git" && strings.Contains(line, " checkout "):
|
||||
r := base(ctx, env, name, args...)
|
||||
trees, _ := filepath.Glob(filepath.Join(h.m.Work, "*", "tree"))
|
||||
for _, tree := range trees {
|
||||
os.MkdirAll(filepath.Join(tree, "sib"), 0o755)
|
||||
os.WriteFile(filepath.Join(tree, "sib", "go.mod"), []byte(strings.Replace(zeroMod, "fixture/member", "fixture/sib", 1)), 0o644)
|
||||
}
|
||||
return r
|
||||
case c.replaced && name == "go" && strings.Contains(line, " mod download") && !strings.Contains(line, "-json"):
|
||||
return forgekey.Ran{} // go.mod names nothing missing: the download passes
|
||||
case name == "go" && strings.Contains(line, " mod download -json "):
|
||||
h.mu.Lock()
|
||||
h.cmds = append(h.cmds, cmdRecord{env: env, line: line})
|
||||
@@ -357,6 +378,13 @@ func TestARequirementAtTheZeroVersionIsTakenFromTheAskOrTheTrunk(t *testing.T) {
|
||||
if h.ran("pinned/mesh-sdk@" + zeroShort) {
|
||||
t.Error("a repository was placed at the commit of zeros")
|
||||
}
|
||||
if c.replaced {
|
||||
var res FetchResult
|
||||
json.Unmarshal(st.Result, &res)
|
||||
if len(res.Packages) != 1 || res.Packages[0].Package != sdk || !strings.Contains(res.Packages[0].Said, "named by sib, not by this go.mod") {
|
||||
t.Errorf("the taken versions = %+v, want %s said as named by sib", res.Packages, sdk)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,6 +66,13 @@ func (m *Module) takeVersions(ctx context.Context, root, dir, d string, asked sd
|
||||
return nil, nil
|
||||
}
|
||||
required, replaced := goModRequirements(string(raw))
|
||||
// A package only a module dir replaces by a directory names is in dir's module graph too (zeroRequirementsOf).
|
||||
namedBy := map[string]string{}
|
||||
for _, r := range zeroRequirementsOf(dir) {
|
||||
if _, ok := required[r.module]; !ok && r.by != "" {
|
||||
required[r.module], namedBy[r.module] = zeroVersion, r.by
|
||||
}
|
||||
}
|
||||
var taken []TakenVersion
|
||||
for _, pkg := range slices.Sorted(maps.Keys(asked)) {
|
||||
named, ok := required[pkg]
|
||||
@@ -81,6 +88,9 @@ func (m *Module) takeVersions(ctx context.Context, root, dir, d string, asked sd
|
||||
return taken, err
|
||||
}
|
||||
}
|
||||
if by := namedBy[pkg]; by != "" {
|
||||
tv.Said = strings.TrimPrefix(tv.Said+"; "+namedBySaid(by), "; ")
|
||||
}
|
||||
if m.Say != nil {
|
||||
m.Say("%s: %s is built at %s (asked %.12s, go.mod names %s)%s", d, pkg, tv.Built, tv.Asked, tv.Named, saidSuffix(tv.Said))
|
||||
}
|
||||
|
||||
@@ -171,6 +171,14 @@ func (m *Module) downloadBeside(ctx context.Context, root, tree string, dirs []s
|
||||
if res := m.Run(ctx, env, "go", "-C", r, "mod", "download"); res.Status != 0 || res.Err != "" {
|
||||
return fmt.Errorf("go mod download of the graph of %s beside: %s", filepath.Base(r), causeLine(res.Err+"\n"+res.Stdout+"\n"+res.Stderr))
|
||||
}
|
||||
// What the packages' load needs beyond the module graph's download (the go.mod of a version a dependency requires,
|
||||
// which a build of the composer reads: issue 1966), fetched with the network, then proven shut off.
|
||||
if res := m.Run(ctx, env, "go", "-C", r, "list", "-e", "-deps", "-test", "-f", shutOffLoadFormat, "./..."); res.Status != 0 || res.Err != "" {
|
||||
return fmt.Errorf("go list of the packages of %s beside: %s", filepath.Base(r), causeLine(res.Err+"\n"+res.Stdout+"\n"+res.Stderr))
|
||||
}
|
||||
if err := m.shutOffLoad(ctx, env, r, "beside "+filepath.Base(r)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -72,7 +73,7 @@ func TestTheFetchDownloadsTheGraphOfASiblingModuleLaidWithTheTreesItRequires(t *
|
||||
put(filepath.Join(root, "beside", "mesh-sdk", "go"), "module git.novox.be/novox/mesh-sdk/go\n\ngo 1.26.0\n")
|
||||
var said []string
|
||||
var lines, envs []string
|
||||
fail := false
|
||||
fail, lost := false, ""
|
||||
m := &Module{Say: func(f string, a ...any) { said = append(said, fmt.Sprintf(f, a...)) }}
|
||||
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||
lines = append(lines, name+" "+strings.Join(args, " "))
|
||||
@@ -80,14 +81,26 @@ func TestTheFetchDownloadsTheGraphOfASiblingModuleLaidWithTheTreesItRequires(t *
|
||||
if fail {
|
||||
return forgekey.Ran{Status: 1, Stderr: "dial tcp: network is unreachable"}
|
||||
}
|
||||
if slices.Contains(env, "GOPROXY=off") {
|
||||
return forgekey.Ran{Stdout: lost}
|
||||
}
|
||||
return forgekey.Ran{}
|
||||
}
|
||||
if err := m.downloadBeside(context.Background(), root, tree, []string{"modules/x"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(lines) != 1 || lines[0] != "go -C "+host+" mod download" || !strings.Contains(envs[0], "GOWORK="+filepath.Join(root, "beside-0.work")) {
|
||||
t.Fatalf("commands %q env %q, want one go mod download of the node-engine's module over its workspace", lines, envs)
|
||||
// The graph's download, the packages' load with the network, then the same load shut off (issue 1966).
|
||||
load := "go -C " + host + " list -e -deps -test -f " + shutOffLoadFormat + " ./..."
|
||||
if len(lines) != 3 || lines[0] != "go -C "+host+" mod download" || lines[1] != load || lines[2] != load ||
|
||||
!strings.Contains(envs[0], "GOWORK="+filepath.Join(root, "beside-0.work")) || strings.HasSuffix(envs[1], "GOPROXY=off") || !strings.HasSuffix(envs[2], "GOPROXY=off") {
|
||||
t.Fatalf("commands %q env %q, want go mod download of the node-engine's module over its workspace, then its packages loaded with the network and shut off", lines, envs)
|
||||
}
|
||||
// 1966/AC178-3 (its unit half): the shut-off load misses a go.mod the composer's build reads: the fetch fails naming it.
|
||||
lines, lost = nil, "github.com/nats-io/nkeys: github.com/nats-io/nkeys@v0.4.16 requires golang.org/x/crypto@v0.52.0: module lookup disabled by GOPROXY=off\n"
|
||||
if err := m.downloadBeside(context.Background(), root, tree, []string{"modules/x"}); err == nil || !strings.Contains(err.Error(), "beside mesh-host") || !strings.Contains(err.Error(), "golang.org/x/crypto@v0.52.0: module lookup disabled") {
|
||||
t.Errorf("a shut-off load that would reach the network = %v, want the fetch failed naming it", err)
|
||||
}
|
||||
lost = ""
|
||||
work, _ := os.ReadFile(filepath.Join(root, "beside-0.work"))
|
||||
for _, want := range []string{filepath.Join(tree, "modules", "x"), filepath.Join(root, "beside", "mesh-sdk", "go"), host} {
|
||||
if !strings.Contains(string(work), want) {
|
||||
|
||||
@@ -41,6 +41,7 @@ require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/ociartifactregistryclient v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
)
|
||||
|
||||
@@ -101,6 +101,7 @@ require (
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/clients/ociartifactregistryclient v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/gopackageregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/npmpackageregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry v0.0.0-00010101000000-000000000000 // indirect
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user