Files
mesh-catalog/modules/mesh-delivery/cmd/mesh-delivery/group_test.go
T

222 lines
9.1 KiB
Go

package main
import (
"encoding/json"
"sort"
"testing"
"time"
)
// **A group is merged in one pass on its one Approve, and delivered by one plan** (novox/hq ADR 0418, ADR 0419, ADR
// 0421, issues 978 and 1047): the members are not ordered, none waits for another's merge or delivery, and a trunk
// move does not ask the operator a second time.
const (
gCtl, gCat = "novox/mesh-controller", "novox/mesh-catalog"
gHc, gHk = "c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1c1", "d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2"
gTrunk0, gT1 = "a2c2c35000000000000000000000000000000000", "d28ca95000000000000000000000000000000000"
)
// approvedGroup is a group of two pull requests on one branch, checks green, composed green and the operator's Approve
// asked and not yet answered (w.op.auto is off).
func approvedGroup(t *testing.T) *world {
w := newWorld(t)
w.op.auto = false
w.h.PullUpdated(pr(gCtl, 1, gHc, "feat/g", "internal/a.go"))
w.h.PullUpdated(pr(gCat, 2, gHk, "feat/g", "modules/app/module.json"))
w.h.Checked(judged(gCtl, 1, gHc, "pass", gTrunk0))
w.h.Checked(judged(gCat, 2, gHk, "pass", gTrunk0))
w.settleAll()
g := w.h.groups["feat/g"]
if g == nil || g.Check == nil {
t.Fatalf("no composed check was asked: %+v", g)
}
w.h.Checked(CheckedEvent{Group: "feat/g", ID: g.Check.Asked, Verdict: "pass", Summary: "composed",
Members: []struct {
Owner string `json:"owner"`
Repo string `json:"repo"`
Commit string `json:"commit"`
Trunk string `json:"trunk,omitempty"`
}{{Owner: "novox", Repo: "mesh-controller", Commit: gHc, Trunk: gTrunk0[:12]},
{Owner: "novox", Repo: "mesh-catalog", Commit: gHk, Trunk: gTrunk0[:12]}}})
w.settleAll()
if w.op.count() != 1 || len(w.op.open()) != 1 {
t.Fatalf("the green group is asked once: %d asks, open %v", w.op.count(), w.op.open())
}
return w
}
// Issue 1047 (the rule the lead settled, ADR 0421 §3): an approved group whose trunk moved through a merge that is not
// its member's has each out-of-date member's check asked at once, then the composed check, and merges on the same
// Approve — with no second one asked.
func TestAnApprovedGroupWhoseTrunkMovedIsCheckedAgainAndMergedOnTheSameApprove(t *testing.T) {
w := approvedGroup(t)
asked := len(w.ctl.checks)
w.h.PullMerged(merged(gCtl, 5, "5555555555555555", gT1))
w.settleAll()
// The member whose trunk moved has its own check asked, in the same look; the other member's trunk did not move.
if got := w.ctl.checks[asked:]; len(got) != 1 || got[0] != ":"+IDOf(gCtl, gHc) {
t.Fatalf("the trunk's move asked %v; wanted the moved member's own check once", got)
}
if w.ctl.baseSHAs[len(w.ctl.baseSHAs)-1] != gT1[:12] && w.ctl.baseSHAs[len(w.ctl.baseSHAs)-1] != gT1 {
t.Fatalf("its check was not asked against the trunk's head: %v", w.ctl.baseSHAs)
}
// The Approve is given while the checks run: nothing merges on the verdict out of date.
w.op.answer(w.op.open()[0], OptionApprove)
w.settleAll()
if len(w.forge.merges) != 0 {
t.Fatalf("merged while the group's checks were out of date: %v", w.forge.merges)
}
// The member is judged again: the composed check is asked on the new verdicts.
w.later(time.Minute)
w.h.Checked(judged(gCtl, 1, gHc, "pass", gT1))
w.settleAll()
g := w.h.groups["feat/g"]
last := w.ctl.checks[len(w.ctl.checks)-1]
if last != "feat/g:"+IDOf(gCat, gHk)+","+IDOf(gCtl, gHc) {
t.Fatalf("the composed check was not asked again: %v", w.ctl.checks)
}
w.h.Checked(CheckedEvent{Group: "feat/g", ID: g.Check.Asked, Verdict: "pass", Summary: "composed again"})
w.settleAll()
sort.Strings(w.forge.merges)
if len(w.forge.merges) != 2 || w.forge.merges[0] != "novox/mesh-catalog#2@"+gHk || w.forge.merges[1] != "novox/mesh-controller#1@"+gHc {
t.Fatalf("both members were not merged on the new composed verdict: %v", w.forge.merges)
}
if w.op.count() != 1 {
t.Fatalf("a second Approve was asked: %d", w.op.count())
}
}
// ADR 0418 §4: a member the forge refuses is merged again on its fresh check, while the other merged in the same pass.
func TestAMemberTheForgeRefusesIsMergedAgainWhileTheOtherMergedInTheSamePass(t *testing.T) {
w := approvedGroup(t)
w.forge.refuse = errAskedAgain
w.op.answer(w.op.open()[0], OptionApprove)
w.settleAll()
if len(w.forge.merges) != 2 {
t.Fatalf("both members are asked in the same pass: %v", w.forge.merges)
}
w.forge.refuse = nil
w.later(2 * time.Minute)
w.settleAll()
if len(w.forge.merges) != 4 {
t.Fatalf("a refused member is asked again a minute on: %v", w.forge.merges)
}
if d := w.delivery(IDOf(gCat, gHk)); d.Ready.Merged == nil {
t.Fatalf("not merged: %+v", d.Ready)
}
}
// Issue 978 (the pin): a feature's single pull request in one repository is a lone delivery, asked alone.
func TestAFeaturesSinglePullRequestIsALoneDeliveryAskedAlone(t *testing.T) {
w := newWorld(t)
w.op.auto = false
w.h.PullUpdated(pr("novox/app", 7, readyHead, "feat/x", "src/a.go"))
w.h.Checked(judgedAt("novox/app", 7, readyHead, "pass", "t1t1t1t1t1t1"))
w.settleAll()
d := w.delivery(IDOf("novox/app", readyHead))
if d.Group != "" || len(w.h.groups) != 0 {
t.Fatalf("a single pull request made a group: %q %v", d.Group, w.h.groups)
}
if w.op.count() != 1 {
t.Fatalf("a lone delivery is asked once, alone: %d", w.op.count())
}
}
// A group answer says its members' gate is the composed check's (agreed with the controller, issue 977).
func TestTheGroupsAnswerSaysItsGateIsTheComposedChecks(t *testing.T) {
w := approvedGroup(t)
lines := w.h.Groups(false)
if len(lines) != 1 || !lines[0].ComposedGate {
t.Fatalf("groups: %+v", lines)
}
raw, _ := json.Marshal(lines[0])
var got map[string]any
if err := json.Unmarshal(raw, &got); err != nil || got["composed_gate"] != true {
t.Fatalf("the group line says %s", raw)
}
if _, ordered := got["order"]; ordered {
t.Fatalf("a group line still says an order: %s", raw)
}
}
// ADR 0403 §1: a member whose own gate the group's composed check judges instead (the controller says its gate layer
// "composed") counts as passed here once its repository's check did not fail, so the group's composed check is asked.
func TestAMemberWhoseGateIsTheComposedChecksCountsAsPassed(t *testing.T) {
w := newWorld(t)
w.op.auto = false
w.h.PullUpdated(pr(gCtl, 1, gHc, "feat/g", "internal/a.go"))
w.h.PullUpdated(pr(gCat, 2, gHk, "feat/g", "modules/app/module.json"))
for _, c := range []struct {
repo string
n int
head string
}{{gCtl, 1, gHc}, {gCat, 2, gHk}} {
v := judged(c.repo, c.n, c.head, "pass", gTrunk0)
v.Gate = &struct {
Verdict string `json:"verdict"`
Summary string `json:"summary"`
Modules []string `json:"modules,omitempty"`
Dependents []string `json:"dependents,omitempty"`
}{Verdict: GateComposed, Summary: "judged by its group's composed check"}
v.RepoCheck = &struct {
Verdict string `json:"verdict"`
Summary string `json:"summary"`
}{Verdict: "pass", Summary: "the repository's own check passed"}
w.h.Checked(v)
}
w.settleAll()
if len(w.ctl.checks) != 1 || w.ctl.checks[0] != "feat/g:"+IDOf(gCat, gHk)+","+IDOf(gCtl, gHc) {
t.Fatalf("the composed check was not asked for members judged by it: %v", w.ctl.checks)
}
// A member whose repository check failed is not passed, whatever the composed check will say.
w2 := newWorld(t)
w2.h.PullUpdated(pr(gCtl, 1, gHc, "feat/g", "internal/a.go"))
v := judged(gCtl, 1, gHc, "pass", gTrunk0)
v.Gate = &struct {
Verdict string `json:"verdict"`
Summary string `json:"summary"`
Modules []string `json:"modules,omitempty"`
Dependents []string `json:"dependents,omitempty"`
}{Verdict: GateComposed}
v.RepoCheck = &struct {
Verdict string `json:"verdict"`
Summary string `json:"summary"`
}{Verdict: "fail", Summary: "a test failed"}
w2.h.Checked(v)
if w2.state(IDOf(gCtl, gHc)) == Ready {
t.Fatalf("a member whose repository check failed is ready")
}
}
// Issue 1049 (survey question 1, confirmed here): a composed check asked after one member has already merged is asked
// with ALL the members, the merged one at the head it merged, never with the unmerged one alone. The controller needs two
// or more members to compose (mesh-controller delivery.go askGroupCheck), and a merge of a contained head is a no-op
// there.
func TestAComposedCheckAskedAfterOneMemberMergedIsAskedWithAllMembers(t *testing.T) {
w := approvedGroup(t)
w.forge.refuseRepo = map[string]error{gCtl: errAskedAgain}
w.op.answer(w.op.open()[0], OptionApprove)
w.settleAll()
w.h.PullMerged(merged(gCat, 2, gHk, gT1))
w.settleAll()
if g := w.h.groups["feat/g"]; g == nil || !g.Closed {
t.Fatalf("the group did not close when a member merged: %+v", g)
}
// The unmerged member gets a new head and its own verdict: the group's composed check is due again.
const gHc2 = "e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3e3"
asked := len(w.ctl.checks)
w.h.PullUpdated(pr(gCtl, 1, gHc2, "feat/g", "internal/a.go"))
w.h.Checked(judged(gCtl, 1, gHc2, "pass", gT1))
w.settleAll()
var composed []string
for _, c := range w.ctl.checks[asked:] {
if len(c) > 7 && c[:7] == "feat/g:" {
composed = append(composed, c)
}
}
if len(composed) != 1 || composed[0] != "feat/g:"+IDOf(gCat, gHk)+","+IDOf(gCtl, gHc2) {
t.Fatalf("the composed check was asked with %v; wanted once, with both members (the merged one at its head)", composed)
}
}