Files
mesh-catalog/modules/mesh-delivery/cmd/mesh-delivery/issue438_test.go
T
jschoubben 450892498d
mesh/merge-gate pass: builds gitea, mesh-delivery → novox; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-check-typescript.sh (typescript)
mesh/delivery delivered
mesh/delivery-group group fix/438-a-check-that-never-answered-is-said delivered: every member is delivered
mesh-delivery: an unanswered line names its checks in a field of their own (issue 438)
The controller's words for the line (mesh-controller #213) name the checks waited
on; a field spares them reading the sentence.
2026-10-11 02:25:48 +02:00

184 lines
9.3 KiB
Go

package main
import (
"encoding/json"
"slices"
"strings"
"testing"
"time"
)
// openPullsOf reads gitea_open_pulls' answer as the forge module gives it, so the tests read the real shape: the
// pull requests below are mesh-controller #212's and #211's heads and mesh-catalog #194's, as the forge said them on
// 2026-10-11 (novox/hq issue 438), with only their numbers and shas as they were.
func openPullsOf(t *testing.T, answer string) []OpenPull {
t.Helper()
var open struct {
Pulls []OpenPull `json:"pulls"`
}
if err := json.Unmarshal([]byte(answer), &open); err != nil {
t.Fatal(err)
}
return open.Pulls
}
// A head the mesh's check started on and never answered: mesh/merge-gate pending since the controller began mapping
// it, mesh/repo-check never set. Its base requires both.
const startedNeverAnswered = `{"pulls": [{
"base": "main", "head_sha": "c11222026a3b26a0cc4b277119e1ea059b82a36b",
"html_url": "http://127.0.0.1:20000/novox/mesh-controller/pulls/212", "number": 212,
"owner": "novox", "repo": "mesh-controller",
"required": ["mesh/merge-gate", "mesh/repo-check"],
"statuses": ["mesh/merge-gate", "mesh/delivery"],
"checks": {
"mesh/delivery": {"state": "pending", "description": "checking", "updated_at": "2026-10-11T00:11:46Z"},
"mesh/merge-gate": {"state": "pending", "description": "the mesh is mapping this head onto its module graph",
"updated_at": "2026-10-11T00:11:39Z"}
}
}]}`
// **A head whose merge check started and never answered is said** (novox/hq issue 438): pending, or missing a check
// its base requires, for as long as the delivery table gives a check asked and not answered (Bounds[Proposed]) since
// this owner first saw it so, it is a stalled line naming each check that did not answer and since when. Before,
// any merge-check status counted as checked, pending included, and such a head was never said.
func TestAHeadWhoseCheckStartedAndNeverAnsweredIsSaid(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), time.Date(2026, 10, 11, 0, 12, 5, 0, time.UTC))
w.forge.open = openPullsOf(t, startedNeverAnswered)
w.h.SeeOpenPulls()
w.later(Bounds[Proposed].For - time.Minute)
w.h.SeeOpenPulls()
if lines := w.h.Stalled(); len(lines) != 0 {
t.Fatalf("a head still within the bound of a check asked and not answered is said: %+v", lines)
}
w.later(2 * time.Minute)
lines := w.h.Stalled()
if len(lines) != 1 {
t.Fatalf("a head whose check started and never answered is not said: %+v", lines)
}
l := lines[0]
if l.ID != IDOf("novox/mesh-controller", "c11222026a3b26a0cc4b277119e1ea059b82a36b") || l.Number != 212 ||
l.State != Unanswered || l.Bound != Bounds[Proposed].For.String() || l.For != "1h1m0s" {
t.Fatalf("the line is not this head's, past the table's bound: %+v", l)
}
for _, want := range []string{"mesh/merge-gate", "pending since 2026-10-11T00:11:39Z", "mesh/repo-check", "never set",
"never answered"} {
if !strings.Contains(l.Says, want) {
t.Fatalf("the line does not say %q: %s", want, l.Says)
}
}
if want := []string{"mesh/merge-gate pending since 2026-10-11T00:11:39Z", "mesh/repo-check never set"}; !slices.Equal(l.Waiting, want) {
t.Fatalf("the line's checks waited on are %q, not %q", l.Waiting, want)
}
raw, _ := json.Marshal(l)
t.Logf("the line as the controller reads it: %s", raw) // mesh-controller's test of its words reads this line
if strings.Contains(l.Says, "mesh/delivery") {
t.Fatalf("the line names a status that is not the merge check: %s", l.Says)
}
// Announced at last: a delivery of its own, with its own bound — no longer this line.
w.h.PullUpdated(pr("novox/mesh-controller", 212, "c11222026a3b26a0cc4b277119e1ea059b82a36b", "fix/402"))
w.h.SeeOpenPulls()
for _, l := range w.h.Stalled() {
if l.State == Unannounced || l.State == Unanswered {
t.Fatalf("an announced head is still said unannounced: %+v", l)
}
}
}
// **A check that answers does not start the bound again**: merge-gate answers at fifty minutes, repo-check is never
// set, and the head is said on the bound from this owner's first sight of it, not fifty minutes later.
func TestACheckThatAnswersDoesNotStartTheBoundAgain(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), time.Date(2026, 10, 11, 0, 12, 5, 0, time.UTC))
w.forge.open = openPullsOf(t, startedNeverAnswered)
w.h.SeeOpenPulls()
w.later(50 * time.Minute)
w.forge.open = openPullsOf(t, strings.Replace(startedNeverAnswered, `"state": "pending", "description": "the mesh is mapping`,
`"state": "success", "description": "pass: the mesh is mapping`, 1))
w.h.SeeOpenPulls()
w.later(11 * time.Minute)
w.h.SeeOpenPulls()
lines := w.h.Stalled()
if len(lines) != 1 || lines[0].For != "1h1m0s" || !strings.Contains(lines[0].Says, "mesh/repo-check never set") ||
strings.Contains(lines[0].Says, "mesh/merge-gate") {
t.Fatalf("a head whose merge-gate answered at fifty minutes is not said at sixty-one on what is left: %+v", lines)
}
}
// A forge time with an offset is said in UTC, as the module's other lines say their times.
func TestAPendingTimeIsSaidInUTC(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), time.Date(2026, 10, 11, 0, 12, 5, 0, time.UTC))
w.forge.open = openPullsOf(t, strings.Replace(startedNeverAnswered, "2026-10-11T00:11:39Z", "2026-10-11T02:11:39+02:00", 1))
w.h.SeeOpenPulls()
w.later(Bounds[Proposed].For + time.Minute)
if lines := w.h.Stalled(); len(lines) != 1 || !strings.Contains(lines[0].Says, "pending since 2026-10-11T00:11:39Z") {
t.Fatalf("the pending time is not said in UTC: %+v", lines)
}
}
// A check asked again — its pending status set anew, as a recheck or a new announcement does — starts the bound again:
// the head is said only once that ask too has gone unanswered past it.
func TestACheckAskedAgainStartsItsBoundAgain(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), time.Date(2026, 10, 11, 0, 12, 5, 0, time.UTC))
w.forge.open = openPullsOf(t, startedNeverAnswered)
w.h.SeeOpenPulls()
w.later(50 * time.Minute)
again := openPullsOf(t, strings.Replace(startedNeverAnswered, "2026-10-11T00:11:39Z", "2026-10-11T01:02:00Z", 1))
w.forge.open = again
w.h.SeeOpenPulls()
w.later(20 * time.Minute)
w.h.SeeOpenPulls()
if lines := w.h.Stalled(); len(lines) != 0 {
t.Fatalf("a check asked again twenty minutes ago is said on the first ask's time: %+v", lines)
}
w.later(41 * time.Minute)
if lines := w.h.Stalled(); len(lines) != 1 || !strings.Contains(lines[0].Says, "pending since 2026-10-11T01:02:00Z") {
t.Fatalf("a check asked again and not answered past the bound is not said: %+v", lines)
}
}
// **A head whose required checks all answered is not said**, whatever they answered: a failure is a verdict, and the
// pull request says it itself. Nor is one whose only pending check is one its base does not require: hq requires
// mesh/repo-check alone.
func TestAHeadWhoseRequiredChecksAnsweredIsNotSaid(t *testing.T) {
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), time.Date(2026, 10, 11, 0, 12, 5, 0, time.UTC))
w.forge.open = openPullsOf(t, `{"pulls": [{
"base": "main", "head_sha": "99df6b999bad77c51dfdd13470cf57b1f38e8c24", "number": 194, "owner": "novox", "repo": "mesh-catalog",
"required": ["mesh/merge-gate", "mesh/repo-check"],
"statuses": ["mesh/merge-gate", "mesh/repo-check", "mesh/delivery"],
"checks": {
"mesh/delivery": {"state": "failure", "description": "rejected", "updated_at": "2026-10-11T00:05:37Z"},
"mesh/merge-gate": {"state": "success", "description": "pass: builds mounts → shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)", "updated_at": "2026-10-11T00:05:28Z"},
"mesh/repo-check": {"state": "failure", "description": "fail: its merge-check.sh failed", "updated_at": "2026-10-11T00:05:28Z"}
}
}, {
"base": "main", "head_sha": "11b719fd5cdec43970e513a10a44a558042599e6", "number": 308, "owner": "novox", "repo": "hq",
"required": ["mesh/repo-check"],
"statuses": ["mesh/merge-gate", "mesh/repo-check", "mesh/delivery"],
"checks": {
"mesh/merge-gate": {"state": "pending", "description": "the mesh is mapping this head onto its module graph", "updated_at": "2026-10-11T00:01:00Z"},
"mesh/repo-check": {"state": "success", "description": "pass: its merge-check.sh passed", "updated_at": "2026-10-11T00:02:00Z"}
}
}]}`)
w.h.SeeOpenPulls()
w.later(3 * Bounds[Proposed].For)
w.h.SeeOpenPulls()
if lines := w.h.Stalled(); len(lines) != 0 {
t.Fatalf("a head whose required checks answered is said: %+v", lines)
}
}
// A forge module that says only which contexts a head has, not their states — the gitea module before issue 438 —
// is read as before: a head with any merge check is not said, and nothing is guessed of what it does not say.
func TestAForgeThatSaysNoStatesIsReadAsBefore(t *testing.T) {
w := newWorld(t)
w.forge.open = openPullsOf(t, `{"pulls": [{"base": "main", "head_sha": "c11222026a3b26a0cc4b277119e1ea059b82a36b",
"number": 212, "owner": "novox", "repo": "mesh-controller", "required": ["mesh/merge-gate", "mesh/repo-check"],
"statuses": ["mesh/merge-gate", "mesh/delivery"]}]}`)
w.h.SeeOpenPulls()
w.later(3 * Bounds[Proposed].For)
w.h.SeeOpenPulls()
if lines := w.h.Stalled(); len(lines) != 0 {
t.Fatalf("a head is said on states the forge did not give: %+v", lines)
}
}