mesh/merge-gate out of date: judged against trunk c975dbf7b053, the trunk is now 76304d9db817; judged again on a recheck or when it is merged
mesh/repo-check out of date: judged against trunk c975dbf7b053, the trunk is now 76304d9db817; judged again on a recheck or when it is merged
mesh/delivery superseded: a newer head of the same pull request
Review of #329: a judge built with HOME in the workspace runs git under a configuration a pull request's script can plant, so a hook could rewrite the judge's sources before they compile and the result be kept under an honest key (issue 482, issue 704). Every judge now builds with its own HOME, configuration and Go cache outside the workspace, no git configuration and no VCS stamping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
274 lines
12 KiB
Go
274 lines
12 KiB
Go
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 its trunk, always: its replays run with the container runtime's socket.
|
|
"mesh-lab": at("mesh-lab", "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"},
|
|
Validator: "mesh-host's validator of " + aRelease + " at 222222222222"}})
|
|
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" ||
|
|
c.Validator != "mesh-host's validator of "+aRelease+" at 222222222222" {
|
|
t.Fatalf("the verdict names what it read beside it as %v at %v", c.Beside, c.BesideAt)
|
|
}
|
|
}
|
|
|
|
// **Clause 5** (ADR 0358 §5): a release branch's own pull request into its trunk is no release-branch check: the forge
|
|
// is asked nothing, the request names no release branch, and the repositories beside it are read as for any trunk
|
|
// pull request. Its delivery group's composed check is asked as for any other (askGroupCheck, unchanged).
|
|
func TestAReleaseBranchsPullRequestIntoItsTrunkIsCheckedAsAnyTrunkPullRequest(t *testing.T) {
|
|
entries := theCore()
|
|
asked := aForgeWithBranches(t, map[string][]string{"novox/mesh-controller": {"main", aRelease}}, nil)
|
|
for _, repo := range []string{"mesh-catalog", "mesh-controller", "mesh-host"} {
|
|
p := link.PullUpdated{Owner: "novox", Repo: repo, Number: 400, Head: aRelease, Base: "main", Commit: "abc",
|
|
Paths: []string{"modules/gitea/index.ts"}}
|
|
got, err := releaseBranchOf(t.Context(), p, pullScope(p, entries, nil, nil), entries)
|
|
if err != nil || got != "" || *asked != 0 {
|
|
t.Fatalf("%s's release branch into main read as release %q (%v) after %d question(s)", repo, got, err, *asked)
|
|
}
|
|
}
|
|
clone := func(s inventory.Source) (string, error) { return "http://git/" + s.Repository + ".git", nil }
|
|
beside, err := besideOf(entries, func(module string) string { return "running-" + module }, clone, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for dir, b := range beside {
|
|
if b.Ref == aRelease || b.Else != "" {
|
|
t.Fatalf("beside a trunk pull request %s is read at %+v", dir, b)
|
|
}
|
|
}
|
|
}
|
|
|
|
// **A forge answer that names no branch is an error**, never "no release branch"; a 404 is "no such branch".
|
|
func TestTheForgesAnswerAboutABranchIsReadStrictly(t *testing.T) {
|
|
for _, c := range []struct {
|
|
what string
|
|
answer link.Answer
|
|
has bool
|
|
fails bool
|
|
}{
|
|
{"the branch named", link.Answer{Result: []byte(`{"result":{"name":"` + aRelease + `"}}`)}, true, false},
|
|
{"not found", link.Answer{Error: "Gitea API /repos/novox/mesh-controller/branches/x: 404 {\"message\":\"not found\"}"},
|
|
false, false},
|
|
{"an empty branch name", link.Answer{Result: []byte(`{"result":{"name":""}}`)}, false, true},
|
|
{"no result", link.Answer{Result: []byte(`{}`)}, false, true},
|
|
{"another branch named", link.Answer{Result: []byte(`{"result":{"name":"main"}}`)}, false, true},
|
|
{"unreadable", link.Answer{Result: []byte(`not json`)}, false, true},
|
|
{"another error", link.Answer{Error: "Gitea API: 500 internal"}, false, true},
|
|
} {
|
|
has, err := branchAnswered(c.answer, aRelease)
|
|
if has != c.has || (err != nil) != c.fails {
|
|
t.Errorf("%s: has %v, error %v", c.what, has, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// **The trunk is the repository's own, not a name**: a repository whose trunk is not main, one that follows main, and
|
|
// a controller whose trunk is not main.
|
|
func TestATrunkIsTheBranchTheRepositoryFollows(t *testing.T) {
|
|
asked := aForgeWithBranches(t, map[string][]string{"novox/mesh-controller": {"main", aRelease, "master"}}, nil)
|
|
legacy := fromRepo("legacy", "novox/legacy", "")
|
|
legacy.Source.Ref = "master"
|
|
onMain := fromRepo("photos", "novox/photos", "")
|
|
onMain.Source.Ref = "main"
|
|
entries := append(theCore(), legacy, onMain)
|
|
for _, c := range []struct {
|
|
what, repo, base, release string
|
|
asks int
|
|
}{
|
|
{"a repository whose trunk is master, into master", "legacy", "master", "", 0},
|
|
{"a repository whose trunk is master, into the release branch", "legacy", aRelease, aRelease, 1},
|
|
{"a repository whose module follows main, into main", "photos", "main", "", 0},
|
|
{"a repository whose module follows main, into the release branch", "photos", aRelease, aRelease, 1},
|
|
} {
|
|
*asked = 0
|
|
p := link.PullUpdated{Owner: "novox", Repo: c.repo, Base: c.base, Commit: "abc", Paths: []string{"x"}}
|
|
got, err := releaseBranchOf(t.Context(), p, pullScope(p, entries, nil, nil), entries)
|
|
if err != nil || got != c.release || *asked != c.asks {
|
|
t.Errorf("%s: release %q (%v) after %d question(s), not %q after %d", c.what, got, err, *asked, c.release, c.asks)
|
|
}
|
|
}
|
|
|
|
// A controller whose trunk is not main: main may be a release branch there, and its own trunk never is.
|
|
core := theCore()
|
|
core[0].Source.Ref = "trunk"
|
|
for _, c := range []struct {
|
|
base, release string
|
|
asks int
|
|
}{{"trunk", "", 0}, {"main", "main", 1}} {
|
|
*asked = 0
|
|
p := link.PullUpdated{Owner: "novox", Repo: "hq", Base: c.base, Commit: "abc", Paths: []string{"x"}}
|
|
got, err := releaseBranchOf(t.Context(), p, pullScope(p, core, nil, nil), core)
|
|
if err != nil || got != c.release || *asked != c.asks {
|
|
t.Errorf("hq into %s, the controller following trunk: release %q (%v) after %d question(s)", c.base, got, err,
|
|
*asked)
|
|
}
|
|
}
|
|
}
|
|
|
|
// trunkOf: the branch a source follows, and main when it records none or records a commit (issue 215's rule).
|
|
func TestATrunkIsMainWhenTheSourceRecordsNone(t *testing.T) {
|
|
for ref, want := range map[string]string{"": "main", "main": "main", "master": "master", "nox-mesh": "nox-mesh",
|
|
"0123456789abcdef0123456789abcdef01234567": "main", " ": "main"} {
|
|
if got := trunkOf(inventory.Source{Ref: ref}); got != want {
|
|
t.Errorf("a source recording %q follows %q, not %q", ref, got, want)
|
|
}
|
|
}
|
|
}
|