Go build module: a package whose tests read MESH_NODE_NAMES has the node list in its digest, so a new node misses a kept verdict
This commit is contained in:
@@ -37,6 +37,10 @@ const DigestVersion = "6"
|
||||
// depends on the siblings' commits.
|
||||
const besideVariable = "MESH_CHECK_BESIDE"
|
||||
|
||||
// namesVariable is the same for the mesh's node names: a package whose tests reach a file naming it depends on the node
|
||||
// list the ask carries, so its digest holds the sorted names and a new node misses a kept verdict (ADR 0567 §12).
|
||||
const namesVariable = "MESH_NODE_NAMES"
|
||||
|
||||
// ReadsOutsideMarker is the name of a file in a package's directory that says its tests read files outside it (a
|
||||
// repository's documents, a sibling directory), which the Go toolchain does not list (issue 1935). It lists, one per
|
||||
// line, what those tests read, as paths from the repository's root; a path may be a glob (filepath.Match), and a
|
||||
@@ -164,6 +168,8 @@ func (m *Module) digestTool() StepTool {
|
||||
"workspace": map[string]any{"type": "string", "description": "the workspace id build_go_fetch answered"},
|
||||
"level": map[string]any{"type": "string", "enum": []string{LevelUnit, LevelIntegration}, "description": "the level"},
|
||||
"runner": runnerProperty,
|
||||
delivery.NodeNamesArg: map[string]any{"type": "array", "items": map[string]any{"type": "string"},
|
||||
"description": "optional, the names of the mesh's nodes: a package whose tests read " + delivery.NodeNamesEnv + " has them in its digest"},
|
||||
},
|
||||
"required": []string{"workspace", "level"},
|
||||
},
|
||||
@@ -178,6 +184,10 @@ func (m *Module) digest(ctx context.Context, args map[string]any) (any, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
names, err := nodeNamesArg(args[delivery.NodeNamesArg])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if runner == RunnerWorkspace {
|
||||
if err := m.workspaceThere(workspace); err != nil {
|
||||
return nil, err
|
||||
@@ -191,7 +201,7 @@ func (m *Module) digest(ctx context.Context, args map[string]any) (any, error) {
|
||||
default:
|
||||
return nil, fmt.Errorf("the level %q is neither unit nor integration", level)
|
||||
}
|
||||
a, judged, err := m.asCandidate(ctx, levelSpec{Step: stepDigest, Level: level, Workspace: workspace})
|
||||
a, judged, err := m.asCandidate(ctx, levelSpec{Step: stepDigest, Level: level, Workspace: workspace, NodeNames: names})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -201,7 +211,7 @@ func (m *Module) digest(ctx context.Context, args map[string]any) (any, error) {
|
||||
a.Digest.JudgedBy = judged
|
||||
return *a.Digest, nil
|
||||
}
|
||||
res, err := m.digestHere(ctx, workspace, level)
|
||||
res, err := m.digestHere(ctx, workspace, level, names)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -210,7 +220,7 @@ func (m *Module) digest(ctx context.Context, args map[string]any) (any, error) {
|
||||
}
|
||||
|
||||
// digestHere is the digest step as this program runs it.
|
||||
func (m *Module) digestHere(ctx context.Context, workspace, level string) (DigestResult, error) {
|
||||
func (m *Module) digestHere(ctx context.Context, workspace, level string, names []string) (DigestResult, error) {
|
||||
if !workspaceOK.MatchString(workspace) {
|
||||
return DigestResult{}, fmt.Errorf("%q is not a workspace id", workspace)
|
||||
}
|
||||
@@ -241,6 +251,14 @@ func (m *Module) digestHere(ctx context.Context, workspace, level string) (Diges
|
||||
besideHeader = append(besideHeader, "beside "+b)
|
||||
}
|
||||
|
||||
// The node names, sorted, as a header line for a package that reads them.
|
||||
sortedNames := append([]string{}, names...)
|
||||
sort.Strings(sortedNames)
|
||||
var namesHeader []string
|
||||
if len(sortedNames) > 0 {
|
||||
namesHeader = []string{"node-names " + strings.Join(sortedNames, ",")}
|
||||
}
|
||||
|
||||
dirs, err := m.goModDirs(ctx, tree)
|
||||
if err != nil {
|
||||
return DigestResult{}, err
|
||||
@@ -251,7 +269,7 @@ func (m *Module) digestHere(ctx context.Context, workspace, level string) (Diges
|
||||
if err != nil {
|
||||
return DigestResult{}, err
|
||||
}
|
||||
if err := digestModule(&res, pkgs, tree, header, besideHeader, level == LevelIntegration); err != nil {
|
||||
if err := digestModule(&res, pkgs, tree, header, besideHeader, namesHeader, level == LevelIntegration); err != nil {
|
||||
return DigestResult{}, err
|
||||
}
|
||||
}
|
||||
@@ -302,7 +320,7 @@ func parsePackages(out string) (map[string]*goPackage, error) {
|
||||
|
||||
// digestModule adds to res the digest of each root package of one go.mod directory. A root is a package of the main
|
||||
// module that is listed, not only a dependency, and not a variant of another.
|
||||
func digestModule(res *DigestResult, pkgs map[string]*goPackage, tree string, header, beside []string, needs bool) error {
|
||||
func digestModule(res *DigestResult, pkgs map[string]*goPackage, tree string, header, beside, names []string, needs bool) error {
|
||||
roots := []string{}
|
||||
for path, p := range pkgs {
|
||||
if p.ForTest == "" && !p.DepOnly && !p.Standard && !strings.HasSuffix(path, ".test") && p.Module != nil && p.Module.Main {
|
||||
@@ -349,7 +367,7 @@ func digestModule(res *DigestResult, pkgs map[string]*goPackage, tree string, he
|
||||
res.neverKept[path] = why
|
||||
continue
|
||||
}
|
||||
d, err := packageDigest(root, testMain, pkgs, tree, header, beside, declared)
|
||||
d, err := packageDigest(root, testMain, pkgs, tree, header, beside, names, declared)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", path, err)
|
||||
}
|
||||
@@ -361,9 +379,9 @@ func digestModule(res *DigestResult, pkgs map[string]*goPackage, tree string, he
|
||||
// packageDigest is the digest of one package: a hash over a sorted text of the header, the files of every package its
|
||||
// tests reach in the module's own tree (by their content, under their path in the tree), the version and checksum of
|
||||
// every module outside it, and the package's own test data. The beside lines (the siblings' commits) join the header
|
||||
// only when a file the tests reach names MESH_CHECK_BESIDE. A package of the standard library is the Go version's.
|
||||
// only when a file the tests reach names MESH_CHECK_BESIDE, and the node names likewise for MESH_NODE_NAMES. A package of the standard library is the Go version's.
|
||||
// The lines a package's ReadsOutsideMarker declares join them (declaredReads).
|
||||
func packageDigest(root, testMain *goPackage, pkgs map[string]*goPackage, tree string, header, beside, declared []string) (string, error) {
|
||||
func packageDigest(root, testMain *goPackage, pkgs map[string]*goPackage, tree string, header, beside, names, declared []string) (string, error) {
|
||||
reached := map[string]*goPackage{root.ImportPath: root}
|
||||
for _, dep := range testMain.Deps {
|
||||
if p := pkgs[dep]; p != nil {
|
||||
@@ -371,7 +389,7 @@ func packageDigest(root, testMain *goPackage, pkgs map[string]*goPackage, tree s
|
||||
}
|
||||
}
|
||||
lines := append([]string{}, declared...)
|
||||
readsBeside := false
|
||||
readsBeside, readsNames := false, false
|
||||
for _, p := range reached {
|
||||
if p.Standard || p.Module == nil {
|
||||
continue
|
||||
@@ -390,9 +408,10 @@ func packageDigest(root, testMain *goPackage, pkgs map[string]*goPackage, tree s
|
||||
return "", err
|
||||
}
|
||||
lines = append(lines, l)
|
||||
if !readsBeside {
|
||||
if raw, err := os.ReadFile(filepath.Join(p.Dir, f)); err == nil && bytes.Contains(raw, []byte(besideVariable)) {
|
||||
readsBeside = true
|
||||
if !readsBeside || !readsNames {
|
||||
if raw, err := os.ReadFile(filepath.Join(p.Dir, f)); err == nil {
|
||||
readsBeside = readsBeside || bytes.Contains(raw, []byte(besideVariable))
|
||||
readsNames = readsNames || bytes.Contains(raw, []byte(namesVariable))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -424,6 +443,9 @@ func packageDigest(root, testMain *goPackage, pkgs map[string]*goPackage, tree s
|
||||
if readsBeside {
|
||||
header = append(append([]string{}, header...), beside...)
|
||||
}
|
||||
if readsNames {
|
||||
header = append(append([]string{}, header...), names...)
|
||||
}
|
||||
h := sha256.New()
|
||||
for _, l := range append(append([]string{}, header...), lines...) {
|
||||
h.Write([]byte(l + "\n"))
|
||||
|
||||
@@ -172,8 +172,9 @@ func TestAModuleOutsideTheTreeCountsByVersionAndChecksum(t *testing.T) {
|
||||
root := &goPackage{ImportPath: "x/r", Dir: dir, Module: &goModule{Path: "x", Main: true}, GoFiles: []string{"r.go"}, TestGoFiles: []string{"r.go"}}
|
||||
testMain := &goPackage{ImportPath: "x/r.test", Deps: []string{ext.ImportPath}}
|
||||
pkgs[root.ImportPath] = root
|
||||
var names []string
|
||||
digest := func() string {
|
||||
d, err := packageDigest(root, testMain, pkgs, dir, []string{"digest 1"}, nil, nil)
|
||||
d, err := packageDigest(root, testMain, pkgs, dir, []string{"digest 1"}, nil, names, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -188,6 +189,20 @@ func TestAModuleOutsideTheTreeCountsByVersionAndChecksum(t *testing.T) {
|
||||
if d3 := digest(); d3 == d1 {
|
||||
t.Errorf("a new version left the digest as it was")
|
||||
}
|
||||
// A different node list misses the cache only for a package whose tests read MESH_NODE_NAMES (ADR 0567 §12).
|
||||
names = []string{"node-names a"}
|
||||
d4 := digest()
|
||||
names = []string{"node-names a", "node-names b"}
|
||||
if d5 := digest(); d5 != d4 {
|
||||
t.Errorf("a package that does not read the node names changed with them")
|
||||
}
|
||||
os.WriteFile(filepath.Join(dir, "r.go"), []byte("package r\n// "+namesVariable+"\n"), 0o644)
|
||||
names = []string{"node-names a"}
|
||||
d6 := digest()
|
||||
names = []string{"node-names a", "node-names b"}
|
||||
if d7 := digest(); d7 == d6 {
|
||||
t.Errorf("a new node left the digest of a package that reads the node names as it was")
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 1935, 1936: a package whose reads-outside marker lists nothing it can trust is never kept: its digest is one no
|
||||
|
||||
@@ -222,7 +222,7 @@ func (m *Module) answer(ctx context.Context, spec levelSpec) levelAnswer {
|
||||
tree := filepath.Join(m.Work, spec.Workspace, "tree")
|
||||
switch spec.Step {
|
||||
case stepDigest:
|
||||
d, err := m.digestHere(ctx, spec.Workspace, spec.Level)
|
||||
d, err := m.digestHere(ctx, spec.Workspace, spec.Level, spec.NodeNames)
|
||||
if err != nil {
|
||||
return levelAnswer{Error: err.Error()}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user