Files
mesh-catalog/modules/mesh-issues/cmd/mesh-issues/learn_test.go
T
jschoubben 3c6f2dc274
mesh/delivery merged: source.main moved to novox/mesh-catalog@02597b66d7de
mesh/check checking 3c6f2dc274a9 on 457c60972c59
mesh-issues: a learned merge is kept as of now, closed untimed issues get no fix time, the forge read is declared, the sweep rotates its asks (issue 1950)
2026-10-27 01:30:37 +01:00

189 lines
7.9 KiB
Go

package main
import (
"encoding/json"
"errors"
"strings"
"testing"
"time"
)
// The forge's answer for novox/mesh-delivery#5, read from gitea.gitea_get_pull_request on 2026-10-27 (shortened): merged, closed,
// its merge commit and moment. mesh-delivery's checks carries the same `pull` block and no `delivery` block for it.
const forgePullMerged = `{"pull":{"base":"main","head":"feat/delivery-parts","merged":true,"merged_at":"2026-10-19T18:05:16Z",` +
`"merge_commit_sha":"1325c8b452c8fa65c478af6815887870a1f42e7b","number":5,"state":"closed"}}`
func TestAPullRequestWithNoFixRecordLearnsItsMerge(t *testing.T) {
mergedAt := time.Date(2026, 10, 19, 18, 5, 16, 0, time.UTC)
merged := Delivery{Pulled: true, Merged: true, MergedAs: "1325c8b452c8fa65c478af6815887870a1f42e7b", MergedAt: &mergedAt, Repository: "novox/mesh-catalog"}
closed := Delivery{Pulled: true, Closed: true}
open := Delivery{Pulled: true}
for _, c := range []struct {
name string
delivery Delivery
deliveryEr error
forge *Delivery
forgeEr error
want string // the delivery line after learning
wantErr bool
}{
{name: "mesh-delivery's pull says merged", delivery: merged, want: "fix merged, not delivered: mesh-catalog#50"},
{name: "mesh-delivery cannot, the forge says merged", deliveryEr: errors.New("down"), forge: &merged,
want: "fix merged, not delivered: mesh-catalog#50"},
{name: "mesh-delivery says nothing of the pull, the forge says merged", forge: &merged, want: "fix merged, not delivered: mesh-catalog#50"},
{name: "closed without merging is withdrawn", delivery: closed, want: "fix withdrawn: mesh-catalog#50 closed without merging"},
{name: "still open stays unheard", delivery: open, want: "nothing heard from the delivery of mesh-catalog#50"},
{name: "a forge that cannot say leaves it", forgeEr: errors.New("forge down"), want: "nothing heard from the delivery of mesh-catalog#50"},
{name: "neither can say", deliveryEr: errors.New("down"), forgeEr: errors.New("forge down"), want: "nothing heard from the delivery of mesh-catalog#50", wantErr: true},
} {
t.Run(c.name, func(t *testing.T) {
h := newHarness(t)
n := fixIssue(t, h, "mesh-catalog#50")
h.Deliveries = func(string, string) (Delivery, error) { return c.delivery, c.deliveryEr }
h.Pull = func(string, string) (Delivery, error) {
if c.forge != nil {
return *c.forge, nil
}
return Delivery{}, errOr(c.forgeEr)
}
_, err := h.LearnFixes(n)
if (err != nil) != c.wantErr {
t.Fatalf("error %v, wanted error %v", err, c.wantErr)
}
if v, _ := h.View(n); v.Delivery != c.want {
t.Fatalf("%q, wanted %q", v.Delivery, c.want)
}
})
}
}
func errOr(e error) error {
if e == nil {
return errors.New("the forge has no such pull request")
}
return e
}
// A fix merged before the tracker heard deliveries: the delivered event itself learns its merge and covers it, a resolved issue's
// included; and the sweep learns it with no event.
func TestAFixMergedBeforeTheTrackerHeardDeliveriesIsDeliveredByTheNextEventOfItsRepository(t *testing.T) {
mergedAs := "1325c8b452c8fa65c478af6815887870a1f42e7b"
forge := func(string, string) (Delivery, error) {
return Delivery{Pulled: true, Merged: true, MergedAs: mergedAs, Repository: "novox/mesh-catalog"}, nil
}
contains := func(_, _, commit, in string) (bool, error) {
return commit == mergedAs && in == "ffffffffffff0000", nil
}
heads := map[string]string{"novox/mesh-catalog": "ffffffffffff0000"}
delivered := "delivered to every machine it should reach"
h := newHarness(t)
n := fixIssue(t, h, "mesh-catalog#51")
h.Pull, h.Contains = forge, contains
viewSays(t, h, 30, "", heads)
if v, _ := h.View(n); v.Delivery != delivered {
t.Fatalf("the event did not cover the old fix: %q", v.Delivery)
}
if f := fixOf(get(t, h, n), "mesh-catalog#51"); f == nil || f.MergedAs != mergedAs || f.Repository != "novox/mesh-catalog" || f.MergedAt != nil && f.MergedAt.IsZero() {
t.Fatalf("fix %+v", f)
}
// A resolved issue is learned by the sweep, which then waits for the next event, not for ever.
h2 := newHarness(t)
m := fixIssue(t, h2, "mesh-catalog#52")
if _, err := h2.Move(MoveAsk{Number: m, Status: StatusResolved, FixedBy: []string{"mesh-catalog#52"}}); err != nil {
t.Fatal(err)
}
if v, _ := h2.View(m); !strings.HasPrefix(v.Delivery, "nothing heard") {
t.Fatalf("before: %q", v.Delivery)
}
h2.Pull, h2.Contains = forge, contains
if _, err := h2.Sweep(); err != nil {
t.Fatal(err)
}
if v, _ := h2.View(m); v.Delivery != "fix merged, not delivered: mesh-catalog#52" {
t.Fatalf("after the sweep: %q", v.Delivery)
}
viewSays(t, h2, 31, "", heads)
if v, _ := h2.View(m); v.Delivery != delivered {
t.Fatalf("after the event: %q", v.Delivery)
}
}
func TestTheForgesRealPullAnswerIsReadAsMerged(t *testing.T) {
var out struct {
Pull pullFacts `json:"pull"`
}
if err := json.Unmarshal([]byte(forgePullMerged), &out); err != nil {
t.Fatal(err)
}
var d Delivery
d.fromPull(out.Pull, "novox/mesh-delivery")
if !d.Pulled || !d.Merged || d.Closed || d.MergedAs != "1325c8b452c8fa65c478af6815887870a1f42e7b" ||
d.MergedAt == nil || d.MergedAt.Format(time.RFC3339) != "2026-10-19T18:05:16Z" || d.Repository != "novox/mesh-delivery" {
t.Fatalf("%+v", d)
}
}
// A merge learned late is kept as of the moment it was learned: the stuck bound runs from then, so an old merge raises no
// false warning in the sweep that learned it; and an issue closed before its fix was timed keeps no fix time when a late
// delivery covers it.
func TestALearnedOldMergeRaisesNoStuckWarningAndTimesNoClosedIssue(t *testing.T) {
h := newHarness(t)
now := clocked(h)
note := &notified{}
h.Notify = note.notify
old := now.Add(-10 * 24 * time.Hour)
mergedAs := "1325c8b452c8fa65c478af6815887870a1f42e7b"
h.Pull = func(string, string) (Delivery, error) {
return Delivery{Pulled: true, Merged: true, MergedAs: mergedAs, MergedAt: &old, Repository: "novox/mesh-catalog"}, nil
}
h.Contains = func(_, _, commit, _ string) (bool, error) { return commit == mergedAs, nil }
open := fixIssue(t, h, "mesh-catalog#60")
closed := fixIssue(t, h, "mesh-catalog#61")
if _, err := h.Move(MoveAsk{Number: closed, Status: StatusResolved, FixedBy: []string{"mesh-catalog#61"}}); err != nil {
t.Fatal(err)
}
if _, err := h.Sweep(); err != nil {
t.Fatal(err)
}
if len(note.said) != 0 || noted(get(t, h, open), "Stuck: the fix") {
t.Fatalf("a learned old merge was said stuck in the sweep that learned it: %q", note.said)
}
if f := fixOf(get(t, h, open), "mesh-catalog#60"); f == nil || f.MergedAt == nil || !f.MergedAt.Equal(old) {
t.Fatalf("the forge's merge moment is kept: %+v", f)
}
*now = now.Add(3 * time.Hour)
if _, err := h.Sweep(); err != nil || len(note.said) != 1 {
t.Fatalf("not delivered 3 hours after it was learned: %q %v", note.said, err)
}
viewSays(t, h, 40, "", map[string]string{"novox/mesh-catalog": "ffffffffffff0000"})
if v, _ := h.View(closed); v.Delivery != "delivered to every machine it should reach" || get(t, h, closed).FixTime != nil {
t.Fatalf("closed issue: %q, fix time %+v", v.Delivery, get(t, h, closed).FixTime)
}
}
// Pull requests that cannot be learned (still open) do not starve the others: the one asked longest ago goes first.
func TestTheSweepAsksTheLeastRecentlyAskedPullRequestFirst(t *testing.T) {
h := newHarness(t)
var stuck []uint64
for i := 0; i < MostSweepAsks+2; i++ {
stuck = append(stuck, fixIssue(t, h, "mesh-catalog#"+itoa(70+i)))
}
last := fixIssue(t, h, "mesh-catalog#99")
h.Pull = func(_, number string) (Delivery, error) {
if number == "99" {
return Delivery{Pulled: true, Merged: true, MergedAs: "1325c8b452c8fa65c478af6815887870a1f42e7b"}, nil
}
return Delivery{Pulled: true}, nil // still open
}
for i := 0; i < 4; i++ {
if _, err := h.Sweep(); err != nil {
t.Fatal(err)
}
}
if v, _ := h.View(last); !strings.HasPrefix(v.Delivery, "fix merged") {
t.Fatalf("starved by pull requests that cannot be learned: %q (%d of them)", v.Delivery, len(stuck))
}
}