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
The controller's words for the line (mesh-controller #213) name the checks waited on; a field spares them reading the sentence.
184 lines
9.3 KiB
Go
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)
|
|
}
|
|
}
|