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
94 lines
4.5 KiB
Go
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")
|
|
}
|
|
}
|