Judge a pull request into a release branch by that branch's controller and repositories
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head 4a75cc51dda6 merged into trunk 535c7641ac41 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 4a75cc51dda6 merged into trunk 535c7641ac41 — pass: its merge-check.sh passed
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head 4a75cc51dda6 merged into trunk 535c7641ac41 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head 4a75cc51dda6 merged into trunk 535c7641ac41 — pass: its merge-check.sh passed
A release branch could never carry a change that needs another repository's release-branch change: its pull requests were judged by the running controller and against the other repositories' main (hq issue 704, ADR 0358). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
)
|
||||
|
||||
// **A pull request into a release branch is judged by that branch** (novox/hq ADR 0358, issue 704): a base that is
|
||||
// not its repository's trunk, where mesh-controller has a branch of that name, is a release branch; the check then
|
||||
// reads every repository beside it at its branch of that name where it has one, else at its trunk. Any other base is
|
||||
// checked exactly as before.
|
||||
|
||||
const aRelease = "feat/57-config-and-provisioning"
|
||||
|
||||
// theCore is the mesh's core as a check reads it beside: the controller, the node-engine, the tool runner and the
|
||||
// catalogue's bus, on the git seat.
|
||||
func theCore() []inventory.Entry {
|
||||
onSeat := func(module, repository string) inventory.Entry {
|
||||
e := fromRepo(module, repository, "")
|
||||
e.Source.Seat = "git"
|
||||
return e
|
||||
}
|
||||
nats := onSeat("nats", "novox/mesh-catalog")
|
||||
nats.Source.Path = "modules/nats"
|
||||
return []inventory.Entry{onSeat("mesh-controller", "novox/mesh-controller"), onSeat("mesh-host", "novox/mesh-host"),
|
||||
onSeat("node-tools", "novox/mesh-tools"), nats, onSeat("gitea", "novox/mesh-catalog")}
|
||||
}
|
||||
|
||||
// aForgeWithBranches stands in for the forge: which branches each repository has, and how often it was asked.
|
||||
func aForgeWithBranches(t *testing.T, branches map[string][]string, failing error) *int {
|
||||
t.Helper()
|
||||
asked := 0
|
||||
was := forgeHasBranch
|
||||
t.Cleanup(func() { forgeHasBranch = was })
|
||||
forgeHasBranch = func(_ context.Context, owner, repo, branch string) (bool, error) {
|
||||
asked++
|
||||
if failing != nil {
|
||||
return false, failing
|
||||
}
|
||||
for _, b := range branches[owner+"/"+repo] {
|
||||
if b == branch {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return &asked
|
||||
}
|
||||
|
||||
func TestABaseIsAReleaseBranchWhenTheControllerHasABranchOfThatName(t *testing.T) {
|
||||
entries := theCore()
|
||||
scopeOf := func(p link.PullUpdated) checkScope { return pullScope(p, entries, nil, nil) }
|
||||
pull := func(repo, base string) link.PullUpdated {
|
||||
return link.PullUpdated{Owner: "novox", Repo: repo, Number: 286, Base: base, Commit: "abc",
|
||||
Paths: []string{"modules/gitea/index.ts"}}
|
||||
}
|
||||
|
||||
asked := aForgeWithBranches(t, map[string][]string{"novox/mesh-controller": {"main", aRelease}}, nil)
|
||||
for _, c := range []struct {
|
||||
what string
|
||||
p link.PullUpdated
|
||||
release string
|
||||
asks int
|
||||
}{
|
||||
{"a pull request into the trunk asks nothing and is no release", pull("mesh-catalog", "main"), "", 0},
|
||||
{"a catalogue pull request into the release branch", pull("mesh-catalog", aRelease), aRelease, 1},
|
||||
{"a controller pull request into the release branch", pull("mesh-controller", aRelease), aRelease, 1},
|
||||
{"a base mesh-controller has no branch of is checked as today", pull("mesh-catalog", "feat/other"), "", 1},
|
||||
{"no base: as today", pull("mesh-catalog", ""), "", 0},
|
||||
} {
|
||||
*asked = 0
|
||||
got, err := releaseBranchOf(t.Context(), c.p, scopeOf(c.p), entries)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", c.what, err)
|
||||
}
|
||||
if got != c.release || *asked != c.asks {
|
||||
t.Errorf("%s: release %q after %d question(s) to the forge, not %q after %d", c.what, got, *asked,
|
||||
c.release, c.asks)
|
||||
}
|
||||
}
|
||||
|
||||
// The branch a module built from the repository follows is its trunk: never asked about.
|
||||
photos := fromRepo("photos", "novox/photos", "")
|
||||
photos.Source.Ref = "nox-mesh"
|
||||
withPhotos := append(theCore(), photos)
|
||||
p := link.PullUpdated{Owner: "novox", Repo: "photos", Base: "nox-mesh", Commit: "abc", Paths: []string{"x.js"}}
|
||||
*asked = 0
|
||||
if got, err := releaseBranchOf(t.Context(), p, pullScope(p, withPhotos, nil, nil), withPhotos); err != nil ||
|
||||
got != "" || *asked != 0 {
|
||||
t.Fatalf("a module's own trunk read as release %q (%v) after %d question(s)", got, err, *asked)
|
||||
}
|
||||
|
||||
// A forge that cannot answer is an error, never "no release branch".
|
||||
aForgeWithBranches(t, nil, errors.New("nothing serves gitea.gitea_api"))
|
||||
q := pull("mesh-catalog", aRelease)
|
||||
if _, err := releaseBranchOf(t.Context(), q, scopeOf(q), entries); err == nil ||
|
||||
!strings.Contains(err.Error(), "cannot be told") {
|
||||
t.Fatalf("an unanswered forge was read as an answer: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBesideAReleaseBranchCheckEachRepositoryIsReadAtThatBranchElseItsTrunk(t *testing.T) {
|
||||
entries := theCore()
|
||||
clone := func(s inventory.Source) (string, error) { return "http://git/" + s.Repository + ".git", nil }
|
||||
running := func(module string) string { return "running-" + module }
|
||||
|
||||
beside, err := besideOf(entries, running, clone, aRelease)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
at := func(repo string, ref, orElse string) link.CheckedOut {
|
||||
return link.CheckedOut{Repository: "http://git/novox/" + repo + ".git", Ref: ref, Else: orElse}
|
||||
}
|
||||
want := map[string]link.CheckedOut{
|
||||
// The judge: mesh-controller's release branch, which it has.
|
||||
"mesh-controller": at("mesh-controller", aRelease, ""),
|
||||
"mesh-controller-main": at("mesh-controller", "main", ""),
|
||||
// A controller pull request into the release branch reads mesh-host at that branch where it has one.
|
||||
"mesh-host": at("mesh-host", aRelease, "main"),
|
||||
"mesh-tools": at("mesh-tools", aRelease, "main"),
|
||||
"mesh-catalog": at("mesh-catalog", aRelease, "main"),
|
||||
"mesh-lab": at("mesh-lab", aRelease, "main"),
|
||||
"mesh-sdk": at("mesh-sdk", aRelease, "main"),
|
||||
}
|
||||
if !reflect.DeepEqual(beside, want) {
|
||||
t.Fatalf("beside a release-branch check:\n got %v\nwant %v", beside, want)
|
||||
}
|
||||
|
||||
// Any other base: exactly as before — what the mesh runs, the catalogue's and the lab's main.
|
||||
beside, err = besideOf(entries, running, clone, "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want = map[string]link.CheckedOut{
|
||||
"mesh-controller": at("mesh-controller", "running-mesh-controller", ""),
|
||||
"mesh-controller-main": at("mesh-controller", "main", ""),
|
||||
"mesh-host": at("mesh-host", "running-mesh-host", ""),
|
||||
"mesh-tools": at("mesh-tools", "running-node-tools", ""),
|
||||
"mesh-catalog": at("mesh-catalog", "main", ""),
|
||||
"mesh-lab": at("mesh-lab", "main", ""),
|
||||
"mesh-sdk": at("mesh-sdk", sdkPinned(), ""),
|
||||
}
|
||||
if !reflect.DeepEqual(beside, want) {
|
||||
t.Fatalf("beside a trunk check:\n got %v\nwant %v", beside, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAReleaseBranchVerdictNamesTheBranchTheJudgeAndWhatItRead(t *testing.T) {
|
||||
asked := &link.CheckRequest{Owner: "novox", Repo: "mesh-catalog", Number: 286, Base: aRelease, Release: aRelease}
|
||||
c := checkedOf(link.BuildResult{ID: "b", Ref: "aaaaaaaaaaaa0000", Checked: asked, Check: &link.CheckOutcome{
|
||||
Verdict: "pass", Head: "aaaaaaaaaaaa", Trunk: "111111111111", Gate: &link.CheckLayer{Verdict: "pass"},
|
||||
RepoCheck: &link.CheckLayer{Verdict: "pass"}, Release: aRelease, JudgedBy: "fcdecb436117",
|
||||
Beside: map[string]string{"mesh-host": "222222222222", "mesh-lab": "333333333333"},
|
||||
BesideAt: map[string]string{"mesh-host": aRelease, "mesh-lab": "main"}}})
|
||||
if c.Release != aRelease || c.JudgedBy != "fcdecb436117" {
|
||||
t.Fatalf("the verdict names release %q judged by %q", c.Release, c.JudgedBy)
|
||||
}
|
||||
if c.BesideAt["mesh-host"] != aRelease || c.BesideAt["mesh-lab"] != "main" || c.Beside["mesh-host"] != "222222222222" {
|
||||
t.Fatalf("the verdict names what it read beside it as %v at %v", c.Beside, c.BesideAt)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user