Swaps three import paths for mesh-sdk's go/catalogue, go/overlay and go/validate (the same packages, copied from this repository at 6c2a8d62) and lets gofmt order the groups. The six swap commits compile together once the pin commit (go.mod, modules.txt) is on the branch; each alone does not, because a file on the SDK's types cannot call one still on the old package.
249 lines
13 KiB
Go
249 lines
13 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/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)
|
|
}
|
|
}
|