diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/forge.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/forge.go index 79b4345..2a802eb 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/forge.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/forge.go @@ -13,16 +13,9 @@ import ( "mesh-delivery-checker/internal/check" ) -// The forge's tools the checker reads with (mesh-catalog modules/gitea): gitea_get_file answers {file: {content}}, the text -// of a file at a ref, and gitea_compare answers {total_commits, commits}, what head holds that base does not. The checker -// writes nothing to the forge: the forge bridge sets the statuses (ADR 0484 clause 2). -const ( - getFileTool = "gitea.gitea_get_file" - compareTool = "gitea.gitea_compare" - commitsTool = "gitea.gitea_commits" - // gitea_list_branches answers {branches: [{name, commit}]}, every branch with the commit it points at. - branchesTool = "gitea.gitea_list_branches" -) +// The forge's tool the checker reads with (mesh-catalog modules/gitea): gitea_get_file answers {file: {content}}, the text +// of a file at a ref. The checker writes nothing to the forge: the forge bridge sets the statuses (ADR 0484 clause 2). +const getFileTool = "gitea.gitea_get_file" // notFoundStatus is the 404 of the forge's error, `Gitea API : `. var notFoundStatus = regexp.MustCompile(`: 404( |$)`) @@ -69,61 +62,3 @@ func (f forgeReads) File(_ context.Context, repository, path, ref string) (strin } return *a.File.Content, nil } - -// OnTrunk says whether the commit is already on the repository's main: main holds it when the commit holds nothing main does -// not. -func (f forgeReads) OnTrunk(_ context.Context, repository, commit string) (bool, error) { - owner, repo := ownerRepo(repository) - raw, err := f.ask(compareTool, map[string]string{"owner": owner, "repo": repo, "base": "main", "head": commit}) - if err != nil { - return false, err - } - var a struct { - Total *int `json:"total_commits"` - } - if err := json.Unmarshal(raw, &a); err != nil || a.Total == nil { - return false, fmt.Errorf("the answer of %s is not {total_commits}: %v", compareTool, err) - } - return *a.Total == 0, nil -} - -// FullSHA is the whole sha of a commit named by its first digits: the newest commit of the history at that ref. -func (f forgeReads) FullSHA(_ context.Context, repository, commit string) (string, error) { - owner, repo := ownerRepo(repository) - raw, err := f.ask(commitsTool, map[string]any{"owner": owner, "repo": repo, "ref": commit, "limit": 1}) - if err != nil { - return "", err - } - var a struct { - Commits []struct { - SHA string `json:"sha"` - } `json:"commits"` - } - if err := json.Unmarshal(raw, &a); err != nil || len(a.Commits) == 0 || len(a.Commits[0].SHA) != 40 || !strings.HasPrefix(a.Commits[0].SHA, commit) { - return "", fmt.Errorf("the answer of %s for %s is not a commit that starts with it: %v", commitsTool, commit, err) - } - return a.Commits[0].SHA, nil -} - -// BranchHeads is the commit each branch of the repository points at. -func (f forgeReads) BranchHeads(_ context.Context, repository string) (map[string]string, error) { - owner, repo := ownerRepo(repository) - raw, err := f.ask(branchesTool, map[string]string{"owner": owner, "repo": repo}) - if err != nil { - return nil, err - } - var a struct { - Branches *[]struct { - Name string `json:"name"` - Commit string `json:"commit"` - } `json:"branches"` - } - if err := json.Unmarshal(raw, &a); err != nil || a.Branches == nil { - return nil, fmt.Errorf("the answer of %s is not {branches: [{name, commit}]}: %v", branchesTool, err) - } - heads := map[string]string{} - for _, b := range *a.Branches { - heads[b.Name] = b.Commit - } - return heads, nil -} diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/forge_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/forge_test.go index d6487d6..c60da5c 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/forge_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/forge_test.go @@ -29,49 +29,19 @@ func TestTheForgeReadsParseWhatTheForgeAnswered(t *testing.T) { // 1624/AC4 f := forgeReads{call: func(key string, body any) (json.RawMessage, error) { b, _ := json.Marshal(body) asked = append(asked, key+" "+string(b)) - switch { - case key == getFileTool: - return capturedAnswer(t, "forge-get-file-go-mod"), nil - case strings.Contains(string(b), "cb54756c3865"): - return capturedAnswer(t, "forge-compare-trunk"), nil - } - return capturedAnswer(t, "forge-compare-branch"), nil + return capturedAnswer(t, "forge-get-file-go-mod"), nil }} text, err := f.File(context.Background(), "novox/mesh-delivery", "modules/mesh-delivery-builder/go.mod", "main") if err != nil || !strings.Contains(text, "git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021100209-cb54756c3865") { t.Errorf("file = %q, %v", text, err) } - if on, err := f.OnTrunk(context.Background(), "novox/mesh-sdk", "cb54756c3865"); err != nil || !on { - t.Errorf("a commit on the trunk: %v, %v", on, err) - } - if on, err := f.OnTrunk(context.Background(), "novox/mesh-sdk", "9aaa5c80ac5c"); err != nil || on { - t.Errorf("a commit of the branch: %v, %v", on, err) - } - if len(asked) != 3 || !strings.Contains(asked[0], `"owner":"novox"`) || !strings.Contains(asked[2], `"base":"main"`) { + if len(asked) != 1 || !strings.Contains(asked[0], `"owner":"novox"`) { t.Errorf("asked %v", asked) } bad := forgeReads{call: func(string, any) (json.RawMessage, error) { return json.RawMessage(`{}`), nil }} if _, err := bad.File(context.Background(), "novox/x", "go.mod", "main"); err == nil { t.Error("an answer with no file was taken") } - if _, err := bad.OnTrunk(context.Background(), "novox/x", "abc"); err == nil { - t.Error("an answer with no total was taken") - } -} - -func TestTheForgeNamesTheWholeShaOfAShortCommit(t *testing.T) { // review D2 - f := forgeReads{call: func(key string, body any) (json.RawMessage, error) { - if key != commitsTool { - t.Errorf("called %s", key) - } - return capturedAnswer(t, "forge-commits"), nil - }} - if sha, err := f.FullSHA(context.Background(), "novox/mesh-sdk", "cb54756c3865"); err != nil || sha != "cb54756c3865df5ac9c0dc0481ad9ebe2bf013a1" { - t.Errorf("sha = %q, %v", sha, err) - } - if _, err := f.FullSHA(context.Background(), "novox/mesh-sdk", "deadbeefdead"); err == nil { - t.Error("a commit that does not start with the pin was taken") - } } func TestAFileTheRefDoesNotHoldIsNotFoundAndOtherFailuresAreNot(t *testing.T) { // 1845/AC1, AC4 @@ -96,10 +66,4 @@ func TestAForgeReadThatReachesNobodyIsUnheard(t *testing.T) { // 1624 C7: "nats: if !errors.As(err, &u) { t.Errorf("err = %v, want it marked as reaching nobody", err) } - if _, err := f.OnTrunk(context.Background(), "novox/mesh-sdk", "abc"); !errors.As(err, &u) { - t.Errorf("compare: %v", err) - } - if _, err := f.FullSHA(context.Background(), "novox/mesh-sdk", "abc"); !errors.As(err, &u) { - t.Errorf("commits: %v", err) - } } diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go index a3bf800..03db849 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/integration_test.go @@ -687,18 +687,17 @@ func TestACheckLeftRunningIsTakenOverWhenTheCheckerStarts(t *testing.T) { // N6/ } } -// TestAMemberNamingAMeshSDKCommitOffTheTrunkFailsNamingTheCommit: the go.mod the forge answers names a commit that main does not -// hold; the verdict fails naming the member and the commit and no test runs (ADR 0484 clause 9). -func TestAMemberNamingAMeshSDKCommitOffTheTrunkFailsNamingTheCommit(t *testing.T) { // 1624/AC2, 1929/AC3 +// TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused: the go.mod the forge answers names a commit that main does +// not hold; the check is not refused for it, since the build takes the latest published mesh-sdk whatever go.mod names +// (novox/hq ADR 0573 §2, §4; issue 1585 S14, which retires issue 1624's pin check). +func TestAMemberNamingAMeshSDKCommitOffTheTrunkIsJudgedAndNotRefused(t *testing.T) { // 1624/AC2 as ADR 0573 §4 changes it b := startBed(t) b.stalePin.Store(true) key := b.candidate(t, "feat/x", pull(repo, 7, head1)) b.start(t, nil) - v := b.verdict(t, key, ended) - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "9aaa5c80ac5c") || !strings.Contains(v.Failed.Words, repo+"#7") { + if v := b.verdict(t, key, ended); v.State != delivery.VerdictPass { t.Fatalf("verdict = %+v", v) } - // 1929/AC3: no assertion that no test ran: one ask runs fetch, digest and tests (ADR 0525 §3). } // triesStore is the tries bucket as the tools write it. diff --git a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go index 8442c7d..f5243b6 100644 --- a/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go +++ b/modules/mesh-delivery-checker/cmd/mesh-delivery-checker/judge.go @@ -69,7 +69,7 @@ func newJudge(d judgeDeps) check.Judge { nodes = d.nodes } return &check.Examiner{Nodes: nodes, Compositions: d.compositions, Asks: d.asks, Toolchains: d.tools, - Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Packages: d.repos, Forge: d.forge, Runs: d.repos, Builds: d.builds, Announce: d.announce, Trunks: d.trunks, Manifests: d.repos, Logf: d.logf} + Kept: check.Kept{Store: d.kept}, Previews: d.previews, Modules: d.repos, Packages: d.repos, Published: d.repos, Forge: d.forge, Runs: d.repos, Builds: d.builds, Announce: d.announce, Trunks: d.trunks, Manifests: d.repos, Logf: d.logf} } // manifestKV reads a manifest by its digest. @@ -92,6 +92,7 @@ type repoModules struct { repoOf map[string]string // by module: its repository commit map[string]string // by module: the commit its record was computed at (the commit the mesh runs) typed map[string]string // by module: its manifest digest + uses map[string][]string // by module: the mesh packages it uses (Inputs2.Packages, ADR 0573 §2) } // Put takes a change of the indexer's modules bucket in: a module's repository and the languages its manifest names. @@ -100,6 +101,7 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted if r.repoOf == nil { r.repoOf, r.typed, r.langs, r.commit = map[string]string{}, map[string]string{}, map[string]map[string]bool{}, map[string]string{} r.tsPkgs, r.path, r.named = map[string][]string{}, map[string]string{}, map[string]map[string][]string{} + r.uses = map[string][]string{} } if deleted { delete(r.repoOf, key) @@ -107,6 +109,7 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted delete(r.commit, key) delete(r.path, key) delete(r.named, key) + delete(r.uses, key) r.mu.Unlock() return } @@ -121,6 +124,10 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted r.commit[key] = m.Commit r.path[key] = m.Path r.named[key] = m.ArtifactPaths + r.uses[key] = nil + if m.Inputs2 != nil { + r.uses[key] = m.Inputs2.Packages + } _, known := r.langs[m.Manifest] r.mu.Unlock() if m.Manifest == "" || known { @@ -152,6 +159,28 @@ func (r *repoModules) Put(ctx context.Context, key string, value []byte, deleted r.mu.Unlock() } +// LatestPackages is the latest published version of each mesh package a module of the mesh uses, and of mesh-sdk's Go +// module always (novox/hq ADR 0573 §2): the trunk merge of the module whose directory holds it, as the indexer's modules +// say. A package no module holds is left out: a check builds it at the version go.mod names. +func (r *repoModules) LatestPackages() delivery.PackageVersions { + r.mu.Lock() + defer r.mu.Unlock() + used := map[string]bool{check.SDKModulePath: true} + held := map[string]delivery.Module{} + for module, repo := range r.repoOf { + for _, p := range r.uses[module] { + used[p] = true + } + held[module] = delivery.Module{Repository: repo, Path: r.path[module], Commit: r.commit[module]} + } + pkgs := make([]string, 0, len(used)) + for p := range used { + pkgs = append(pkgs, p) + } + versions, _ := delivery.PackageVersionsOf(pkgs, held) + return versions +} + // PackagePaths are the paths, relative to the repository, that the TypeScript package artifacts of a repository's modules // are built from, sorted: the named paths the indexer keeps in each module's value (delivery.Module.ArtifactPaths; only the // indexer reads them from a manifest, the lead's decision L8), under the module's directory. None for a module whose value diff --git a/modules/mesh-delivery-checker/go.mod b/modules/mesh-delivery-checker/go.mod index 3cb7a51..9e6d299 100644 --- a/modules/mesh-delivery-checker/go.mod +++ b/modules/mesh-delivery-checker/go.mod @@ -3,7 +3,7 @@ module mesh-delivery-checker go 1.26.0 require ( - git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101013319-25a2cecd989c + git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101014012-a20bdf795236 github.com/nats-io/nats-server/v2 v2.11.17 github.com/nats-io/nats.go v1.54.0 ) diff --git a/modules/mesh-delivery-checker/go.sum b/modules/mesh-delivery-checker/go.sum index 4c2c5c5..add1f6c 100644 --- a/modules/mesh-delivery-checker/go.sum +++ b/modules/mesh-delivery-checker/go.sum @@ -6,6 +6,8 @@ git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101012031-7cab079e2ec3 h1:an2f0MLx git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101012031-7cab079e2ec3/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101013319-25a2cecd989c h1:rlUCZ5ifLojqDUTkcCCcPrugTzV2RT6F6JbIVTJkaC8= git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101013319-25a2cecd989c/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101014012-a20bdf795236 h1:JEkSnrmHs5QIRJGhtrghLC1+C0AxqMpef8a32d/BLPg= +git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261101014012-a20bdf795236/go.mod h1:ouERNpZizzwZ0HLEtAypsY9FR20FyUgUYRni6J7kVCY= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op h1:Z/MZK75wC/NSrkgqeNIa7jexam9uWzhLmFTSCPI/kn0= github.com/antithesishq/antithesis-sdk-go v0.7.0-default-no-op/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= github.com/google/go-tpm v0.9.8 h1:slArAR9Ft+1ybZu0lBwpSmpwhRXaa85hWtMinMyRAWo= diff --git a/modules/mesh-delivery-checker/internal/check/beside.go b/modules/mesh-delivery-checker/internal/check/beside.go index fd5ab99..43c0b8c 100644 --- a/modules/mesh-delivery-checker/internal/check/beside.go +++ b/modules/mesh-delivery-checker/internal/check/beside.go @@ -77,20 +77,6 @@ func (j *Examiner) besideFor(ctx context.Context, c *Case, p Pull) ([]BesideRef, // running commit puts the verdict out of date; at the trunk, a trunk move does (Checker.besideNow). out = append(out, BesideRef{Repository: repo, Commit: commit, atTrunk: atTrunk, runs: !atTrunk}) } - case repo == SDKRepository && p.Repository == controllerRepo && j.Forge != nil: - text, err := j.Forge.File(ctx, p.Repository, "go.mod", p.Head) - if err != nil { - return nil, fmt.Errorf("go.mod of %s#%d cannot be read to place the mesh-sdk its tests read: %w", p.Repository, p.Number, err) - } - pin, ok := SDKPin(text) - if !ok { - return nil, fmt.Errorf("go.mod of %s#%d names no mesh-sdk commit, so the mesh-sdk its tests read cannot be placed", p.Repository, p.Number) - } - full, err := j.Forge.FullSHA(ctx, SDKRepository, pin) - if err != nil { - return nil, fmt.Errorf("mesh-sdk %s, which %s#%d pins, cannot be found: %w", pin, p.Repository, p.Number, err) - } - out = append(out, BesideRef{Repository: repo, Commit: full}) default: if member := c.member(repo); member != nil { out = append(out, BesideRef{Repository: repo, Commit: member.Head, Ref: refOf(member.Number)}) diff --git a/modules/mesh-delivery-checker/internal/check/heard_test.go b/modules/mesh-delivery-checker/internal/check/heard_test.go deleted file mode 100644 index 3072d8c..0000000 --- a/modules/mesh-delivery-checker/internal/check/heard_test.go +++ /dev/null @@ -1,21 +0,0 @@ -package check - -import ( - "fmt" - "testing" - - "github.com/nats-io/nats.go" - - "git.novox.be/novox/mesh-sdk/go/delivery" -) - -// Issue 1820 AC5: a read of the forge that nobody answers is the forge's failure, not a build node's. (AC1-AC4 moved a -// step to another node; since R4g an ask whose node stops is delivered again by its stream: ADR 0525 §6.) -func TestAPinReadThatNobodyAnswersIsTheForgesFailureNotABuildNodes(t *testing.T) { // 1820/AC5 - b := groupBench(t, "cb54756c3865") - b.ex.Forge.(*fakeForge).err = fmt.Errorf("gitea.gitea_get_file: %w", nats.ErrNoResponders) - v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict - if v.State != delivery.VerdictFail || v.Failed.Machine != "forge" { - t.Errorf("verdict = %+v, want a failure at the forge: no build node was to blame", v) - } -} diff --git a/modules/mesh-delivery-checker/internal/check/judge.go b/modules/mesh-delivery-checker/internal/check/judge.go index 02affea..7478a7a 100644 --- a/modules/mesh-delivery-checker/internal/check/judge.go +++ b/modules/mesh-delivery-checker/internal/check/judge.go @@ -91,9 +91,14 @@ type Examiner struct { Modules BuildModules // Runs says which commit the mesh runs of a module (the indexer's modules); nil knows none. Runs interface{ CommitOf(module string) string } - // Forge reads the go.mod files of the Go members and says whether a mesh-sdk commit is on its trunk (ADR 0484 clause 9); - // nil checks no pin. + // Forge reads the forge's files for the siblings a repository's tests read; nil reads none. Forge Forge + // Published names the latest published version of every mesh package a module of the mesh uses, by package, from the + // indexer's modules (novox/hq ADR 0573 §2): a check builds against those, or against a member's head for a package + // its group publishes (§4), whatever go.mod names. Nil names none, and go.mod's versions are built. + Published interface { + LatestPackages() delivery.PackageVersions + } // Builds says how the build of a fingerprint the candidate carries stands (the builder's builds bucket); nil says nothing // of the builds. Builds BuildStates @@ -405,8 +410,8 @@ func (st *state) judgeAll(ctx context.Context) *delivery.Verdict { // need that is busy is waited for, and short enough that a mesh where no node has it says so. const DefaultDeclineBound = 30 * time.Minute -// ask puts the target's check ask, waits for the step of the node that pulled it and judges its answer: the fetch, the -// pins of its go.mod files, then each level's digest, kept verdicts and tests. When the answer leaves packages unmet, +// ask puts the target's check ask, waits for the step of the node that pulled it and judges its answer: the fetch, then +// each level's digest, kept verdicts and tests. When the answer leaves packages unmet, // their tests needing what that node lacks (issue 890), the passes are kept and the levels from the first such one are // asked again of a node that has it (placed). func (st *state) ask(ctx context.Context, t *target, merged *[]Fetched) *delivery.Verdict { @@ -442,6 +447,9 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l } a := delivery.StepAsk{ID: buildstep.NewID(), Kind: kind, Language: askLanguage[t.module], Asker: delivery.CheckerModule, At: st.j.now().UTC(), Check: job, Needs: needs} + if a.Language == delivery.AskGo { + a.Packages = st.packagesOf(p.Repository) + } var step delivery.Step var firstDecline time.Time for { @@ -498,7 +506,7 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l st.nodes[step.Node] = true st.c.Progress.Set(fmt.Sprintf("judge the check of %s#%d", p.Repository, p.Number), step.Node) if step.State != delivery.StepPassed { - return fail(step.Node, fmt.Sprintf("the check of %s#%d %s: %s%s", p.Repository, p.Number, step.State, step.Reason, pinHint(st.c, step.Reason))) + return fail(step.Node, fmt.Sprintf("the check of %s#%d %s: %s", p.Repository, p.Number, step.State, step.Reason)) } var answer delivery.CheckAnswer if err := json.Unmarshal(step.Result, &answer); err != nil { @@ -517,11 +525,8 @@ func (st *state) askNeeding(ctx context.Context, t *target, merged *[]Fetched, l if !changesBuildGo(p.Repository, t.changed) { t.runner = "" // the build module used no runner: the merge does not change it } - if !placed { // the placed ask fetched the same merge: its preview and pins were read from the first + if !placed { // the placed ask fetched the same merge: its preview was read from the first *merged = append(*merged, t.fetched) - if v := st.checkPinsOf(ctx, t); v != nil { - return v - } if v := st.checkBinariesOf(t); v != nil { return v } diff --git a/modules/mesh-delivery-checker/internal/check/pins.go b/modules/mesh-delivery-checker/internal/check/pins.go index 1de0a6e..c8db733 100644 --- a/modules/mesh-delivery-checker/internal/check/pins.go +++ b/modules/mesh-delivery-checker/internal/check/pins.go @@ -3,17 +3,17 @@ package check import ( "context" "errors" - "fmt" - "path" - "regexp" - "sort" - "strings" + "maps" + + "git.novox.be/novox/mesh-sdk/go/delivery" ) -// The pin of mesh-sdk in a Go member's go.mod (novox/hq ADR 0484 clause 9, ADR 0482 clause 2, issue 1624): a delivery group -// has one verdict, and the mesh-sdk its Go members are built and tested with is the group's: a pin to the head of the group's -// mesh-sdk pull request, or to a commit already on mesh-sdk's trunk. Any other commit is a stale or a foreign pin, and the -// group fails naming the member and the commit. +// **A check builds against the group's own package heads, not against what a member names** (novox/hq ADR 0573 §2, §4; +// issue 1585). Every Go check ask carries the version of each mesh package to build against (delivery.StepAsk.Packages): +// the head of the group's member that publishes it, else its latest published version as the indexer holds it. The build +// module writes them into the workspace, whatever version or commit a member's go.mod names, so no member is refused +// for naming no version of the group's package, an older head of it, a merge commit or a commit on no pull request: that +// refusal (issue 1624's pin check) was a fault of the check (§4), and is gone. // SDKRepository is the repository of mesh-sdk, and SDKModulePath its Go module. const ( @@ -21,142 +21,47 @@ const ( SDKModulePath = "git.novox.be/novox/mesh-sdk/go" ) -// Issue1148 is why a pin that only a head carries fails the check. -const Issue1148 = "issue 1148" - // ErrNotFound is what Forge.File wraps when the ref holds no such file (the forge answered 404): a file the ref does not hold, // told from a forge that could not be read (issue 1845). var ErrNotFound = errors.New("the ref holds no such file") -// Forge reads a file of a repository at a ref, and says whether a commit is on a repository's trunk. +// Forge reads a file of a repository at a ref. type Forge interface { // File is the text of a file at a ref (a branch, a tag or a commit). File(ctx context.Context, repository, path, ref string) (string, error) - // OnTrunk says whether commit (a full or a 12-digit sha) is already merged into the trunk of the repository: it holds no - // commit the trunk does not. - OnTrunk(ctx context.Context, repository, commit string) (bool, error) - // FullSHA is the 40-digit sha of a commit named by its first 12 digits. - FullSHA(ctx context.Context, repository, commit string) (string, error) - // BranchHeads is the commit every branch of a repository points at, by branch name (issue 1921). - BranchHeads(ctx context.Context, repository string) (map[string]string, error) } -var pseudoVersion = regexp.MustCompile(`-([0-9a-f]{12})$`) - -// SDKPin is the commit (12 hex digits) a go.mod names for mesh-sdk by a pseudo-version, or none for a go.mod that does not -// require mesh-sdk or requires a version that is no pseudo-version (a tag is a release the SDK itself names). -func SDKPin(goMod string) (commit string, ok bool) { - for _, line := range strings.Split(goMod, "\n") { - f := strings.Fields(strings.TrimSpace(strings.TrimPrefix(strings.TrimSpace(line), "require"))) - if len(f) >= 2 && f[0] == SDKModulePath { - if m := pseudoVersion.FindStringSubmatch(f[1]); m != nil { - return m[1], true - } - return "", false +// packagesOf is the version of each mesh package a Go check of repository builds against (ADR 0573 §2, §4): the latest +// published version of each package the mesh's modules use (Published), and for a package one of the case's pull +// requests publishes, that pull request's head (mesh-sdk's Go module whenever mesh-sdk is a member). A package of +// repository itself comes with the tree the check fetched, and is left out. Nil for none. +func (st *state) packagesOf(repository string) delivery.PackageVersions { + versions := delivery.PackageVersions{} + if st.j.Published != nil { + maps.Copy(versions, st.j.Published.LatestPackages()) + } + if st.c.member(SDKRepository) != nil { + if _, named := versions[SDKModulePath]; !named { + versions[SDKModulePath] = "" } } - return "", false -} - -// checkPinsOf refuses a Go member whose go.mod names a mesh-sdk commit that is neither on mesh-sdk's trunk nor the head of the -// group's mesh-sdk pull request. It reads each go.mod the fetch named from the forge: the one the check builds, which is the -// head's when the head holds the file and the trunk's when it does not, since a module added on the trunk after the branch was -// cut is in the merge and not at the head (issue 1845). It answers nil when every pin is the group's. -func (st *state) checkPinsOf(ctx context.Context, t *target) verdictPtr { - if st.j.Forge == nil { + for pkg := range versions { + repo, _, ok := delivery.PackageRepository(pkg) + if !ok || delivery.SameRepository(repo, repository) { + delete(versions, pkg) + continue + } + if m := st.c.member(repo); m != nil && len(m.Head) == 40 { + versions[pkg] = m.Head + } + if versions[pkg] == "" { + delete(versions, pkg) + } + } + if len(versions) == 0 { return nil } - sdk := st.c.member(SDKRepository) - if t.module != goModule || t.pull.Repository == SDKRepository { - return nil - } - { - for _, dir := range t.fetched.Modules { - file := path.Join(dir, "go.mod") - st.c.Progress.Set(fmt.Sprintf("read %s of %s#%d", file, t.pull.Repository, t.pull.Number), t.node) - text, err := st.j.Forge.File(ctx, t.pull.Repository, file, t.pull.Head) - if errors.Is(err, ErrNotFound) && t.pull.Trunk != "" { - // Only a file the head does not hold is read at the trunk; any other failure fails the check as it did. - text, err = st.j.Forge.File(ctx, t.pull.Repository, file, t.pull.Trunk) - } - if err != nil { - return failed(forgeMachine(err, t.node), fmt.Sprintf("%s#%d: %s cannot be read to check its mesh-sdk pin: %v%s", t.pull.Repository, t.pull.Number, file, err, unheardWords(err))) - } - pin, ok := SDKPin(text) - if !ok { - continue - } - if sdk != nil && strings.HasPrefix(sdk.Head, pin) { - continue // the group's own head - } - onTrunk, err := st.j.Forge.OnTrunk(ctx, SDKRepository, pin) - if err != nil { - return failed(forgeMachine(err, t.node), fmt.Sprintf("%s#%d: whether mesh-sdk %s is on its trunk cannot be told: %v%s", t.pull.Repository, t.pull.Number, pin, err, unheardWords(err))) - } - if onTrunk { - continue - } - if st.c.Try != nil { - // A try only (issue 1921): a repository tried on a branch that mesh-sdk shares by name (a milestone's branch across - // repositories) is built with that branch's mesh-sdk head, which no group pull request holds before the pull requests exist. - branch, err := st.sameBranchPin(ctx, t, pin) - if err != nil { - return failed(forgeMachine(err, t.node), fmt.Sprintf("%s#%d: whether mesh-sdk %s is the head of a branch cannot be told: %v%s", t.pull.Repository, t.pull.Number, pin, err, unheardWords(err))) - } - if branch != "" { - st.c.note(fmt.Sprintf("pin accepted: head of mesh-sdk %s", branch)) - continue - } - } - nor := "the group has no mesh-sdk pull request" - if sdk != nil { - nor = "the head of the group's mesh-sdk pull request is " + short12(sdk.Head) - } - return failed(t.node, fmt.Sprintf("%s#%d: %s names mesh-sdk %s, which is neither on mesh-sdk's trunk nor the group's head (%s)", t.pull.Repository, t.pull.Number, file, pin, nor)) - } - } - return nil -} - -// sameBranchPin names the branch of the tried repository that points at the tried head and that mesh-sdk also has, at the -// commit the pin names; empty when there is none. -func (st *state) sameBranchPin(ctx context.Context, t *target, pin string) (string, error) { - mine, err := st.j.Forge.BranchHeads(ctx, t.pull.Repository) - if err != nil { - return "", err - } - var same []string - for name, head := range mine { - if head == t.pull.Head && name != "main" { - same = append(same, name) - } - } - if len(same) == 0 { - return "", nil - } - sdk, err := st.j.Forge.BranchHeads(ctx, SDKRepository) - if err != nil { - return "", err - } - sort.Strings(same) - for _, name := range same { - if h, ok := sdk[name]; ok && strings.HasPrefix(h, pin) { - return name, nil - } - } - return "", nil -} - -// failureVerdict is a verdict of failure the check ends with. - -// pinHint is added to the failure of a Go member's fetch that names mesh-sdk while the group holds a mesh-sdk pull request: -// a pin to that pull request's head names a commit only the head carries, which go mod download cannot resolve before the -// pull request is merged (issue 1148), so such a check fails and never passes. -func pinHint(c *Case, fetchError string) string { - if c.member(SDKRepository) == nil || !strings.Contains(fetchError, "mesh-sdk") { - return "" - } - return " (a pin to the head of the group's mesh-sdk pull request names a commit only that head carries, which go mod download cannot resolve; " + Issue1148 + ")" + return versions } // unheardWords ends the words of a failed read of the forge that reached nobody: the forge was not heard (UnheardWords). diff --git a/modules/mesh-delivery-checker/internal/check/pins_test.go b/modules/mesh-delivery-checker/internal/check/pins_test.go index 727903d..878d720 100644 --- a/modules/mesh-delivery-checker/internal/check/pins_test.go +++ b/modules/mesh-delivery-checker/internal/check/pins_test.go @@ -1,214 +1,63 @@ package check import ( - "context" - "encoding/json" - "fmt" - "os" - "path/filepath" + "maps" "strings" "testing" + "git.novox.be/novox/mesh-sdk/go/buildstep" "git.novox.be/novox/mesh-sdk/go/delivery" ) -// fakeForge answers the forge's two reads from files and commits it is given. -type fakeForge struct { - goMods map[string]string // ` ` -> text - onTrunk map[string]bool // by commit prefix - err error - errAt map[string]error // by ref: a read at that ref fails with it - // branches are the branches of a repository with the commit each points at. - branches map[string]map[string]string +// published is the latest published version of each mesh package, as the indexer's modules say (Examiner.Published). +type published delivery.PackageVersions + +func (p published) LatestPackages() delivery.PackageVersions { + return maps.Clone(delivery.PackageVersions(p)) } -func (f *fakeForge) BranchHeads(_ context.Context, repository string) (map[string]string, error) { - if f.err != nil { - return nil, f.err - } - return f.branches[repository], nil -} - -func (f *fakeForge) File(_ context.Context, repository, path, ref string) (string, error) { - if f.err != nil { - return "", f.err - } - if e := f.errAt[ref]; e != nil { - return "", e - } - if s, ok := f.goMods[repository+" "+path+" "+ref]; ok { - return s, nil - } - return "", fmt.Errorf("no file %s of %s at %s: %w", path, repository, ref, ErrNotFound) -} - -func (f *fakeForge) FullSHA(_ context.Context, _ string, commit string) (string, error) { - if f.err != nil { - return "", f.err - } - return commit + strings.Repeat("0", 40-len(commit)), nil -} - -func (f *fakeForge) OnTrunk(_ context.Context, _ string, commit string) (bool, error) { - if f.err != nil { - return false, f.err - } - return f.onTrunk[commit[:12]], nil -} - -// capturedGoMod is the go.mod the forge answered (testdata/captured/forge-get-file-go-mod.json), pinned to the commit given. -func capturedGoMod(t *testing.T, pin string) string { +// packagesOfFetch is the package versions the fetch of repository was handed (delivery.StepAsk.Packages, issue 1585). +func packagesOfFetch(t *testing.T, b *bench, repository string) delivery.PackageVersions { t.Helper() - raw, err := os.ReadFile(filepath.Join("testdata", "captured", "forge-get-file-go-mod.json")) - if err != nil { - t.Fatal(err) + for _, f := range b.steps.tool("fetch") { + if f.args["repository"] == repository { + p, err := buildstep.PackagesArg(f.args["packages"]) + if err != nil { + t.Fatal(err) + } + return p + } } - var a struct{ File struct{ Content string } } - if err := json.Unmarshal(raw, &a); err != nil { - t.Fatal(err) - } - return strings.Replace(a.File.Content, "cb54756c3865", pin, 1) + t.Fatalf("%s was not fetched", repository) + return nil } -func TestTheSDKPinIsTheCommitOfAPseudoVersion(t *testing.T) { // 1624 - if c, ok := SDKPin(capturedGoMod(t, "cb54756c3865")); !ok || c != "cb54756c3865" { - t.Errorf("pin = %q, %v", c, ok) - } - if _, ok := SDKPin("module x\n\nrequire git.novox.be/novox/mesh-sdk/go v0.2.0\n"); ok { - t.Error("a tagged version is no commit") - } - if c, ok := SDKPin("module x\n\nrequire git.novox.be/novox/mesh-sdk/go v0.1.14-0.20261021100209-cb54756c3865\n"); !ok || c != "cb54756c3865" { - t.Errorf("a one-line require: %q, %v", c, ok) - } - if _, ok := SDKPin("module x\n\ngo 1.26\n"); ok { - t.Error("a go.mod that does not require mesh-sdk has a pin") - } -} - -// groupBench is a group of mesh-delivery#7 and the SDK's pull request 12, mesh-delivery's go.mod at its head naming pin. -func groupBench(t *testing.T, pin string, trunk ...string) *bench { +// ADR 0573 §4 (issue 1585 S14): a group's Go member is built against the head of the group's mesh-sdk member, whatever its +// go.mod names (an older head of it included, which issue 1624's pin check refused), and never refused for its pin; a +// pull request alone is built against the latest published version of each package, and its own repository's packages +// come with its tree. +func TestAGroupMemberIsBuiltAgainstTheSDKMembersHeadWhateverItsGoModNames(t *testing.T) { // 1624/AC2, AC4 as ADR 0573 §4 changes them + const contract = "git.novox.be/novox/mesh-delivery/contract" b := newBench(t) b.fetch.Modules = []string{"modules/m"} - forge := &fakeForge{goMods: map[string]string{repoA + " modules/m/go.mod " + head1: capturedGoMod(t, pin)}, onTrunk: map[string]bool{}} - for _, c := range trunk { - forge.onTrunk[c] = true - } - b.ex.Forge = forge - return b -} - -func TestAMemberPinnedToTheSDKMembersHeadPassesAndIsBuiltAgainstIt(t *testing.T) { // 1624/AC2, AC4 - b := groupBench(t, head2[:12]) + b.ex.Published = published{SDKModulePath: trunkS, contract: trunkA} v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict if v.State != delivery.VerdictPass { t.Fatalf("verdict = %+v", v) } - var besideSDK bool - for _, f := range b.steps.tool("fetch") { - if f.args["repository"] == repoA { - refs, _ := refsOf(f.args["beside"]), true - for _, r := range refs { - besideSDK = besideSDK || (r.Repository == repoSDK && r.Commit == head2) - } - } + if got := packagesOfFetch(t, b, repoA); len(got) != 1 || got[SDKModulePath] != head2 { + t.Errorf("the member's packages = %v, want mesh-sdk at the group's head %s alone (its own contract comes with its tree)", got, head2) } - if !besideSDK { + if s := besideOf(t, b, repoA)[repoSDK]; s.Commit != head2 { t.Error("mesh-delivery's tests were not given the SDK member at its head") } -} - -func TestAMemberPinnedToAnOlderHeadOfTheSDKMemberFailsNamingTheMemberAndTheCommit(t *testing.T) { // 1624/AC2, AC4 - b := groupBench(t, "9aaa5c80ac5c") // a commit of the SDK branch, not its head, not on the trunk - v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict - f := v.Failed - if v.State != delivery.VerdictFail || f.Machine != "shanks" || !strings.Contains(f.Words, repoA+"#7") || !strings.Contains(f.Words, "9aaa5c80ac5c") || - !strings.Contains(f.Words, "neither on mesh-sdk's trunk nor the group's head") || !strings.Contains(f.Words, short12(head2)) { - t.Fatalf("verdict = %+v", v) + b = newBench(t) + b.ex.Published = published{SDKModulePath: trunkS, contract: trunkA} + if v := b.judge(t, caseOf(t, pullOf("novox/mesh-catalog", 7, head1))).Verdict; v.State != delivery.VerdictPass { + t.Fatalf("a pull request alone: verdict = %+v", v) } - // Since R4g the levels run in the member's one check ask before its go.mod pins are judged (ADR 0525 §3); the verdict is - // still the pin's, whatever the levels said. -} - -func TestAMemberPinnedToACommitOnTheTrunkPasses(t *testing.T) { // 1624/AC2, AC4 - b := groupBench(t, "cb54756c3865", "cb54756c3865") - if v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict; v.State != delivery.VerdictPass { - t.Fatalf("verdict = %+v", v) - } - // A lone pull request of a Go member is held to the trunk alone. - b = groupBench(t, "9aaa5c80ac5c") - v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "the group has no mesh-sdk pull request") { - t.Errorf("a foreign pin with no SDK member: %+v", v) - } -} - -func TestAPinOnlyTheSDKHeadCarriesThatDownloadCannotResolveFailsNamingIssue1148(t *testing.T) { // 1624/AC5, ADR 0453 clause 12 - b := groupBench(t, head2[:12]) - b.steps.handlers["build_go_fetch"] = func(c stepCall) (any, error) { - if c.args["repository"] == repoA { - return nil, fmt.Errorf("go mod download: git.novox.be/novox/mesh-sdk/go@v0.1.14-0.20261021100209-%s: invalid version: unknown revision", head2[:12]) - } - return b.fetch, nil - } - v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "issue 1148") || !strings.Contains(v.Failed.Words, head2[:12]) { - t.Errorf("verdict = %+v, want a failure naming issue 1148 and the commit", v) - } -} - -func TestAPinThatCannotBeCheckedFailsAndNeverPasses(t *testing.T) { // ADR 0453 clause 12 - b := groupBench(t, "cb54756c3865") - b.ex.Forge.(*fakeForge).err = fmt.Errorf("the forge is down") - v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1), pullOf(repoSDK, 12, head2))).Verdict - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "the forge is down") { - t.Errorf("verdict = %+v", v) - } -} - -// trunkOnlyBench is a group of mesh-delivery#7 whose fetch holds two Go modules: modules/m, which the head holds, and -// modules/new, which only the trunk holds (added on the trunk after the branch was cut). The SDK pin of each go.mod is given. -func trunkOnlyBench(t *testing.T, headPin, trunkPin string) *bench { - b := newBench(t) - b.fetch.Modules = []string{"modules/m", "modules/new"} - forge := &fakeForge{goMods: map[string]string{ - repoA + " modules/m/go.mod " + head1: capturedGoMod(t, headPin), - repoA + " modules/m/go.mod " + trunkA: capturedGoMod(t, headPin), - repoA + " modules/new/go.mod " + trunkA: capturedGoMod(t, trunkPin), - }, onTrunk: map[string]bool{"cb54756c3865": true}} - b.ex.Forge = forge - return b -} - -func TestAModuleOnlyTheTrunkHoldsIsReadAtTheTrunkAndPasses(t *testing.T) { // 1845/AC1 - b := trunkOnlyBench(t, "cb54756c3865", "cb54756c3865") - if v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict; v.State != delivery.VerdictPass { - t.Fatalf("verdict = %+v", v) - } -} - -func TestAStalePinOfAModuleOnlyTheTrunkHoldsStillFailsNamingTheFileAndTheCommit(t *testing.T) { // 1845/AC2 - b := trunkOnlyBench(t, "cb54756c3865", "9aaa5c80ac5c") - v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, repoA+"#7") || !strings.Contains(v.Failed.Words, "modules/new/go.mod") || !strings.Contains(v.Failed.Words, "9aaa5c80ac5c") { - t.Fatalf("verdict = %+v", v) - } -} - -func TestAModuleTheHeadHoldsIsJudgedByTheHeadsPin(t *testing.T) { // 1845/AC3 - b := trunkOnlyBench(t, "9aaa5c80ac5c", "cb54756c3865") // the head's pin is stale, the trunk's go.mod of the same module is fine - b.ex.Forge.(*fakeForge).goMods[repoA+" modules/m/go.mod "+trunkA] = capturedGoMod(t, "cb54756c3865") - v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "modules/m/go.mod") || !strings.Contains(v.Failed.Words, "9aaa5c80ac5c") { - t.Fatalf("verdict = %+v", v) - } -} - -func TestAReadThatFailsForAnotherReasonStillFailsAndIsNotTriedAtTheTrunk(t *testing.T) { // 1845/AC4 - b := trunkOnlyBench(t, "cb54756c3865", "cb54756c3865") - b.ex.Forge.(*fakeForge).errAt = map[string]error{head1: fmt.Errorf("the forge answered 500")} - v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "modules/m/go.mod") || !strings.Contains(v.Failed.Words, "the forge answered 500") { - t.Fatalf("verdict = %+v", v) + if got := packagesOfFetch(t, b, "novox/mesh-catalog"); len(got) != 2 || got[SDKModulePath] != trunkS || got[contract] != trunkA { + t.Errorf("a pull request alone is built against %v, want every package at its latest published version", got) } } @@ -236,9 +85,6 @@ func TestTheSiblingsAreWhereTheTestsStateThem(t *testing.T) { // review D2, ADR b := newBench(t) b.ex.Trunks = trunksOf(t, map[string]string{controller: trunkA, catalogue: trunkS, host: trunkA2, SDKRepository: "9999999999999999999999999999999999999999"}) b.ex.Runs = runsAt{"mesh-host": hostRuns} - forge := &fakeForge{goMods: map[string]string{controller + " go.mod " + head1: capturedGoMod(t, "cb54756c3865"), - catalogue + " go.mod " + head2: "module git.novox.be/novox/mesh-catalog\n"}, onTrunk: map[string]bool{"cb54756c3865": true}} - b.ex.Forge = forge // A group of the controller and the catalogue: the controller's tests read the catalogue's trunk, not the group's head. out := b.judge(t, caseOf(t, pullOf(controller, 7, head1), pullOf(catalogue, 12, head2))) // ADR 0548 §6, issue 568's own example: the verdict names the node-engine at the commit the mesh runs, and mesh-sdk at the @@ -253,8 +99,10 @@ func TestTheSiblingsAreWhereTheTestsStateThem(t *testing.T) { // review D2, ADR if h := got[host]; h.Commit != hostRuns { t.Errorf("the node-engine beside the controller = %+v, want the commit the mesh runs %s", h, hostRuns) } - if s := got[SDKRepository]; s.Commit != "cb54756c3865"+strings.Repeat("0", 28) { - t.Errorf("mesh-sdk beside the controller = %+v, want the commit its go.mod pins, whole", s) + // ADR 0573 §2: the controller is built against mesh-sdk's latest published version, so its tests read that one too, + // not the commit its go.mod names. + if s := got[SDKRepository]; s.Commit != "9999999999999999999999999999999999999999" { + t.Errorf("mesh-sdk beside the controller = %+v, want its trunk", s) } } @@ -283,36 +131,12 @@ func TestTheNodeEnginesTestsHaveTheControllerBesideThem(t *testing.T) { } b = newBench(t) b.ex.Trunks = trunksOf(t, map[string]string{host: trunkA, controllerRepo: trunkA2, catalogueRepository: trunkS, SDKRepository: trunkS}) - b.ex.Forge = &fakeForge{goMods: map[string]string{controllerRepo + " go.mod " + head2: capturedGoMod(t, "cb54756c3865")}, - onTrunk: map[string]bool{"cb54756c3865": true}} b.judge(t, hostCase(t, pullOf(host, 5, head1), pullOf(controllerRepo, 9, head2))) if c := besideOf(t, b, host)[controllerRepo]; c.Commit != head2 || c.Ref != "refs/pull/9/head" { t.Errorf("the controller beside the node-engine in a group = %+v, want the member's head %s", c, head2) } } -func TestThePinOfTheControllerThatCannotBeReadFailsTheCheck(t *testing.T) { // review D2 - b := newBench(t) - b.ex.Trunks = trunksOf(t, map[string]string{"novox/mesh-controller": trunkA}) - b.ex.Forge = &fakeForge{goMods: map[string]string{}, onTrunk: map[string]bool{}} - v := b.judge(t, caseOf(t, pullOf("novox/mesh-controller", 7, head1))).Verdict - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "go.mod") { - t.Errorf("verdict = %+v", v) - } -} - -func TestAForgeThatIsNotHeardFailsAtTheForgeAndIsJudgedAgainWhenTheNodesChange(t *testing.T) { // 1624 C7 - b := newBench(t) - b.ex.Forge = &fakeForge{err: &Unheard{Err: fmt.Errorf("nats: no responders available for request")}} - v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict - if v.State != delivery.VerdictFail || v.Failed.Machine != "forge" { - t.Fatalf("verdict = %+v, want failed at the forge", v) - } - if !IsNodesFailure(v) { - t.Errorf("a forge that was not heard is not the change's failure: %+v is not judged again when the nodes change", v.Failed) - } -} - // 1887/A12: the change's own repository is placed beside it at the trunk it is merged onto, for every repository, so a // shrink-only list test reads the trunk's copy at $MESH_CHECK_BESIDE/. func TestTheChangesOwnRepositoryIsBesideItAtItsTrunk(t *testing.T) { @@ -376,44 +200,3 @@ func TestTheNodeEnginesCheckFailsWhereTheControllersTrunkIsNotKnown(t *testing.T t.Fatalf("verdict = %+v", v) } } - -// sameBranchBench is a try of mesh-delivery's feat/r6 at head1 whose go.mod pins the SDK at pin, with the SDK's feat/r6 at head2. -func sameBranchBench(t *testing.T, pin string) (*bench, *Case) { - b := groupBench(t, pin) - b.ex.Forge.(*fakeForge).branches = map[string]map[string]string{ - repoA: {"main": trunkA, "feat/r6": head1}, - repoSDK: {"main": "cb54756c3865" + strings.Repeat("0", 28), "feat/r6": head2}, - } - c := caseOf(t, pullOf(repoA, 7, head1)) - c.Try = &TryOptions{} - return b, c -} - -func TestATryPinnedToTheHeadOfTheSDKBranchOfTheTriedBranchPasses(t *testing.T) { // 1921/AC1 - b, c := sameBranchBench(t, head2[:12]) - if v := b.judge(t, c).Verdict; v.State != delivery.VerdictPass { - t.Fatalf("verdict = %+v", v) - } - if n := c.Notes(); len(n) != 1 || n[0] != "pin accepted: head of mesh-sdk feat/r6" { - t.Errorf("notes = %q", n) - } -} - -func TestATryPinnedToAnotherCommitIsStillRefused(t *testing.T) { // 1921/AC2 - b, c := sameBranchBench(t, "9aaa5c80ac5c") - v := b.judge(t, c).Verdict - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "9aaa5c80ac5c") || !strings.Contains(v.Failed.Words, "neither on mesh-sdk's trunk nor the group's head") { - t.Fatalf("verdict = %+v", v) - } - if len(c.Notes()) != 0 { - t.Errorf("notes = %q", c.Notes()) - } -} - -func TestAMergeCheckWithTheSameBranchPinIsStillRefusedWithoutAGroupPullRequest(t *testing.T) { // 1921/AC3 - b, _ := sameBranchBench(t, head2[:12]) - v := b.judge(t, caseOf(t, pullOf(repoA, 7, head1))).Verdict // no Try: a pull request's check - if v.State != delivery.VerdictFail || !strings.Contains(v.Failed.Words, "the group has no mesh-sdk pull request") { - t.Fatalf("verdict = %+v", v) - } -} diff --git a/modules/mesh-delivery-checker/internal/check/preview.go b/modules/mesh-delivery-checker/internal/check/preview.go index bc37e03..a3b185a 100644 --- a/modules/mesh-delivery-checker/internal/check/preview.go +++ b/modules/mesh-delivery-checker/internal/check/preview.go @@ -43,7 +43,7 @@ func PreviewOf(p Pull, fetched []Fetched) (delivery.Preview, bool) { if t.Tree == "" || !digest64.MatchString(t.Manifest) { continue } - mods[dir] = delivery.PreviewModule{Tree: t.Tree, Manifest: t.Manifest, Locks: t.Locks, Paths: t.Paths} + mods[dir] = delivery.PreviewModule{Tree: t.Tree, Manifest: t.Manifest, Locks: t.Locks, Paths: t.Paths, Packages: t.Packages} } } if merge == "" { diff --git a/modules/mesh-delivery-checker/internal/check/preview_named_test.go b/modules/mesh-delivery-checker/internal/check/preview_named_test.go index fccc274..ebd968a 100644 --- a/modules/mesh-delivery-checker/internal/check/preview_named_test.go +++ b/modules/mesh-delivery-checker/internal/check/preview_named_test.go @@ -12,12 +12,13 @@ func TestAPreviewCarriesTheNamedPathsObjectsOfAFetchsModuleTrees(t *testing.T) { var f Fetched // As buildstep.ModuleTrees answers a module naming its paths: the named tree and the objects. raw := `{"workspace":"w","merge":"6666666666666666666666666666666666666666","trees":{".":{"tree":"named:aa","manifest":"` + - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + `","paths":{"src":"1111111111111111111111111111111111111111"}}}}` + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + `","paths":{"src":"1111111111111111111111111111111111111111"},"packages":["git.novox.be/novox/mesh-sdk/go"]}}}` if err := json.Unmarshal([]byte(raw), &f); err != nil { t.Fatal(err) } pv, ok := PreviewOf(Pull{Repository: "novox/mesh-sdk", Head: "h", Trunk: "t"}, []Fetched{f}) - if !ok || !reflect.DeepEqual(pv.Modules["."].Paths, map[string]string{"src": "1111111111111111111111111111111111111111"}) || pv.Modules["."].Tree != "named:aa" { + if !ok || !reflect.DeepEqual(pv.Modules["."].Paths, map[string]string{"src": "1111111111111111111111111111111111111111"}) || pv.Modules["."].Tree != "named:aa" || + !reflect.DeepEqual(pv.Modules["."].Packages, []string{"git.novox.be/novox/mesh-sdk/go"}) { // and the packages it uses (issue 1585) t.Errorf("preview = %+v, want the module's named tree and its objects", pv) } } diff --git a/modules/mesh-delivery-checker/internal/check/results.go b/modules/mesh-delivery-checker/internal/check/results.go index 768e7c3..8f1cecb 100644 --- a/modules/mesh-delivery-checker/internal/check/results.go +++ b/modules/mesh-delivery-checker/internal/check/results.go @@ -22,6 +22,8 @@ type MergeTree struct { // Paths are, of a module whose every artifact names its paths, each named path's object; Tree is then their named // tree (mesh-sdk buildstep.ModuleTree, novox/hq ADR 0499 clause 2). Paths map[string]string `json:"paths,omitempty"` + // Packages are the mesh packages the directory's go.sum names (mesh-sdk buildstep.ModuleTree, novox/hq ADR 0573 §2). + Packages []string `json:"packages,omitempty"` } // UnmarshalJSON takes a string as the tree alone, or an object. diff --git a/modules/mesh-delivery-checker/internal/check/testdata/captured/README b/modules/mesh-delivery-checker/internal/check/testdata/captured/README index 253863c..7df6c1f 100644 --- a/modules/mesh-delivery-checker/internal/check/testdata/captured/README +++ b/modules/mesh-delivery-checker/internal/check/testdata/captured/README @@ -17,5 +17,3 @@ novox/mesh-delivery at main, and forge-compare-trunk.json the answer of gitea_co trunk: no commit main does not hold); both called through the mesh on 2026-10-21 for issue 1624. forge-compare-branch.json is the answer of the same call for a head that is not on the trunk (novox/mesh-sdk base main, head afbe371003a5, the branch feat/r4c5-sdk), shortened to two of its seven commits and one of its twenty-six files, with the head and the totals set to match. - -forge-commits.json is the answer of the forge's gitea_commits for novox/mesh-sdk, ref cb54756c3865, limit 1, called through the mesh on 2026-10-21 (review D2). diff --git a/modules/mesh-delivery-checker/internal/check/testdata/captured/forge-commits.json b/modules/mesh-delivery-checker/internal/check/testdata/captured/forge-commits.json deleted file mode 100644 index 813ec62..0000000 --- a/modules/mesh-delivery-checker/internal/check/testdata/captured/forge-commits.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "commits": [ - { - "author": "mesh-tools", - "date": "2026-10-21T10:02:09Z", - "sha": "cb54756c3865df5ac9c0dc0481ad9ebe2bf013a1", - "subject": "Merge pull request 'Give delivery.Module the toolchains its fingerprint was computed with, and '.' as a root module's path, so the builder builds a key only with its own toolchain (issue 1615)' (#55) from fix/1615-module-toolchains-root into main" - } - ], - "more": true, - "page": 1, - "ref": "cb54756c3865", - "repository": "novox/mesh-sdk" -}