An ask's asker is the last token of its accept subject; the holder checks the kind against the callers the build seat's definition lists (Puller.Callers, CallersFromDefinitions), refuses an ask of another kind answering error naming the kind and the asker and ending it failed, takes only a build and a run on main's subject, and gives back an ask the seat list does not judge yet. A second worker per queue (AskNamedWorker, filtered to accept.<queue>.*) is pulled after main's; the holder says `named worker not bound` while its membership lists none. Builds: design 47 G §543, §544, §481, A-163, A-170 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
87 lines
4.1 KiB
Go
87 lines
4.1 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)
|
|
src.PublishWithSubject(c.ask.Kind, raw, 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
|
|
case "refused":
|
|
waitFor(t, "the ask ended", func() bool { _, termed, _ := src.Fates(); return len(termed) == 1 })
|
|
key, _ := sdkstep.AskStepKey("shanks", c.ask.ID)
|
|
s, _ := st.get(key)
|
|
asker := c.subject[strings.LastIndex(c.subject, ".")+1:]
|
|
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")
|
|
}
|
|
}
|