Files
mesh-delivery/modules/mesh-delivery-sdk/steprun/askers_test.go
T
jschoubben d29e1426d0 Asker rename R2: every asker of a build seat asks under its seat's own name, and its step is keyed so
A-163: the asker in `accept.<queue>.<asker>` is the asking seat's name (mesh-pipeline-runner,
mesh-catalog, mesh-wants, mesh-builds), never a nickname: the builder asks as mesh-builds (was
"builder"), the indexer's validation as mesh-catalog (was "indexer"), the assigner's runs as
mesh-wants (was "assigner"), the runner's steps on `accept.check.mesh-pipeline-runner` and
`accept.delivery.mesh-pipeline-runner` (were `accept.check.step` and `accept.delivery.step`, where
the holder read `step` as the asker). An asker naming none (the checker) asks on main's subject
(A-184). A build seat's queues are delivery, check and recheck alone (A-170, A-184): buildstep's
AskKinds holds no step, a holder takes a step from check or delivery, and the grants part makes a
worker per queue, never per kind. A holder keys a named ask's step `<asker>.<node>.<id>` and main's
`<node>.<id>` (§481, §483); readers watch their own name's keys. The composition step's step-progress
names the one caller mesh-wants' definition lists for step, read from the seat list (A-294).

Builds: design 47 G §481, §483, §543, §600, A-163, A-170, A-184, A-248, A-294

Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
2026-11-12 18:37:29 +01:00

94 lines
4.5 KiB
Go

package steprun
import (
"context"
"strings"
"testing"
"time"
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/seatdef"
)
func callersFor(table map[string][]string) func(string) ([]string, bool) {
return func(kind string) ([]string, bool) { c, ok := table[kind]; return c, ok }
}
// Design 47 G §543 (BG2, BG3, A-163, A-170): an ask whose kind its asker, the last token of its subject, is no caller of is
// answered error naming the kind and the asker and ended failed; a step on main's subject is refused as well; one the
// seat list does not judge yet is given back.
func TestPullRefusesAKindItsAskerMayNotAsk(t *testing.T) {
step := testAsk(sdkstep.AskStep)
step.Check = nil
step.Step = &sdkstep.StepRun{Pipeline: "p", Step: sdkstep.StepUnit, Module: "m", Artifact: "m:a", Commit: "0123456789abcdef0123456789abcdef01234567",
Fingerprint: "fp", Fetch: sdkstep.Fetch{Repository: "novox/x", Commit: "0123456789abcdef0123456789abcdef01234567"}}
build := testAsk(sdkstep.AskDelivery)
table := map[string][]string{"step": {"mesh-pipeline-runner"}, "delivery": {"mesh-builds"}}
for _, c := range []struct {
name, subject string
ask sdkstep.StepAsk
callers func(string) ([]string, bool)
want string // "" runs it, "refused" refuses it, "back" gives it back
}{
{"the indexer's step", "mesh.seat.node-build-go.accept.check.mesh-catalog", step, callersFor(table), "refused"},
{"the runner's step", "mesh.seat.node-build-go.accept.check.mesh-pipeline-runner", step, callersFor(table), ""},
{"a step on main's subject", "mesh.seat.node-build-go.accept.check", step, callersFor(table), "refused"},
{"a build on main's subject", "mesh.seat.node-build-go.accept.delivery", build, callersFor(table), ""},
{"a kind the seat list does not hold", "mesh.seat.node-build-go.accept.check.mesh-pipeline-runner", step, callersFor(nil), "back"},
} {
t.Run(c.name, func(t *testing.T) {
src, st, sy := NewFakeAsks(), newSteps(), &sayer{}
raw, _ := sdkstep.EncodeStepAsk(c.ask)
queue := c.ask.Kind
if queue == sdkstep.AskStep {
queue = sdkstep.AskCheck // a step rides the queue check (A-170, A-184)
}
src.PublishWithSubject(queue, raw, c.subject)
_, _, asker, _ := sdkstep.ParseAskSubjectBy(c.subject)
ran := make(chan struct{}, 1)
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 }, Callers: c.callers,
PullWait: 5 * time.Millisecond, Heartbeat: time.Hour, Say: sy.say, ProgressEvery: time.Hour,
Run: func(context.Context, sdkstep.StepAsk) (any, error) { ran <- struct{}{}; return nil, nil }}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go p.Pull(ctx)
switch c.want {
case "":
<-ran
// §481: the step is keyed under the asker its subject names, main's under its node alone.
key, _ := sdkstep.AskStepKey(asker, "shanks", c.ask.ID)
waitFor(t, "the step keyed "+key, func() bool { _, ok := st.get(key); return ok })
case "refused":
waitFor(t, "the ask ended", func() bool { _, termed, _ := src.Fates(); return len(termed) == 1 })
key, _ := sdkstep.AskStepKey(asker, "shanks", c.ask.ID)
s, _ := st.get(key)
if s.State != sdkstep.StepFailed || !strings.Contains(s.Reason, KindOf(c.ask)) || (strings.Contains(c.subject, "mesh-catalog") && !strings.Contains(s.Reason, asker)) {
t.Fatalf("the step = %+v, want failed naming the kind %s and the asker", s, KindOf(c.ask))
}
if end := sy.of("step-ended"); len(end) != 1 || end[0].body["outcome"] != sdkstep.OutcomeFailed {
t.Fatalf("step-ended = %+v, want failed", end)
}
select {
case <-ran:
t.Fatal("a refused ask ran")
default:
}
case "back":
waitFor(t, "the ask given back", func() bool { src.mu.Lock(); defer src.mu.Unlock(); return len(src.Delayed) > 0 })
}
})
}
}
func TestCallersFromDefinitionsReadsTheSeatsCallers(t *testing.T) {
def := seatdef.Definition{Seat: "node-build-go", Accepts: []seatdef.Message{
{Name: "step", Callers: &seatdef.Callers{List: []string{"seat:mesh-pipeline-runner"}}}}}
callers := CallersFromDefinitions(sdkstep.AskGo, func() map[string]seatdef.Definition { return map[string]seatdef.Definition{"node-build-go": def} })
if got, ok := callers("step"); !ok || len(got) != 1 || got[0] != "mesh-pipeline-runner" {
t.Fatalf("callers of step = %v, %v", got, ok)
}
if _, ok := callers("run"); ok {
t.Fatal("a kind the definition does not list is found")
}
}