mesh/merge-gate head de5292323f33 merged into trunk a47976be4e69 — pass: builds mesh-controller → novox; no bus step; every machine composes with the chang…
mesh/repo-check head de5292323f33 merged into trunk a47976be4e69 — pass: its merge-check.sh passed
mesh/delivery delivered
A recheck rebuilds the pull request without its head branch name, so the sibling lookup found no group and said nothing: mesh-host#114's recheck was judged without its sibling. With no branch name, the group is the one whose member_details hold the pull request; an older mesh-delivery says the siblings could not be learned. TestReplay867Recheck replays it. Co-Authored-By: Claude <noreply@anthropic.com>
249 lines
13 KiB
Go
249 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
"github.com/novox/mesh-controller/internal/link"
|
|
)
|
|
|
|
// **A delivery group's member is checked beside the other members' heads** (novox/hq ADR 0382 §1, §7, issue 867): the
|
|
// controller asks mesh-delivery's `groups` once for every check it asks, and names in the ask the group's other open
|
|
// pull requests into a base of the same name, in a repository the build seat reads beside it — mesh-lab never.
|
|
|
|
const (
|
|
headHost = "1111111111111111111111111111111111111111"
|
|
headController = "2222222222222222222222222222222222222222"
|
|
headSDK = "3333333333333333333333333333333333333333"
|
|
headLab = "4444444444444444444444444444444444444444"
|
|
headHq = "5555555555555555555555555555555555555555"
|
|
)
|
|
|
|
func theCoreOnTheGitSeat() []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"
|
|
held := catalogue.Manifest{Module: "mesh-delivery", Claims: []catalogue.Claim{{Name: catalogue.DeliverySeat, Scope: catalogue.ScopeMesh}}}
|
|
return []inventory.Entry{onSeat("mesh-controller", "novox/mesh-controller"), onSeat("mesh-host", "novox/mesh-host"),
|
|
onSeat("node-tools", "novox/mesh-tools"), nats, {Manifest: held, On: []string{"anchor"}}}
|
|
}
|
|
|
|
func besideTheCore() map[string]link.CheckedOut {
|
|
return map[string]link.CheckedOut{"mesh-controller": {Repository: "c"}, "mesh-host": {Repository: "h"},
|
|
"mesh-tools": {Repository: "t"}, "mesh-catalog": {Repository: "k"}, "mesh-sdk": {Repository: "s"}, "mesh-lab": {Repository: "l"}}
|
|
}
|
|
|
|
func member(repository string, number int, head, base string, open bool) memberLine {
|
|
return memberLine{ID: repository + "@" + head[:12], Repository: repository, State: "checked", Number: number, Head: head,
|
|
Base: base, Open: open}
|
|
}
|
|
|
|
func group(id string, members ...memberLine) groupsLine {
|
|
line := groupsLine{ID: id, Details: members}
|
|
for _, m := range members {
|
|
line.Members = append(line.Members, m.ID+" ("+m.State+")")
|
|
}
|
|
return line
|
|
}
|
|
|
|
func TestASiblingIsTheGroupsOtherOpenPullRequestIntoABaseOfTheSameName(t *testing.T) {
|
|
entries := theCoreOnTheGitSeat()
|
|
pull := link.PullUpdated{Owner: "novox", Repo: "mesh-host", Number: 114, Base: "main", Head: "feat/x", Commit: headHost}
|
|
for _, c := range []struct {
|
|
what string
|
|
lines []groupsLine
|
|
want []string
|
|
unknown string
|
|
}{
|
|
{"the other member, open, same base", []groupsLine{group("feat/x",
|
|
member("novox/mesh-host", 114, headHost, "main", true), member("novox/mesh-controller", 346, headController, "main", true))},
|
|
[]string{"mesh-controller=novox/mesh-controller#346@" + headController + " onto main"}, ""},
|
|
{"a member into a base of another name is not one", []groupsLine{group("feat/x",
|
|
member("novox/mesh-host", 114, headHost, "main", true), member("novox/mesh-controller", 346, headController, "release", true))},
|
|
nil, ""},
|
|
{"a member not open (merged or closed) is not one", []groupsLine{group("feat/x",
|
|
member("novox/mesh-host", 114, headHost, "main", true), member("novox/mesh-controller", 346, headController, "main", false))},
|
|
nil, ""},
|
|
{"mesh-lab is never one", []groupsLine{group("feat/x",
|
|
member("novox/mesh-host", 114, headHost, "main", true), member("novox/mesh-lab", 5, headLab, "main", true))},
|
|
nil, ""},
|
|
{"a repository the check reads nothing of is not one", []groupsLine{group("feat/x",
|
|
member("novox/mesh-host", 114, headHost, "main", true), member("novox/hq", 9, headHq, "main", true))},
|
|
nil, ""},
|
|
{"mesh-sdk, read beside the controller, is one", []groupsLine{group("feat/x",
|
|
member("novox/mesh-host", 114, headHost, "main", true), member("novox/mesh-sdk", 7, headSDK, "main", true))},
|
|
[]string{"mesh-sdk=novox/mesh-sdk#7@" + headSDK + " onto main"}, ""},
|
|
{"another group is none of its business", []groupsLine{group("feat/y",
|
|
member("novox/mesh-host", 114, headHost, "main", true), member("novox/mesh-controller", 346, headController, "main", true))},
|
|
nil, ""},
|
|
{"a group of its own alone has none", []groupsLine{group("feat/x", member("novox/mesh-host", 114, headHost, "main", true))},
|
|
nil, ""},
|
|
{"an older mesh-delivery names no heads", []groupsLine{{ID: "feat/x",
|
|
Members: []string{"novox/mesh-host@111111111111 (checked)", "novox/mesh-controller@222222222222 (checked)"}}},
|
|
nil, "without their heads and bases"},
|
|
{"a member without its base", []groupsLine{group("feat/x",
|
|
member("novox/mesh-host", 114, headHost, "main", true), member("novox/mesh-controller", 346, headController, "", true))},
|
|
nil, "without its head or base"},
|
|
{"a head that is no commit", []groupsLine{group("feat/x",
|
|
member("novox/mesh-host", 114, headHost, "main", true), member("novox/mesh-controller", 346, headController[:12]+"zzzzzzzzzzzzzzzzzzzzzzzzzzzz", "main", true))},
|
|
nil, "not a whole commit"},
|
|
} {
|
|
got, unknown := siblingsFor(pull, entries, besideTheCore(), c.lines)
|
|
var said []string
|
|
for _, s := range got {
|
|
said = append(said, s.Dir+"="+s.Repository+"#"+itoa(s.Number)+"@"+s.Head+" onto "+s.Base)
|
|
}
|
|
if strings.Join(said, ";") != strings.Join(c.want, ";") {
|
|
t.Errorf("%s: siblings %v, wanted %v", c.what, said, c.want)
|
|
}
|
|
if (c.unknown == "") != (unknown == "") || !strings.Contains(unknown, c.unknown) {
|
|
t.Errorf("%s: could not learn %q, wanted %q", c.what, unknown, c.unknown)
|
|
}
|
|
}
|
|
// A pull request into a release branch has siblings into the release branch of the same name (ADR 0358 §1).
|
|
release := pull
|
|
release.Base = "feat/57"
|
|
lines := []groupsLine{group("feat/x", member("novox/mesh-host", 114, headHost, "feat/57", true),
|
|
member("novox/mesh-controller", 346, headController, "feat/57", true), member("novox/mesh-tools", 8, headLab, "main", true))}
|
|
if got, _ := siblingsFor(release, entries, besideTheCore(), lines); len(got) != 1 || got[0].Dir != "mesh-controller" {
|
|
t.Errorf("into a release branch: %+v", got)
|
|
}
|
|
// A recheck announces the pull request without its head branch: its group is the one holding it (repository, number).
|
|
pull.Head = ""
|
|
got, unknown := siblingsFor(pull, entries, besideTheCore(), lines)
|
|
if len(got) != 1 || got[0].Dir != "mesh-tools" || unknown != "" {
|
|
t.Errorf("a recheck of a group member: %+v %q", got, unknown)
|
|
}
|
|
// A pull request in no group, rechecked, is alone: no sibling, and nothing said.
|
|
pull.Number = 999
|
|
if got, unknown := siblingsFor(pull, entries, besideTheCore(), lines); got != nil || unknown != "" {
|
|
t.Errorf("a lone pull request: %+v %q", got, unknown)
|
|
}
|
|
if got, unknown := siblingsFor(pull, entries, besideTheCore(), nil); got != nil || unknown != "" {
|
|
t.Errorf("no groups at all: %+v %q", got, unknown)
|
|
}
|
|
// A mesh-delivery that names members without their pull requests cannot find the group of a recheck: it says so.
|
|
older := []groupsLine{{ID: "feat/x", Members: []string{"novox/mesh-host@111111111111 (checked)"}}}
|
|
pull.Number = 114
|
|
if got, unknown := siblingsFor(pull, entries, besideTheCore(), older); got != nil || unknown == "" {
|
|
t.Errorf("an older mesh-delivery: %+v %q", got, unknown)
|
|
}
|
|
}
|
|
|
|
func itoa(n int) string { b, _ := json.Marshal(n); return string(b) }
|
|
|
|
// Every check asks mesh-delivery's `groups` once; the ask names the sibling's repository, head and base.
|
|
func TestEveryCheckAsksMeshDeliveryOnceAndNamesItsSiblings(t *testing.T) {
|
|
entries := theCoreOnTheGitSeat()
|
|
asked := 0
|
|
was := groupsAsked
|
|
t.Cleanup(func() { groupsAsked = was })
|
|
groupsAsked = func(_ context.Context, held bool) ([]groupsLine, error) {
|
|
asked++
|
|
if !held {
|
|
t.Error("the seat is held, yet it was asked as not held")
|
|
}
|
|
return []groupsLine{group("feat/x", member("novox/mesh-host", 114, headHost, "main", true),
|
|
member("novox/mesh-controller", 346, headController, "main", true))}, nil
|
|
}
|
|
request := link.BuildRequest{Check: &link.CheckRequest{Owner: "novox", Repo: "mesh-host", Base: "main", Beside: besideTheCore()}}
|
|
withSiblings(t.Context(), &request, link.PullUpdated{Owner: "novox", Repo: "mesh-host", Base: "main", Head: "feat/x"}, entries)
|
|
if asked != 1 {
|
|
t.Fatalf("mesh-delivery was asked %d times for one check", asked)
|
|
}
|
|
want := link.CheckSibling{Repository: "novox/mesh-controller", Number: 346, Head: headController, Base: "main", Dir: "mesh-controller"}
|
|
if got := request.Check.Siblings; len(got) != 1 || got[0] != want || request.Check.SiblingsUnknown != "" {
|
|
t.Fatalf("the ask names siblings %+v, unknown %q", got, request.Check.SiblingsUnknown)
|
|
}
|
|
body, _ := json.Marshal(request.Check)
|
|
if !strings.Contains(string(body), `"siblings":[{"repository":"novox/mesh-controller","number":346,"head":"`+headController+
|
|
`","base":"main","dir":"mesh-controller"}]`) {
|
|
t.Fatalf("on the wire: %s", body)
|
|
}
|
|
}
|
|
|
|
// When mesh-delivery does not answer in time (or is not held, or answered without the fields), the check reads no
|
|
// sibling and its request says it could not learn them, so a fix to mesh-delivery is still checked without it.
|
|
func TestWhenMeshDeliveryDoesNotAnswerTheCheckReadsNoSiblingAndSaysSo(t *testing.T) {
|
|
was := groupsAsked
|
|
t.Cleanup(func() { groupsAsked = was })
|
|
for _, c := range []struct {
|
|
what string
|
|
err error
|
|
held bool
|
|
}{{"it did not answer", context.DeadlineExceeded, true}, {"nothing serves it", errors.New("nothing serves mesh-delivery.groups"), true},
|
|
{"no holder on record", errors.New("no holder of mesh-delivery is on record to say it"), false}} {
|
|
entries := theCoreOnTheGitSeat()
|
|
if !c.held {
|
|
entries = entries[:4]
|
|
}
|
|
groupsAsked = func(_ context.Context, held bool) ([]groupsLine, error) {
|
|
if held != c.held {
|
|
t.Errorf("%s: held %v", c.what, held)
|
|
}
|
|
return nil, c.err
|
|
}
|
|
request := link.BuildRequest{Check: &link.CheckRequest{Owner: "novox", Repo: "mesh-host", Base: "main", Beside: besideTheCore()}}
|
|
withSiblings(t.Context(), &request, link.PullUpdated{Owner: "novox", Repo: "mesh-host", Base: "main", Head: "feat/x"}, entries)
|
|
if len(request.Check.Siblings) != 0 || !strings.Contains(request.Check.SiblingsUnknown, c.err.Error()) ||
|
|
!strings.Contains(request.Check.SiblingsUnknown, "reads no siblings") {
|
|
t.Errorf("%s: siblings %+v, unknown %q", c.what, request.Check.Siblings, request.Check.SiblingsUnknown)
|
|
}
|
|
}
|
|
}
|
|
|
|
// **A pull request for which mesh-delivery answered and named no sibling is asked exactly as before** (ADR 0382 §7):
|
|
// byte for byte the request it was without the rule.
|
|
func TestAPullRequestWithNoSiblingIsAskedByteForByteAsBefore(t *testing.T) {
|
|
entries := theCoreOnTheGitSeat()
|
|
was := groupsAsked
|
|
t.Cleanup(func() { groupsAsked = was })
|
|
for _, answer := range [][]groupsLine{nil, {group("feat/other", member("novox/mesh-controller", 346, headController, "main", true))},
|
|
{group("feat/x", member("novox/mesh-host", 114, headHost, "main", true), member("novox/hq", 3, headHq, "main", true))}} {
|
|
groupsAsked = func(context.Context, bool) ([]groupsLine, error) { return answer, nil }
|
|
build := func() link.BuildRequest {
|
|
return link.BuildRequest{ID: "build-1", Repository: "r", Ref: headHost, Check: &link.CheckRequest{Owner: "novox",
|
|
Repo: "mesh-host", Number: 114, Base: "main", Beside: besideTheCore(), Modules: []string{"mesh-host"}, Judge: "validator"}}
|
|
}
|
|
before, _ := json.Marshal(build())
|
|
after := build()
|
|
withSiblings(t.Context(), &after, link.PullUpdated{Owner: "novox", Repo: "mesh-host", Base: "main", Head: "feat/x"}, entries)
|
|
got, _ := json.Marshal(after)
|
|
if string(before) != string(got) {
|
|
t.Fatalf("the request changed:\nbefore %s\nafter %s", before, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
// What the build seat's verdict says of its siblings reaches the forge's holder in the controller's `checked`.
|
|
func TestTheVerdictCarriesTheSiblingsAndWhereTheJudgeCameFrom(t *testing.T) {
|
|
read := []link.CheckSibling{{Repository: "novox/mesh-controller", Number: 346, Head: headController, Base: "main", BaseCommit: headHost}}
|
|
c := checkedOf(link.BuildResult{ID: "b", Ref: headHost, Checked: &link.CheckRequest{Owner: "novox", Repo: "mesh-host", Number: 114},
|
|
Check: &link.CheckOutcome{Verdict: "pass", Summary: "ok", Siblings: read, JudgeFrom: "sibling novox/mesh-controller#346 merge abc",
|
|
Validator: "the change's own validator"}})
|
|
if len(c.Siblings) != 1 || c.Siblings[0] != read[0] || c.JudgeFrom == "" || c.Validator == "" {
|
|
t.Fatalf("the verdict says %+v", c)
|
|
}
|
|
body, _ := json.Marshal(c)
|
|
for _, want := range []string{`"siblings":[{"repository":"novox/mesh-controller","number":346,"head":"` + headController +
|
|
`","base":"main","base_commit":"` + headHost + `"}]`, `"judge_from":"sibling novox/mesh-controller#346 merge abc"`} {
|
|
if !strings.Contains(string(body), want) {
|
|
t.Fatalf("the verdict lacks %s:\n%s", want, body)
|
|
}
|
|
}
|
|
unknown := checkedOf(link.BuildResult{ID: "b", Check: &link.CheckOutcome{Verdict: "pass", SiblingsUnknown: "mesh-delivery did not answer"}})
|
|
if body, _ := json.Marshal(unknown); !strings.Contains(string(body), `"siblings_unknown":"mesh-delivery did not answer"`) ||
|
|
strings.Contains(string(body), `"siblings":`) {
|
|
t.Fatalf("the verdict that could not learn them says %s", body)
|
|
}
|
|
}
|