Files
mesh-catalog/modules/mesh-delivery/cmd/mesh-delivery/table_test.go
T
jochen 2d1d4bf988
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
mesh/delivery superseded: a newer head of the same pull request
End a merge that moves nothing or that a later merge delivered, and lift a group hold its check answered (hq issues 319, 316)
A held delivery asked a person a question with no right answer: four hq merges that build nothing,
merges adding a module the controller sends nowhere, controller merges whose change a later walk of
the same trunk already delivered, and a group member held because its composed check had not yet
answered. Only stop could end them, which marks a delivered change as an error on its pull request.

- A merge whose plan moves no module (nothing, or only a new module sent nowhere) is delivered at once.
- A new final state, covered: every module a merge moves was sent by a finished walk of a later merge
  into the same trunk, which contains it. Shown as success on the pull request; a group counts it as
  delivered.
- A group member waits published while its composed check is asked and unanswered, and a member held
  for it is lifted back to published once it passes for the heads that merged.

Read again each tick, so the deliveries already held end by themselves after the rollout.
2026-10-09 02:24:26 +02:00

236 lines
9.4 KiB
Go

package main
import (
"errors"
"testing"
"time"
)
// The table walked (novox/hq ADR 0239 decision 2): every row it holds can be taken when its guard holds and
// is refused when it does not; every pair it does not hold is refused, by name; and the rules the table
// exists for are true of the table itself, not of the code around it.
// aDeliveryFor is a delivery whose facts make the row's guard hold.
func aDeliveryFor(r Row) (*Delivery, Facts) {
now := time.Date(2026, 10, 6, 12, 0, 0, 0, time.UTC)
d := &Delivery{ID: "novox/app@c0ffee000000", Repository: "novox/app", Commit: "c0ffee000000", Number: 7}
f := Facts{Now: now}
switch r.Event {
case EvAppeared:
d.MergedAs, d.HeldWhy = "c0ffee000000", "no pull request"
case EvAdopted, EvGo:
d.Walk = &WalkSeen{ID: "plan-1", State: walkRolling}
if r.From[0] == Held {
d.Walk.WaitedFor, d.Walk.GoBy = "mesh-delivery", "a person (jochen)"
}
case EvNewHead:
d.NewerHead = "beef"
case EvClosed:
d.ClosedUnmerged = true
case EvMerged:
d.MergedAs, d.Walk = "aaaa1111", &WalkSeen{ID: "plan-1", State: walkBuilding, Waits: true, WaitedFor: "mesh-delivery"}
case EvMergedUnchecked:
d.MergedAs = "aaaa1111"
case EvHistory, EvRetire:
d.MergedAs, d.FromForge = "aaaa1111", true
d.MergedAt, d.Created = now.Add(-48*time.Hour), now
if r.Event == EvHistory {
d.History = "merged two days before this owner heard of it"
} else {
f.Why, f.By = "history adopted at the switch", "jochen"
}
case EvChecked, EvAccepted:
d.Check = &Verdict{Gate: "pass"}
case EvRefused:
d.Check = &Verdict{Gate: "fail"}
case EvRecheck, EvRelease, EvStop:
f.Why, f.By = "a person's reason", "jochen"
if r.Event == EvStop && !r.Act {
f = Facts{Now: now}
d.Walk = &WalkSeen{ID: "plan-1", State: walkFailed, StoppedBy: "mesh-delivery for jochen"}
}
case EvReask:
f.Why, f.By = "delivery stalled", byH2
d.CheckAsk = &CheckAsk{ID: "check-1", At: now.Add(-2 * time.Hour), By: byOwner}
case EvHold:
d.HeldWhy, d.MergedAs = "its group's check did not pass", "aaaa1111"
case EvDone:
switch {
case r.From[0] == Published:
d.Plan = &DeliveryPlan{Summary: "moves nothing"}
case r.From[0] == Proposed:
d.MergedAs, d.Plan = "aaaa1111", &DeliveryPlan{Summary: "builds nothing"}
default:
d.Walk = &WalkSeen{ID: "plan-1", State: walkDone}
}
case EvCovered:
d.MergedAs, d.CoveredBy = "aaaa1111", "delivered with novox/app@bbbb (merged as bbbb2222, walk plan-2): app"
case EvLifted:
d.MergedAs, d.Lifted = "aaaa1111", "its group's composed check passed"
d.Walk = &WalkSeen{ID: "plan-1", State: walkBuilding, Waits: true, WaitedFor: "mesh-delivery"}
case EvFailed:
d.Walk = &WalkSeen{ID: "plan-1", State: walkFailed}
case EvSuperseded:
d.Walk = &WalkSeen{ID: "plan-1", State: walkSuperseded}
}
return d, f
}
func TestEveryRowOfTheTableIsTakenWhenItsGuardHoldsAndRefusedWhenNot(t *testing.T) {
for _, r := range Table {
for _, from := range r.From {
d, f := aDeliveryFor(r)
d.State = from
got, err := Apply(d, r.Event, r.Act, f)
if err != nil {
t.Errorf("%s —%s→ %s was refused with its guard holding: %v", orNothing(from), r.Event, r.To, err)
continue
}
if d.State != r.To || got.From != from || got.To != r.To || len(d.Transitions) != 1 {
t.Errorf("%s —%s→ left it %s, transition %+v", orNothing(from), r.Event, d.State, got)
}
// The guard not holding: a delivery with no facts.
bare := &Delivery{ID: "novox/app@bare", State: from, Check: &Verdict{Gate: "pass"}}
if r.Event == EvAccepted || r.Event == EvChecked {
bare.Check = nil
if r.Event == EvAccepted {
bare.Check = &Verdict{Gate: "fail"}
}
}
if r.Event == EvRefused {
bare.Check = &Verdict{Gate: "pass"}
}
if _, err := Apply(bare, r.Event, r.Act, Facts{Now: f.Now}); err == nil {
t.Errorf("%s —%s→ %s was taken with no fact for its guard", orNothing(from), r.Event, r.To)
} else if bare.State != from || len(bare.Transitions) != 0 {
t.Errorf("a refused transition moved the delivery to %s", bare.State)
}
}
}
}
func TestEveryPairTheTableDoesNotHoldIsRefusedByName(t *testing.T) {
events := []Event{EvAnnounced, EvAppeared, EvAdopted, EvChecked, EvAccepted, EvRefused, EvRecheck, EvReask, EvNewHead,
EvClosed, EvMerged, EvMergedUnchecked, EvGo, EvHold, EvRelease, EvDone, EvFailed, EvSuperseded, EvStop,
EvHistory, EvRetire, EvCovered, EvLifted}
for _, from := range append([]State{None}, AllStates...) {
for _, ev := range events {
for _, act := range []bool{false, true} {
if rowFor(from, ev, act) != nil {
continue
}
d := &Delivery{ID: "novox/app@c0ffee", State: from, MergedAs: "x", NewerHead: "y", Check: &Verdict{Gate: "pass"},
Walk: &WalkSeen{ID: "p", State: walkDone}, HeldWhy: "h", ClosedUnmerged: true, Number: 1, Commit: "c"}
_, err := Apply(d, ev, act, Facts{Now: time.Now(), Why: "w", By: "b"})
var refused ErrRefused
if !errors.As(err, &refused) || refused.Event != ev || refused.From != from {
t.Errorf("%s —%s (act %v)→ was not refused by name: %v", orNothing(from), ev, act, err)
}
if d.State != from {
t.Errorf("a pair the table does not hold moved %s to %s", orNothing(from), d.State)
}
}
}
}
}
// The rules the table is for, read off the table.
func TestTheTableKeepsItsRules(t *testing.T) {
for _, r := range Table {
for _, from := range r.From {
// Nothing leaves a final state.
if from.Final() {
t.Errorf("%s is final and the table leaves it by %s", from, r.Event)
}
// Nothing goes back before the trunk once a delivery is on it.
if (from == Published || from == Delivering || from == Held) &&
(r.To == Proposed || r.To == Checked || r.To == Ready || r.To == Rejected) {
t.Errorf("%s goes back to %s by %s", from, r.To, r.Event)
}
}
// The trunk rule: only a ready delivery is published, by its merge — or a held one published again when
// what held it is gone (issue 316); nothing off the trunk delivers.
if r.To == Published && !(r.Event == EvMerged && len(r.From) == 1 && r.From[0] == Ready) &&
!(r.Event == EvLifted && len(r.From) == 1 && r.From[0] == Held) {
t.Errorf("%v —%s→ published: only a ready delivery's merge publishes", r.From, r.Event)
}
// Nothing a person did not decide ends a delivery as stopped or failed by observation from held.
if contains(r.From, Held) && !r.Act && (r.To == Stopped || r.To == Failed) {
t.Errorf("held —%s→ %s by observation", r.Event, r.To)
}
if r.To == Delivering {
for _, from := range r.From {
if from == Proposed || from == Checked || from == Ready || from == Rejected {
t.Errorf("%s delivers by %s without reaching the trunk", from, r.Event)
}
}
}
// Held is left only by a person or a walk that started on a word that was not this owner's.
if contains(r.From, Held) && r.To == Delivering && r.Event == EvRelease && !r.Act {
t.Error("a held delivery is released by observation")
}
}
// Every state that is not final has a bound or is the operator's to wait in, and every H2 transition is
// a row the table holds from that state: observed once the walk's record is read again, or H2's own ask.
for state, b := range Bounds {
for _, ev := range b.H2 {
if rowFor(state, ev, false) == nil && rowFor(state, ev, true) == nil {
t.Errorf("H2 may take %s from %s, which the table does not hold", ev, state)
}
}
}
// H2 re-asks a check once: a second re-ask is refused by the table itself.
d0 := &Delivery{ID: "novox/app@c0", State: Proposed, Number: 1, CheckAsk: &CheckAsk{ID: "check-1", Reasked: 1}}
if _, err := Apply(d0, EvReask, true, Facts{Now: time.Now(), By: byH2, Why: "stalled"}); err == nil {
t.Fatal("a check re-asked once was re-asked again")
}
// A ready delivery off the trunk is refused publication, whatever else is true of it.
d := &Delivery{ID: "novox/app@c0", State: Ready, Walk: &WalkSeen{ID: "plan-x", State: walkRolling}}
if _, err := Apply(d, EvMerged, false, Facts{Now: time.Now()}); err == nil {
t.Fatal("a commit not on the trunk was published")
}
}
func TestTheMachineStepTable(t *testing.T) {
for _, c := range []struct {
from, to StepState
may bool
}{
{"", StepSent, true}, {StepSent, StepJudging, true}, {StepJudging, StepPassed, true},
{StepJudging, StepFailed, true}, {StepFailed, StepRolledBack, true}, {StepSent, StepFailed, true},
{StepPassed, StepFailed, false}, {StepRolledBack, StepPassed, false}, {StepPassed, StepSent, false},
{StepJudging, StepSent, false}, {StepFailed, StepPassed, false},
} {
if StepMay(c.from, c.to) != c.may {
t.Errorf("%q → %q: may %v, wanted %v", c.from, c.to, !c.may, c.may)
}
}
}
// settle stands a delivery where its facts put it, whatever order they came in.
func TestSettleIsTheOrderOfTheFactsNotOfTheirArrival(t *testing.T) {
now := time.Now()
d := &Delivery{ID: "novox/app@c0", Number: 1, Commit: "c0"}
if _, err := Apply(d, EvAnnounced, false, Facts{Now: now}); err != nil {
t.Fatal(err)
}
// The verdict, the merge and a finished walk, all known before anything settled.
d.Check = &Verdict{Gate: "pass"}
d.MergedAs = "m1"
d.Walk = &WalkSeen{ID: "plan-1", State: walkDone}
ts := settle(d, Facts{Now: now})
var path []State
for _, t := range ts {
path = append(path, t.To)
}
want := []State{Checked, Ready, Published, Delivering, Delivered}
if len(path) != len(want) {
t.Fatalf("settled through %v, wanted %v", path, want)
}
for i := range want {
if path[i] != want[i] {
t.Fatalf("settled through %v, wanted %v", path, want)
}
}
}