S4: the fetch proves with a shut-off package load that it downloaded all a step loads (issue 1966)
mesh/delivery merged: source.main moved to novox/mesh-catalog@7cc81512a389
mesh/check checking 4a3914ee1ce7 on 3c0b4d29a8ea
mesh/delivery merged: source.main moved to novox/mesh-catalog@7cc81512a389
mesh/check checking 4a3914ee1ce7 on 3c0b4d29a8ea
go mod download loads only the module graph a go.mod names; a vet, a test or a build then loads packages shut off from the network and failed far from the fetch, in fewer words (the builder's "pull_drop.go:11:2: module lookup disabled by GOPROXY=off"). After each directory's download the fetch loads its packages with their tests shut off and fails naming each package whose load would reach the network. The graph of a sibling laid beside is loaded once with the network first, so a go.mod the composer's build reads and the graph's download skipped is fetched, then proven the same way. 1966/AC178-2: a row of TestAVerifyThatFailsEndsTheStepNamingTheDirectoryAndTheLine; the beside case in TestTheFetchDownloadsTheGraphOfASiblingModuleLaidWithTheTreesItRequires. Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -212,7 +213,9 @@ func (m *Module) downloadModules(ctx context.Context, root, repository, dir, d s
|
|||||||
for {
|
for {
|
||||||
failure := m.downloadAndVerify(ctx, dir, d)
|
failure := m.downloadAndVerify(ctx, dir, d)
|
||||||
if failure == nil {
|
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.
|
// 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)
|
took, err := m.takeZeroPins(ctx, root, repository, dir, d, failure.Error(), asked, pins)
|
||||||
@@ -232,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.
|
// 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
|
var verifyLocks sync.Map
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"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.
|
// 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) {
|
func TestAVerifyThatFailsEndsTheStepNamingTheDirectoryAndTheLine(t *testing.T) {
|
||||||
f := &fakeStep{gomods: "go.mod\ntools/go.mod\n", goRule: func(line string) forgekey.Ran {
|
const seatPkg = "git.novox.be/novox/mesh-delivery/contracts/go/seats/nodebuildgo"
|
||||||
if strings.HasSuffix(line, "tools mod verify") || strings.Contains(line, "/tools mod verify") {
|
for _, c := range []struct {
|
||||||
return forgekey.Ran{Status: 1, Stdout: "\nexample.org/x v1.0.0: dir has been modified\nsecond line\n"}
|
name, ran, out string
|
||||||
}
|
want []string
|
||||||
return forgekey.Ran{}
|
notWant string
|
||||||
}}
|
}{
|
||||||
m, states := fetchModule(t, f)
|
{name: "go mod verify fails", ran: "tools mod verify", out: "\nexample.org/x v1.0.0: dir has been modified\nsecond line\n",
|
||||||
if _, err := fetchTool(t, m)(fetchArgs); err != nil {
|
want: []string{"tools", "example.org/x v1.0.0: dir has been modified"}, notWant: "second line"},
|
||||||
t.Fatal(err)
|
// 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.
|
||||||
st := states.waitEnd(t)
|
{name: "the shut-off package load would reach the network", ran: "tools list -e -deps -test",
|
||||||
if st.State != sdkstep.StepFailed {
|
out: "fixture/tools/cmd: \n" + seatPkg + ": module lookup disabled by GOPROXY=off\n",
|
||||||
t.Fatalf("the step ended %s, want failed", st.State)
|
want: []string{"tools", "shut off", seatPkg + ": module lookup disabled by GOPROXY=off"}, notWant: "fixture/tools/cmd"},
|
||||||
}
|
} {
|
||||||
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.Run(c.name, func(t *testing.T) {
|
||||||
t.Errorf("reason = %q", st.Reason)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 != "" {
|
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))
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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")
|
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 said []string
|
||||||
var lines, envs []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 := &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 {
|
m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
|
||||||
lines = append(lines, name+" "+strings.Join(args, " "))
|
lines = append(lines, name+" "+strings.Join(args, " "))
|
||||||
@@ -80,14 +81,26 @@ func TestTheFetchDownloadsTheGraphOfASiblingModuleLaidWithTheTreesItRequires(t *
|
|||||||
if fail {
|
if fail {
|
||||||
return forgekey.Ran{Status: 1, Stderr: "dial tcp: network is unreachable"}
|
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{}
|
return forgekey.Ran{}
|
||||||
}
|
}
|
||||||
if err := m.downloadBeside(context.Background(), root, tree, []string{"modules/x"}); err != nil {
|
if err := m.downloadBeside(context.Background(), root, tree, []string{"modules/x"}); err != nil {
|
||||||
t.Fatal(err)
|
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")) {
|
// The graph's download, the packages' load with the network, then the same load shut off (issue 1966).
|
||||||
t.Fatalf("commands %q env %q, want one go mod download of the node-engine's module over its workspace", lines, envs)
|
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"))
|
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} {
|
for _, want := range []string{filepath.Join(tree, "modules", "x"), filepath.Join(root, "beside", "mesh-sdk", "go"), host} {
|
||||||
if !strings.Contains(string(work), want) {
|
if !strings.Contains(string(work), want) {
|
||||||
|
|||||||
Reference in New Issue
Block a user