The puller's end of a run carries the run's program's last line of standard output as `output` up to StepEndedInlineMost, else keeps it with KeepOutput and names the object in `output-object`; a program that exits other than 0 ends failed. OutputTool serves the seat's verb `output` from the kept object, as the whole file the tool runner sends in chunks; OutputsStore names the store. Builds: design 47 G A-254, A-257, §602 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
77 lines
3.1 KiB
Go
77 lines
3.1 KiB
Go
package steprun
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
|
|
)
|
|
|
|
// 1887/A9a, ADR 0525 §10: a build seat's verbs are <seat>.current, .pause and .resume; current names the ask running,
|
|
// pause stops the puller taking asks, resume lets it take them again.
|
|
func TestSeatToolsCurrentPauseResume(t *testing.T) {
|
|
src, st := NewFakeAsks(), newSteps()
|
|
release := make(chan struct{})
|
|
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 },
|
|
PullWait: 10 * time.Millisecond, Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { <-release; return nil, nil }}
|
|
tools := map[string]func(map[string]any) (any, error){}
|
|
for _, tool := range SeatTools("node-build-go", "Go", p) {
|
|
tools[tool.Name] = tool.Run
|
|
}
|
|
for _, name := range []string{"node-build-go.current", "node-build-go.pause", "node-build-go.resume"} {
|
|
if tools[name] == nil {
|
|
t.Fatalf("no %s", name)
|
|
}
|
|
}
|
|
a := testAsk(sdkstep.AskDelivery)
|
|
_ = src.PublishAsk(a)
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
go p.Pull(ctx)
|
|
waitFor(t, "the ask running", func() bool { return p.InHand() == 1 })
|
|
out, _ := tools["node-build-go.current"](nil)
|
|
cur := out.(map[string]any)
|
|
running := cur["running"].([]RunningAsk)
|
|
if len(running) != 1 || running[0].ID != a.ID || running[0].Module != "m" || cur["capacity"] != 1 || cur["paused"] != false {
|
|
t.Fatalf("current = %+v", cur)
|
|
}
|
|
if out, err := tools["node-build-go.pause"](nil); err != nil || out.(map[string]any)["paused"] != true {
|
|
t.Fatalf("pause = %v, %v", out, err)
|
|
}
|
|
close(release)
|
|
_ = src.PublishAsk(testAsk(sdkstep.AskDelivery))
|
|
time.Sleep(50 * time.Millisecond)
|
|
if src.Waiting(sdkstep.AskDelivery) != 1 {
|
|
t.Fatal("a paused node took an ask")
|
|
}
|
|
if _, err := tools["node-build-go.resume"](nil); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
waitFor(t, "the ask taken after the resume", func() bool { return src.Waiting(sdkstep.AskDelivery) == 0 })
|
|
}
|
|
|
|
// Design 47 G A-254: `<seat>.output` answers a run's answer by its ask as the whole file (base64), which the tool runner sends in
|
|
// chunks (A-257); an ask this node keeps no answer of answers no chunks.
|
|
func TestOutputToolAnswersTheKeptAnswer(t *testing.T) {
|
|
kept := map[string][]byte{sdkstep.OutputObjectName("a1"): []byte(`{"verdict":"x"}`)}
|
|
tool := OutputTool("node-build-go", func(object string) ([]byte, error) { return kept[object], nil })
|
|
if tool.Name != "node-build-go.output" {
|
|
t.Fatalf("name %q", tool.Name)
|
|
}
|
|
got, err := tool.Run(map[string]any{"ask": "a1", "call": "c"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if m := got.(map[string]any); m["ask"] != "a1" || m["file"] != "eyJ2ZXJkaWN0IjoieCJ9" {
|
|
t.Fatalf("answer %v, want the ask and its answer's bytes as the file", got)
|
|
}
|
|
got, _ = tool.Run(map[string]any{"ask": "none", "call": "c"})
|
|
if m := got.(map[string]any); m["ask"] != "none" || m["chunks"] != 0 || m["file"] != nil {
|
|
t.Fatalf("answer %v, want no chunks and no file", got)
|
|
}
|
|
if _, err := tool.Run(map[string]any{"call": "c"}); err == nil {
|
|
t.Fatal("a call with no ask was answered")
|
|
}
|
|
}
|