A pull request's head announced again from the bus's history at mesh-delivery's first start was proposed with no verdict and nothing ever asked its check: the controller had taken that announcement long before, and stalled raised it after an hour for the operator. A proposed delivery with no verdict and no check asked is now asked through delivery-check once it has waited past a grace longer than a check takes; an announcement carrying its head's decided gate status takes it. The proposed bound runs from the ask, and H2's close may re-ask once (a table row) before the delivery is the operator's.
213 lines
8.3 KiB
Go
213 lines
8.3 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 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:
|
|
if r.From[0] == Published {
|
|
d.Plan = &DeliveryPlan{Summary: "moves nothing"}
|
|
} else {
|
|
d.Walk = &WalkSeen{ID: "plan-1", State: walkDone}
|
|
}
|
|
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}
|
|
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; nothing off the trunk delivers.
|
|
if r.To == Published && (r.Event != EvMerged || len(r.From) != 1 || r.From[0] != Ready) {
|
|
t.Errorf("%v —%s→ published: only a ready delivery's merge publishes", r.From, r.Event)
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
}
|