mesh/merge-gate pass: builds 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 covered after its failure: a later walk that contains it was delivered: plan-1791682028101285377 (novox/mesh-catalog at a5b4a757 into main,…
The review asked for the clearing paths to be pinned: a recheck or a merge clears the line before the forge is read again, and a pull request gone from the open list is no longer said.
252 lines
12 KiB
Go
252 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// A delivery held in ready or rejected whose required merge check reads pending or missing on the forge
|
|
// (novox/hq issue 441). The pull requests below are mesh-controller #215's head, ready, and mesh-lab #76's, rejected,
|
|
// as gitea_open_pulls said them on 2026-10-11, with only the one status changed that the case is about: a status
|
|
// write that lagged or was lost (issue 423), or a check asked again after the verdict.
|
|
|
|
const readyHeadSHA = "d2e9dc61590f950b74ae2e93282c354646705188"
|
|
|
|
// readyRepoCheckPending is #215 with mesh/repo-check pending since its delivery was ready.
|
|
const readyRepoCheckPending = `{"pulls": [{
|
|
"base": "main", "head_sha": "d2e9dc61590f950b74ae2e93282c354646705188",
|
|
"html_url": "http://127.0.0.1:20000/novox/mesh-controller/pulls/215", "number": 215,
|
|
"owner": "novox", "repo": "mesh-controller",
|
|
"required": ["mesh/merge-gate", "mesh/repo-check"],
|
|
"statuses": ["mesh/merge-gate", "mesh/repo-check", "mesh/delivery"],
|
|
"checks": {
|
|
"mesh/delivery": {"state": "success", "description": "ready: it delivers once merged", "updated_at": "2026-10-11T00:42:06Z"},
|
|
"mesh/merge-gate": {"state": "success", "description": "pass: builds build-agent → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)", "updated_at": "2026-10-11T00:42:01Z"},
|
|
"mesh/repo-check": {"state": "pending", "description": "the repository's own check runs", "updated_at": "2026-10-11T00:42:01Z"}
|
|
}
|
|
}]}`
|
|
|
|
// readyRepoCheckMissing is #215 whose mesh/repo-check was never written.
|
|
const readyRepoCheckMissing = `{"pulls": [{
|
|
"base": "main", "head_sha": "d2e9dc61590f950b74ae2e93282c354646705188", "number": 215,
|
|
"owner": "novox", "repo": "mesh-controller",
|
|
"required": ["mesh/merge-gate", "mesh/repo-check"],
|
|
"statuses": ["mesh/merge-gate", "mesh/delivery"],
|
|
"checks": {
|
|
"mesh/delivery": {"state": "success", "description": "ready: it delivers once merged", "updated_at": "2026-10-11T00:42:06Z"},
|
|
"mesh/merge-gate": {"state": "success", "description": "pass", "updated_at": "2026-10-11T00:42:01Z"}
|
|
}
|
|
}]}`
|
|
|
|
// readyAnswered is #215 as the forge said it: every required check answered.
|
|
const readyAnswered = `{"pulls": [{
|
|
"base": "main", "head_sha": "d2e9dc61590f950b74ae2e93282c354646705188", "number": 215,
|
|
"owner": "novox", "repo": "mesh-controller",
|
|
"required": ["mesh/merge-gate", "mesh/repo-check"],
|
|
"statuses": ["mesh/merge-gate", "mesh/repo-check", "mesh/delivery"],
|
|
"checks": {
|
|
"mesh/merge-gate": {"state": "success", "description": "pass", "updated_at": "2026-10-11T00:42:01Z"},
|
|
"mesh/repo-check": {"state": "success", "description": "pass: its merge-check.sh passed", "updated_at": "2026-10-11T00:42:01Z"}
|
|
}
|
|
}]}`
|
|
|
|
// heldIn is a delivery of #215's head in ready (a passing verdict) or rejected (a failing one).
|
|
func heldIn(t *testing.T, w *world, gate string, want State) string {
|
|
t.Helper()
|
|
w.h.PullUpdated(pr("novox/mesh-controller", 215, readyHeadSHA, "fix/215"))
|
|
w.h.Checked(verdict("novox/mesh-controller", 215, readyHeadSHA, gate))
|
|
id := IDOf("novox/mesh-controller", readyHeadSHA)
|
|
if s := w.state(id); s != want {
|
|
t.Fatalf("a %s verdict left it %s", gate, s)
|
|
}
|
|
return id
|
|
}
|
|
|
|
// **A delivery in ready whose required check reads pending on the forge is said** (novox/hq issue 441) on the same
|
|
// rule as a head with no delivery (issue 438): past the table's bound for a check asked and not answered, since this
|
|
// owner first saw it so, as an unanswered line naming the check and since when. Before, any head with a delivery was
|
|
// skipped, and ready has no bound of its own, so it waited for ever — unable to merge — with nothing said.
|
|
func TestADeliveryInReadyWhoseCheckIsPendingIsSaid(t *testing.T) {
|
|
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), time.Date(2026, 10, 11, 0, 43, 0, 0, time.UTC))
|
|
id := heldIn(t, w, "pass", Ready)
|
|
w.forge.open = openPullsOf(t, readyRepoCheckPending)
|
|
w.h.SeeOpenPulls()
|
|
w.later(Bounds[Proposed].For - time.Minute)
|
|
w.h.SeeOpenPulls()
|
|
if lines := w.h.Stalled(); len(lines) != 0 {
|
|
t.Fatalf("a ready delivery still within the bound is said: %+v", lines)
|
|
}
|
|
w.later(2 * time.Minute)
|
|
lines := w.h.Stalled()
|
|
if len(lines) != 1 {
|
|
t.Fatalf("a ready delivery whose required check is pending past the bound is not said: %+v", lines)
|
|
}
|
|
l := lines[0]
|
|
if l.ID != id || l.Number != 215 || l.State != Unanswered || l.Bound != Bounds[Proposed].For.String() || l.For != "1h1m0s" {
|
|
t.Fatalf("the line is not this delivery's, past the table's bound: %+v", l)
|
|
}
|
|
if want := []string{"mesh/repo-check pending since 2026-10-11T00:42:01Z"}; !slices.Equal(l.Waiting, want) {
|
|
t.Fatalf("the line's checks waited on are %q, not %q", l.Waiting, want)
|
|
}
|
|
for _, want := range []string{"novox/mesh-controller#215", "ready", "mesh/repo-check pending since 2026-10-11T00:42:01Z", "recheck"} {
|
|
if !strings.Contains(l.Says, want) {
|
|
t.Fatalf("the line does not say %q: %s", want, l.Says)
|
|
}
|
|
}
|
|
if strings.Contains(l.Says, "No delivery of it is held") {
|
|
t.Fatalf("the line says no delivery is held of a head whose delivery is ready: %s", l.Says)
|
|
}
|
|
|
|
// The forge's status catches up: the check answered, and the line is gone.
|
|
w.forge.open = openPullsOf(t, readyAnswered)
|
|
w.h.SeeOpenPulls()
|
|
if lines := w.h.Stalled(); len(lines) != 0 {
|
|
t.Fatalf("a ready delivery whose check answered is still said: %+v", lines)
|
|
}
|
|
}
|
|
|
|
// **A delivery in rejected whose required check was never set is said** too: a lost status write leaves the pull
|
|
// request with no verdict to show, and nothing here asks again. It was announced — a delivery exists — so it is
|
|
// unanswered, never unannounced, though its head has no merge check at all.
|
|
func TestADeliveryInRejectedWhoseCheckIsMissingIsSaid(t *testing.T) {
|
|
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), time.Date(2026, 10, 11, 0, 43, 0, 0, time.UTC))
|
|
id := heldIn(t, w, "fail", Rejected)
|
|
w.forge.open = openPullsOf(t, strings.Replace(readyRepoCheckMissing,
|
|
`"mesh/merge-gate": {"state": "success", "description": "pass", "updated_at": "2026-10-11T00:42:01Z"}`,
|
|
`"mesh/merge-gate": {"state": "failure", "description": "fail", "updated_at": "2026-10-11T00:42:01Z"}`, 1))
|
|
w.h.SeeOpenPulls()
|
|
w.later(Bounds[Proposed].For + time.Minute)
|
|
w.h.SeeOpenPulls()
|
|
lines := w.h.Stalled()
|
|
if len(lines) != 1 || lines[0].ID != id || lines[0].State != Unanswered ||
|
|
!slices.Equal(lines[0].Waiting, []string{"mesh/repo-check never set"}) || !strings.Contains(lines[0].Says, "rejected") {
|
|
t.Fatalf("a rejected delivery whose required check was never set is not said: %+v", lines)
|
|
}
|
|
|
|
// No merge check on its head at all — both writes lost — is still a delivery's: unanswered, never unannounced.
|
|
w2 := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), time.Date(2026, 10, 11, 0, 43, 0, 0, time.UTC))
|
|
heldIn(t, w2, "fail", Rejected)
|
|
w2.forge.open = openPullsOf(t, `{"pulls": [{"base": "main", "head_sha": "`+readyHeadSHA+`", "number": 215,
|
|
"owner": "novox", "repo": "mesh-controller", "required": ["mesh/merge-gate", "mesh/repo-check"],
|
|
"statuses": ["mesh/delivery"], "checks": {"mesh/delivery": {"state": "failure", "description": "rejected"}}}]}`)
|
|
w2.h.SeeOpenPulls()
|
|
if lines := w2.h.Stalled(); len(lines) != 0 {
|
|
t.Fatalf("a delivery's head without a check is said at once, before the table's bound: %+v", lines)
|
|
}
|
|
w2.later(Bounds[Proposed].For + time.Minute)
|
|
w2.h.SeeOpenPulls()
|
|
lines = w2.h.Stalled()
|
|
if len(lines) != 1 || lines[0].State != Unanswered ||
|
|
!slices.Equal(lines[0].Waiting, []string{"mesh/merge-gate never set", "mesh/repo-check never set"}) {
|
|
t.Fatalf("a rejected delivery whose head has no merge check is not said as unanswered: %+v", lines)
|
|
}
|
|
}
|
|
|
|
// **A delivery in a state with a bound of its own is not said here**: a proposed one waits on its check under
|
|
// Bounds[Proposed] and H2's re-ask, and a second line for it would say the same thing twice.
|
|
func TestAProposedDeliveryIsSaidByItsOwnBoundOnly(t *testing.T) {
|
|
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), time.Date(2026, 10, 11, 0, 43, 0, 0, time.UTC))
|
|
w.h.PullUpdated(pr("novox/mesh-controller", 215, readyHeadSHA, "fix/215"))
|
|
id := IDOf("novox/mesh-controller", readyHeadSHA)
|
|
w.forge.open = openPullsOf(t, readyRepoCheckPending)
|
|
w.h.SeeOpenPulls()
|
|
w.later(3 * Bounds[Proposed].For)
|
|
w.h.SeeOpenPulls()
|
|
for _, l := range w.h.Stalled() {
|
|
if l.State == Unanswered || l.State == Unannounced {
|
|
t.Fatalf("a proposed delivery %s is said as a head without a delivery: %+v", id, l)
|
|
}
|
|
}
|
|
}
|
|
|
|
// **A ready delivery whose required checks answered is not said**, as the forge said every one on 2026-10-11.
|
|
func TestAReadyDeliveryWhoseChecksAnsweredIsNotSaid(t *testing.T) {
|
|
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), time.Date(2026, 10, 11, 0, 43, 0, 0, time.UTC))
|
|
heldIn(t, w, "pass", Ready)
|
|
w.forge.open = openPullsOf(t, readyAnswered)
|
|
w.h.SeeOpenPulls()
|
|
w.later(3 * Bounds[Proposed].For)
|
|
w.h.SeeOpenPulls()
|
|
if lines := w.h.Stalled(); len(lines) != 0 {
|
|
t.Fatalf("a ready delivery whose checks answered is said: %+v", lines)
|
|
}
|
|
}
|
|
|
|
// saidReady is a ready delivery of #215's head whose mesh/repo-check reads pending on the forge, said past the bound.
|
|
func saidReady(t *testing.T) (*world, string) {
|
|
t.Helper()
|
|
w := newWorldOver(t, newMemStore(), newFakeController(), newFakeForge(), time.Date(2026, 10, 11, 0, 43, 0, 0, time.UTC))
|
|
id := heldIn(t, w, "pass", Ready)
|
|
w.forge.open = openPullsOf(t, readyRepoCheckPending)
|
|
w.h.SeeOpenPulls()
|
|
w.later(Bounds[Proposed].For + time.Minute)
|
|
if lines := w.h.Stalled(); len(lines) != 1 || lines[0].ID != id || lines[0].State != Unanswered {
|
|
t.Fatalf("a ready delivery whose check is pending past the bound is not said: %+v", lines)
|
|
}
|
|
return w, id
|
|
}
|
|
|
|
// unansweredOf are the unanswered lines among what `stalled` says.
|
|
func unansweredOf(w *world) []StalledLine {
|
|
var out []StalledLine
|
|
for _, l := range w.h.Stalled() {
|
|
if l.State == Unanswered {
|
|
out = append(out, l)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// **A said line clears when its delivery's state changes** (novox/hq issue 441): a recheck proposes it again, under
|
|
// the proposed state's own bound, and a merge publishes it, past the pull request. Both clear the line at once —
|
|
// before the forge is read again, which may lag — and stay cleared once it is read.
|
|
func TestASaidLineClearsWhenItsDeliveryMovesOn(t *testing.T) {
|
|
t.Run("a recheck proposes it", func(t *testing.T) {
|
|
w, id := saidReady(t)
|
|
if _, err := w.h.Recheck(id, "its repo-check status was lost", "a person"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := w.state(id); s != Proposed {
|
|
t.Fatalf("a recheck left it %s", s)
|
|
}
|
|
if lines := unansweredOf(w); len(lines) != 0 {
|
|
t.Fatalf("a rechecked delivery, proposed again, is still said as unanswered: %+v", lines)
|
|
}
|
|
w.h.SeeOpenPulls()
|
|
if lines := unansweredOf(w); len(lines) != 0 {
|
|
t.Fatalf("a rechecked delivery is said again once the forge is read: %+v", lines)
|
|
}
|
|
})
|
|
t.Run("a merge publishes it", func(t *testing.T) {
|
|
w, id := saidReady(t)
|
|
w.h.WalkMoved(w.ctl.put(aWalk("plan-1", "novox/mesh-controller", merge, true)))
|
|
w.h.PullMerged(merged("novox/mesh-controller", 215, readyHeadSHA, merge))
|
|
if s := w.state(id); s != Published {
|
|
t.Fatalf("a merge left it %s", s)
|
|
}
|
|
if lines := unansweredOf(w); len(lines) != 0 {
|
|
t.Fatalf("a published delivery is still said as unanswered: %+v", lines)
|
|
}
|
|
w.h.SeeOpenPulls() // the forge's open list lags the merge: the head is still on it
|
|
if lines := unansweredOf(w); len(lines) != 0 {
|
|
t.Fatalf("a published delivery still on a lagging open list is said: %+v", lines)
|
|
}
|
|
})
|
|
}
|
|
|
|
// **A said line clears when its pull request leaves the open list** — closed, its event to this owner lost, so the
|
|
// delivery is still ready: the forge no longer lists it, and nothing is said of it.
|
|
func TestASaidLineClearsWhenItsPullRequestIsNoLongerOpen(t *testing.T) {
|
|
w, id := saidReady(t)
|
|
w.forge.open = nil
|
|
w.h.SeeOpenPulls()
|
|
if s := w.state(id); s != Ready {
|
|
t.Fatalf("the delivery moved to %s with no event: the case is not the one meant", s)
|
|
}
|
|
if lines := unansweredOf(w); len(lines) != 0 {
|
|
t.Fatalf("a pull request no longer open is still said: %+v", lines)
|
|
}
|
|
}
|