Build Go modules against the package versions an ask names

ADR 0573 §2 and §4 have a build take the latest published version of
each mesh package, or a group's own head, whatever go.mod names (issue
1585, S15). The fetch writes each asked version into the directory's
temporary modfile before downloading: a trunk commit is required at its
Go version with its checksums for go mod verify, a pull request's head
is placed as a pin on a head is, a newer go.mod version is kept and
said, and the answer and the log name each.

Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
2026-11-01 02:46:04 +01:00
parent 9fa7f7b535
commit fcd7df5356
6 changed files with 453 additions and 26 deletions
+7
View File
@@ -55,6 +55,13 @@ fetch looks for that commit among the forge's `refs/pull/*/head`, places the rep
temporary `-modfile` in the workspace's `pins/` that replaces the module by that directory. A pin Go can fetch is left as
it is, a pin on no ref fails naming the module and the commit, and a directory that vendors is never resolved.
The asked versions of the mesh's packages (novox/hq ADR 0573 §2, §4; issue 1585): a fetch given `packages` (a full commit
by Go module path, which the builder and the checker fill from the indexer's trunk values or a group's heads) writes each
into the temporary `-modfile` of every directory whose `go.mod` requires the package, before anything is downloaded. A
commit Go can fetch is required at its Go version with its checksums beside the modfile, which `go mod verify` checks; a
head only a pull request carries is placed as a pin on a head is; a newer version `go.mod` names is built instead, and
said. The fetch's answer names each in `packages`, and the module's log says each.
The account's ssh files (`files/forge`, `files/known_hosts`, `files/gitconfig`) are filled on the node by the
provisioner's client from the module's entry of `forge-account` (the manifest says `moved`, ADR 0466 §4, ADR 0400 §5).
The forge writes that entry only once the module has run and reported its key (ADR 0400 §2), so until then the client
@@ -52,6 +52,9 @@ type FetchResult struct {
// Beside are the sibling repositories placed as plain directories under the workspace's beside directory, which
// build_go_test hands to the tests as MESH_CHECK_BESIDE (ADR 0484 clause 4).
Beside []Sibling `json:"beside,omitempty"`
// Packages are, of each directory whose go.mod requires an asked mesh package, the version it is built against
// (versions.go; novox/hq ADR 0573 §2): the asked, or go.mod's when it names a newer one, said.
Packages []TakenVersion `json:"packages,omitempty"`
}
// fetchTool is the fetch step tool.
@@ -70,6 +73,7 @@ func (m *Module) fetchTool() StepTool {
"base": map[string]any{"type": "string", "description": "optional trunk commit, 40 hex digits: the head is merged onto it as the forge's merge would, and the answer carries the merge commit, the files in conflict (an answer, the workspace is then left at the trunk commit), the changed paths and the tree of each module directory"},
"dir": buildstep.FetchRequestProperties()["dir"],
"paths": buildstep.FetchRequestProperties()["paths"],
"packages": buildstep.FetchRequestProperties()["packages"],
"beside": map[string]any{"type": "array", "description": "optional sibling repositories the tests read, each placed as a plain directory (no symlink) under the workspace's beside/<name>, which build_go_test gives the tests as MESH_CHECK_BESIDE",
"items": map[string]any{"type": "object", "properties": map[string]any{
"repository": map[string]any{"type": "string", "description": "<owner>/<repository> on the forge"},
@@ -156,6 +160,7 @@ func (m *Module) fetchInto(ctx context.Context, req buildstep.FetchRequest, id s
// step's. Resolving a pin places a repository beside the workspace, which two directories may share: one at a time.
var pins sync.Mutex
errs := make([]error, len(dirs))
taken := make([][]TakenVersion, len(dirs))
slots := make(chan struct{}, m.parallel())
var wg sync.WaitGroup
for i, d := range dirs {
@@ -167,6 +172,10 @@ func (m *Module) fetchInto(ctx context.Context, req buildstep.FetchRequest, id s
go func() {
defer wg.Done()
defer func() { <-slots }()
// The asked versions of the mesh's packages go into the modfile before anything is downloaded (versions.go).
if taken[i], errs[i] = m.takeVersions(ctx, root, filepath.Join(tree, d), d, req.Packages, &pins); errs[i] != nil {
return
}
errs[i] = m.downloadModules(ctx, root, filepath.Join(tree, d), d, &pins)
}()
}
@@ -176,6 +185,9 @@ func (m *Module) fetchInto(ctx context.Context, req buildstep.FetchRequest, id s
return nil, err
}
}
for _, t := range taken {
res.Packages = append(res.Packages, t...)
}
return res, nil
}
@@ -204,18 +216,20 @@ func (m *Module) downloadModules(ctx context.Context, root, dir, d string, pins
var verifyLocks sync.Map
// verifiedMark is the file naming a pass of go mod verify for a go.mod and go.sum pair, "" when a pass is not kept:
// the module cache is only read by steps that are shut off with it read-only (ADR 0448 §3), so the pass holds only then,
// and a directory with a pin modfile verifies another file.
// the module cache is only read by steps that are shut off with it read-only (ADR 0448 §3), so the pass holds only then.
// A directory with a temporary modfile (an asked version, a pin on a head) verifies the modfile and the sums beside it,
// which a placed repository's path makes the workspace's own.
func (m *Module) verifiedMark(dir string) string {
if m.Cache == "" || m.Bubblewrap == nil {
return ""
}
if _, ok := m.pinModfile(dir); ok {
return ""
from := dir
if modfile, ok := m.pinModfile(dir); ok {
from = filepath.Dir(modfile)
}
h := sha256.New()
for _, name := range []string{"go.mod", "go.sum"} {
raw, _ := os.ReadFile(filepath.Join(dir, name))
raw, _ := os.ReadFile(filepath.Join(from, name))
fmt.Fprintf(h, "%s %d\n", name, len(raw))
h.Write(raw)
}
+29 -15
View File
@@ -151,27 +151,41 @@ func (m *Module) placePin(ctx context.Context, root string, p pin, failure strin
return where, nil
}
// replaceInModfile adds `replace module => where` to the temporary modfile of dir, made from the directory's go.mod and
// go.sum the first time; Go reads the sums beside the modfile.
// replaceInModfile adds `replace module => where` to the temporary modfile of dir (ensureModfile).
func (m *Module) replaceInModfile(dir, module, where string) error {
pins := m.pinsDir(dir)
if pins == "" {
return fmt.Errorf("the directory %s lies outside a workspace", dir)
}
if err := os.MkdirAll(pins, 0o700); err != nil {
modfile, err := m.ensureModfile(dir)
if err != nil {
return err
}
modfile := filepath.Join(pins, "go.mod")
body, err := os.ReadFile(modfile)
if err != nil {
if body, err = os.ReadFile(filepath.Join(dir, "go.mod")); err != nil {
return err
}
sum, _ := os.ReadFile(filepath.Join(dir, "go.sum"))
if err := os.WriteFile(filepath.Join(pins, "go.sum"), sum, 0o600); err != nil {
return err
}
return err
}
body = append(body, []byte("\nreplace "+module+" => "+where+"\n")...)
return os.WriteFile(modfile, body, 0o600)
}
// ensureModfile is the temporary modfile of dir, made from the directory's go.mod and go.sum the first time (an asked
// version, versions.go, or a pin on a head writes into it); Go reads the sums beside the modfile.
func (m *Module) ensureModfile(dir string) (string, error) {
pins := m.pinsDir(dir)
if pins == "" {
return "", fmt.Errorf("the directory %s lies outside a workspace", dir)
}
modfile := filepath.Join(pins, "go.mod")
if _, err := os.Stat(modfile); err == nil {
return modfile, nil
}
if err := os.MkdirAll(pins, 0o700); err != nil {
return "", err
}
body, err := os.ReadFile(filepath.Join(dir, "go.mod"))
if err != nil {
return "", err
}
sum, _ := os.ReadFile(filepath.Join(dir, "go.sum"))
if err := os.WriteFile(filepath.Join(pins, "go.sum"), sum, 0o600); err != nil {
return "", err
}
return modfile, os.WriteFile(modfile, body, 0o600)
}
@@ -16,10 +16,11 @@ import (
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// 1148/AC2: given a forge with a mesh-sdk-like pull request whose branch is deleted (only refs/pull/7/head carries its
// commit) and a Go member pinning that head by a pseudo-version, when build_go_fetch then build_go_build run on the
// member, then both pass, the build with GOPROXY=off.
func TestRealFetchAndBuildOfAMemberPinningAPullHead(t *testing.T) {
// 1148/AC2 as issue 1585 S15 has it (novox/hq ADR 0573 §4): given a forge with a mesh-sdk-like pull request whose branch
// is deleted (only refs/pull/7/head carries its commit) and a Go member whose go.mod names an older commit of it, when
// build_go_fetch is asked that head as the package's version and build_go_build runs on the member, then both pass, the
// build with GOPROXY=off against the head: the older commit holds no Go package at all.
func TestRealFetchAndBuildOfAMemberAskedAPullHeadItsGoModDoesNotName(t *testing.T) {
// Not skipped when a tool is missing: a pass of the check must mean this test ran (the check prints no test log).
for _, tool := range []string{"go", "git"} {
if _, err := exec.LookPath(tool); err != nil {
@@ -34,7 +35,7 @@ func TestRealFetchAndBuildOfAMemberPinningAPullHead(t *testing.T) {
}, "refs/pull/7/head")
git(t, work, "checkout", "-q", "main") // no branch of the repository carries the head
member := map[string]string{
"go.mod": "module fixture/member\n\ngo 1.25.0\n\nrequire git.novox.be/novox/dep/go v0.0.0-20261022120000-" + head[:12] + "\n",
"go.mod": "module fixture/member\n\ngo 1.25.0\n\nrequire git.novox.be/novox/dep/go v0.0.0-20261021120000-" + fork[:12] + "\n",
"cmd/prog/main.go": "package main\n\nimport (\n\t\"fmt\"\n\n\tdep \"git.novox.be/novox/dep/go\"\n)\n\n" +
"func main() { fmt.Println(dep.Name) }\n",
"module.json": `{"module":"fixture-member","version":"1","build":{"artifacts":[{"name":"tools","kind":"bundle","language":"go","system":"arch","from":"cmd/prog","binary":"prog-bin","loads":["prog-bin"]}]}}`,
@@ -43,10 +44,14 @@ func TestRealFetchAndBuildOfAMemberPinningAPullHead(t *testing.T) {
m, states := f.module(t)
withStore(t, m) // the build pushes its archive (ADR 0498)
res, st := fetched(t, m, states, map[string]any{"repository": "novox/member", "commit": memberHead})
res, st := fetched(t, m, states, map[string]any{"repository": "novox/member", "commit": memberHead,
"packages": map[string]any{"git.novox.be/novox/dep/go": head}})
if st.State != delivery.StepPassed {
t.Fatalf("the fetch ended %s: %s", st.State, st.Reason)
}
if len(res.Packages) != 1 || res.Packages[0].Built != head || res.Packages[0].Named != "v0.0.0-20261021120000-"+fork[:12] {
t.Errorf("the fetch names what it built against: %+v, want the asked head %s", res.Packages, head)
}
if _, err := os.Stat(filepath.Join(m.Work, res.Workspace, "pinned", "dep@"+head, "beside", "dep", "go", "dep.go")); err != nil {
t.Errorf("the pull request's repository was not placed beside the workspace: %v", err)
}
@@ -0,0 +1,265 @@
package main
// The asked versions of the mesh's packages (novox/hq ADR 0573 §2, §4; issue 1585): every module builds against the latest
// published version of each mesh package it uses, and a delivery group's check against the group's own heads, whatever
// version its go.mod names. The builder and the checker name each version in the ask (delivery.StepAsk.Packages, a full
// commit by package); before Go downloads anything, the fetch writes them into the temporary -modfile of each directory
// whose go.mod requires the package (pins.go), so the fetch's go commands, the build and the tests all take them:
//
// - a commit Go can fetch (a trunk merge) is resolved to its Go version, required in the modfile, and its checksums
// written beside it, which go mod verify then checks the downloaded module against;
// - a commit Go cannot fetch (the head of a group's pull request) is found among the forge's pull request heads and
// placed beside the workspace, and the modfile replaces the package by it, as a pin on a head is (pins.go);
// - a version go.mod names is the oldest the module accepts: one newer than the asked is built, and said loudly.
//
// Each package's outcome is named in the module's log and in the fetch's answer (FetchResult.Packages).
import (
"context"
"encoding/json"
"fmt"
"maps"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"sync"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// TakenVersion is what the fetch built a directory against for one asked package: the asked commit, the version its go.mod
// names, the version built (a Go version, or the asked commit when it was placed from a pull request's head) and its
// checksum, and why the built one is not the asked one when it is not.
type TakenVersion struct {
Dir string `json:"dir"`
Package string `json:"package"`
Asked string `json:"asked"`
Named string `json:"named"`
Built string `json:"built"`
Sum string `json:"sum,omitempty"`
Said string `json:"said,omitempty"`
}
// downloadInfo is what go mod download -json answers of one module.
type downloadInfo struct {
Version string
Sum string
GoModSum string
Error string
}
// takeVersions writes the asked versions into the modfile of dir (d in the tree) and answers what it took, one entry per
// asked package its go.mod requires. A package go.mod replaces is built as go.mod says, and said. A version that can be
// neither fetched nor found among the forge's pull request heads fails the fetch, naming it. pins serialises the
// placements a workspace's directories share.
func (m *Module) takeVersions(ctx context.Context, root, dir, d string, asked delivery.PackageVersions, pins *sync.Mutex) ([]TakenVersion, error) {
raw, err := os.ReadFile(filepath.Join(dir, "go.mod"))
if err != nil {
return nil, nil
}
required, replaced := goModRequirements(string(raw))
var taken []TakenVersion
for _, pkg := range slices.Sorted(maps.Keys(asked)) {
named, ok := required[pkg]
if !ok {
continue // the module does not use it
}
tv := TakenVersion{Dir: d, Package: pkg, Asked: asked[pkg], Named: named}
switch {
case replaced[pkg]:
tv.Built, tv.Said = named, "go.mod replaces it, so it is built as go.mod says"
default:
if err := m.takeVersion(ctx, root, dir, &tv, pins); err != nil {
return taken, err
}
}
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))
}
taken = append(taken, tv)
}
return taken, nil
}
func saidSuffix(said string) string {
if said == "" {
return ""
}
return ": " + said
}
// takeVersion takes one asked version into dir's modfile (takeVersions).
func (m *Module) takeVersion(ctx context.Context, root, dir string, tv *TakenVersion, pins *sync.Mutex) error {
modfile, err := m.ensureModfile(dir)
if err != nil {
return err
}
pinsDir := filepath.Dir(modfile)
r := m.Run(ctx, m.goEnv(), "go", "-C", pinsDir, "mod", "download", "-json", tv.Package+"@"+tv.Asked)
var info downloadInfo
_ = json.Unmarshal([]byte(r.Stdout), &info)
if r.Status != 0 || r.Err != "" || info.Version == "" || info.Error != "" {
// Not a commit Go can fetch: the head of a group's pull request (ADR 0573 §4), placed from the forge's heads.
failure := strings.TrimSpace(info.Error + "\n" + r.Err + "\n" + r.Stderr)
repo, sub, _ := strings.Cut(strings.TrimPrefix(tv.Package, forgePrefix), "/")
p := pin{module: tv.Package, repo: repo, sub: sub, short: tv.Asked[:12]}
pins.Lock()
where, err := m.placePin(ctx, root, p, failure)
if err == nil {
err = m.replaceInModfile(dir, tv.Package, where)
}
pins.Unlock()
if err != nil {
return fmt.Errorf("the asked version %s of %s: %w", tv.Asked, tv.Package, err)
}
tv.Built, tv.Said = tv.Asked, "Go cannot fetch it: placed from the pull request's head, which has no checksum"
return nil
}
if compareVersions(tv.Named, info.Version) > 0 {
// A version go.mod names is the oldest the module accepts (ADR 0573 §2): said loudly, and go.mod's is built.
tv.Built = tv.Named
tv.Said = fmt.Sprintf("go.mod names %s, newer than the asked %s, so go.mod's is built (a version a repository names is the oldest it accepts, ADR 0573 §2)", tv.Named, info.Version)
return nil
}
tv.Built, tv.Sum = info.Version, info.Sum
if tv.Named == info.Version {
return nil
}
if r := m.Run(ctx, m.goEnv(), "go", "-C", pinsDir, "mod", "edit", "-require="+tv.Package+"@"+info.Version); r.Status != 0 || r.Err != "" {
return fmt.Errorf("the asked version %s of %s could not be required: %s", info.Version, tv.Package, causeLine(r.Err+"\n"+r.Stderr))
}
// The checksums Go computed of the download, which go mod verify checks the module cache against.
sums := ""
if info.Sum != "" {
sums += tv.Package + " " + info.Version + " " + info.Sum + "\n"
}
if info.GoModSum != "" {
sums += tv.Package + " " + info.Version + "/go.mod " + info.GoModSum + "\n"
}
f, err := os.OpenFile(filepath.Join(pinsDir, "go.sum"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)
if err != nil {
return err
}
_, err = f.WriteString(sums)
if cerr := f.Close(); err == nil {
err = cerr
}
return err
}
// goModRequirements are the module paths a go.mod requires, with the version each names, and those it replaces.
func goModRequirements(gomod string) (required map[string]string, replaced map[string]bool) {
required, replaced = map[string]string{}, map[string]bool{}
block := ""
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 f[0] == ")":
block = ""
continue
case len(f) == 2 && f[1] == "(":
block = f[0]
continue
}
verb := block
if f[0] == "require" || f[0] == "replace" {
verb, f = f[0], f[1:]
}
switch {
case verb == "require" && len(f) >= 2:
required[f[0]] = f[1]
case verb == "replace" && len(f) >= 1:
replaced[f[0]] = true
}
}
return required, replaced
}
// compareVersions orders two Go module versions (semantic versions, pseudo-versions among them, by their pre-release
// identifiers): negative when a is older than b, 0 when equal, positive when newer. A version that is not a semantic
// version is older than any that is.
func compareVersions(a, b string) int {
pa, oka := parseVersion(a)
pb, okb := parseVersion(b)
switch {
case !oka && !okb:
return strings.Compare(a, b)
case !oka:
return -1
case !okb:
return 1
}
for i := 0; i < 3; i++ {
if c := pa.core[i] - pb.core[i]; c != 0 {
if c < 0 {
return -1
}
return 1
}
}
switch {
case len(pa.pre) == 0 && len(pb.pre) == 0:
return 0
case len(pa.pre) == 0:
return 1 // a release is newer than its pre-releases
case len(pb.pre) == 0:
return -1
}
for i := 0; i < len(pa.pre) && i < len(pb.pre); i++ {
if c := comparePre(pa.pre[i], pb.pre[i]); c != 0 {
return c
}
}
return len(pa.pre) - len(pb.pre)
}
type version struct {
core [3]int
pre []string
}
func parseVersion(v string) (version, bool) {
s, ok := strings.CutPrefix(v, "v")
if !ok {
return version{}, false
}
s, _, _ = strings.Cut(s, "+")
core, pre, hasPre := strings.Cut(s, "-")
parts := strings.Split(core, ".")
if len(parts) != 3 {
return version{}, false
}
var out version
for i, p := range parts {
n, err := strconv.Atoi(p)
if err != nil || n < 0 {
return version{}, false
}
out.core[i] = n
}
if hasPre {
out.pre = strings.Split(pre, ".")
}
return out, true
}
func comparePre(a, b string) int {
na, ea := strconv.Atoi(a)
nb, eb := strconv.Atoi(b)
switch {
case ea == nil && eb == nil:
return na - nb
case ea == nil:
return -1 // numeric identifiers are older than alphanumeric ones
case eb == nil:
return 1
}
return strings.Compare(a, b)
}
@@ -0,0 +1,122 @@
package main
import (
"context"
"encoding/json"
"os"
"path/filepath"
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-sdk/go/forgekey"
)
// Issue 1585 S15 (novox/hq ADR 0573 §2, §4): the asked version of a mesh package is written into the directory's modfile
// before anything is downloaded, whatever go.mod names, with its checksums beside it, and named in the fetch's answer: a
// trunk commit Go resolves is required at its Go version; a version go.mod names that is newer is built instead, said; a
// group's head Go cannot fetch is placed from the forge's pull request heads; a go.mod that does not require the package
// takes nothing.
func TestAnAskedVersionIsWrittenIntoTheModfileWhateverGoModNames(t *testing.T) {
const sdk = "git.novox.be/novox/mesh-sdk/go"
trunk := strings.Repeat("a", 40)
for _, c := range []struct {
name, asked, answer string
failing bool
built, said, edit string
}{
{name: "a trunk commit newer than go.mod's", asked: trunk,
answer: `{"Path":"` + sdk + `","Version":"v0.1.15-0.20261101000000-aaaaaaaaaaaa","Sum":"h1:zip=","GoModSum":"h1:mod="}`,
built: "v0.1.15-0.20261101000000-aaaaaaaaaaaa", edit: "-require=" + sdk + "@v0.1.15-0.20261101000000-aaaaaaaaaaaa"},
{name: "go.mod names a newer one", asked: trunk,
answer: `{"Path":"` + sdk + `","Version":"v0.1.15-0.20261001000000-aaaaaaaaaaaa","Sum":"h1:zip="}`,
built: "v0.1.15-0.20261022120000-" + pinShort, said: "newer than the asked"},
{name: "a group's head Go cannot fetch", asked: commitSHA, failing: true,
answer: `{"Path":"` + sdk + `","Error":"invalid version: unknown revision ` + pinShort + `"}`,
built: commitSHA, said: "placed from the pull request's head"},
} {
t.Run(c.name, func(t *testing.T) {
h, states := newPinHarness(t, pinnedGoMod, false, pullList)
h.f.gomods = "go.mod\nother/go.mod\n"
base := h.m.Run
h.m.Run = func(ctx context.Context, env []string, name string, args ...string) forgekey.Ran {
line := name + " " + strings.Join(args, " ")
if name == "git" && strings.Contains(line, " checkout ") && !strings.Contains(line, "pinned") {
r := base(ctx, env, name, args...)
for i, a := range args {
if a == "-C" { // other/ uses no mesh package
os.WriteFile(filepath.Join(args[i+1], "other", "go.mod"), []byte("module fixture/other\n\ngo 1.26\n"), 0o644)
}
}
return r
}
if name == "go" && strings.Contains(line, "/other mod ") {
return forgekey.Ran{} // other/ requires nothing Go cannot fetch
}
if name == "go" && strings.Contains(line, " mod download -json "+sdk+"@") {
h.mu.Lock()
h.cmds = append(h.cmds, cmdRecord{env: env, line: line})
h.mu.Unlock()
if c.failing {
return forgekey.Ran{Status: 1, Stdout: c.answer}
}
return forgekey.Ran{Stdout: c.answer}
}
return base(ctx, env, name, args...)
}
args := map[string]any{"repository": "novox/mesh-delivery", "commit": commitSHA, "packages": map[string]any{sdk: c.asked}}
if _, err := fetchTool(t, h.m)(args); err != nil {
t.Fatal(err)
}
st := states.waitEnd(t)
if st.State != delivery.StepPassed {
t.Fatalf("the step ended %s: %s", st.State, st.Reason)
}
var res FetchResult
if err := json.Unmarshal(st.Result, &res); err != nil {
t.Fatal(err)
}
if len(res.Packages) != 1 {
t.Fatalf("packages = %+v, want one: the directory requiring mesh-sdk", res.Packages)
}
got := res.Packages[0]
if got.Dir != "." || got.Package != sdk || got.Asked != c.asked || got.Built != c.built || !strings.Contains(got.Said, c.said) {
t.Errorf("taken = %+v, want built %s saying %q", got, c.built, c.said)
}
if h.ran(" mod edit ") != (c.edit != "") || (c.edit != "" && !h.ran(c.edit)) {
t.Errorf("go mod edit %q ran: %v", c.edit, h.ran(" mod edit "))
}
modfile, ok := h.m.pinModfile(filepath.Join(h.m.Work, res.Workspace, "tree"))
if !ok {
t.Fatal("the directory was handed no modfile")
}
sums, _ := os.ReadFile(filepath.Join(filepath.Dir(modfile), "go.sum"))
if wantSum := c.edit != ""; strings.Contains(string(sums), "h1:zip=") != wantSum || strings.Contains(string(sums), "/go.mod h1:mod=") != wantSum {
t.Errorf("the checksums beside the modfile:\n%s", sums)
}
body, _ := os.ReadFile(modfile)
if placed := strings.Contains(string(body), "replace "+sdk+" => "); placed != c.failing {
t.Errorf("the modfile replaces mesh-sdk by a placed head: %v, want %v:\n%s", placed, c.failing, body)
}
if _, other := h.m.pinModfile(filepath.Join(h.m.Work, res.Workspace, "tree", "other")); other {
t.Error("a go.mod that requires no asked package was handed a modfile")
}
})
}
// The order of Go versions: a pseudo-version by its time, a release after its pre-releases, a non-version first.
for _, v := range []struct {
a, b string
want int
}{
{"v0.1.15-0.20261101000000-aaaaaaaaaaaa", "v0.1.15-0.20261022120000-0123456789ab", 1},
{"v0.1.14-0.20261031084708-acbf80ad910a", "v0.1.14-0.20261101005726-af4e4e60a3a0", -1},
{"v0.1.7", "v0.1.14-0.20261101005726-af4e4e60a3a0", -1},
{"v0.1.14", "v0.1.14-0.20261101005726-af4e4e60a3a0", 1},
{"v0.2.0", "v0.2.0", 0},
{"main", "v0.0.1", -1},
} {
if got := compareVersions(v.a, v.b); (got > 0) != (v.want > 0) || (got < 0) != (v.want < 0) {
t.Errorf("compareVersions(%s, %s) = %d, want the sign of %d", v.a, v.b, got, v.want)
}
}
}