Files
mesh-delivery/modules/mesh-delivery-sdk/steprun/run_output_test.go
T
jschoubben 7125a9f92f C1c-2a: the step runner says a run's answer in step-ended, inline or as an object, and serves it
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
2026-11-12 09:37:51 +01:00

84 lines
3.6 KiB
Go

package steprun
import (
"context"
"encoding/json"
"errors"
"strings"
"sync"
"testing"
"time"
sdkstep "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-sdk/buildstep"
)
// Design 47 G A-254: a build seat's step-ended of a run carries the run's answer, the last line of its program's standard output:
// inline as `output` up to the event bound, else as `output-object`, the object the holder keeps, which the verb `output` answers.
func TestStepEndedOfARunCarriesItsAnswer(t *testing.T) {
big := `"` + strings.Repeat("x", sdkstep.StepEndedInlineMost) + `"`
for _, c := range []struct {
name string
answer sdkstep.RunAnswer
keepErr error
outcome string
inline string
objectKept bool
said string
}{
{name: "a small answer is inline", answer: sdkstep.RunAnswer{Output: json.RawMessage(`{"verdict":true}`)}, outcome: sdkstep.OutcomePassed, inline: `{"verdict":true}`},
{name: "an answer over the event bound is an object", answer: sdkstep.RunAnswer{Output: json.RawMessage(big)}, outcome: sdkstep.OutcomePassed, objectKept: true},
{name: "a program that exits other than 0 failed, its answer kept", answer: sdkstep.RunAnswer{Exit: 3, Output: json.RawMessage(`"no"`), Said: "bad input"},
outcome: sdkstep.OutcomeFailed, inline: `"no"`, said: "the program exited 3: bad input"},
{name: "an answer the holder cannot keep is said", answer: sdkstep.RunAnswer{Output: json.RawMessage(big)}, keepErr: errors.New("store full"),
outcome: sdkstep.OutcomeFailed, said: "store full"},
} {
t.Run(c.name, func(t *testing.T) {
src, st, sy := NewFakeAsks(), newSteps(), &sayer{}
a := testAsk(sdkstep.AskCheck)
a.Check = nil
in, _ := sdkstep.RunInputOf("seat:mesh-catalog.input", []byte(`{"v":1}`))
commit := "0123456789abcdef0123456789abcdef01234567"
a.Run = &sdkstep.RunJob{Program: sdkstep.RunProgram{Repository: "novox/mesh-delivery", Commit: commit, Dir: "modules/x", Package: "./cmd/x"}, Input: in}
_ = src.PublishAsk(a)
var mu sync.Mutex
kept := map[string][]byte{}
p := &Puller{Source: src, Language: sdkstep.AskGo, Node: "shanks", Put: st.put, Capacity: func() int { return 1 },
PullWait: 10 * time.Millisecond, Heartbeat: time.Hour, Say: sy.say,
KeepOutput: func(object string, body []byte) error {
mu.Lock()
defer mu.Unlock()
if c.keepErr != nil {
return c.keepErr
}
kept[object] = body
return nil
},
Run: func(ctx context.Context, a sdkstep.StepAsk) (any, error) { return c.answer, nil }}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go p.Pull(ctx)
waitFor(t, "step-ended", func() bool { return len(sy.of("step-ended")) == 1 })
end := sy.of("step-ended")[0].body
if end["outcome"] != c.outcome {
t.Fatalf("outcome %v, want %s (%v)", end["outcome"], c.outcome, end)
}
if c.inline != "" && end["output"] != c.inline {
t.Fatalf("output %v, want %s inline", end["output"], c.inline)
}
if c.said != "" && !strings.Contains(end["said"].(string), c.said) {
t.Fatalf("said %v, want it to hold %q", end["said"], c.said)
}
mu.Lock()
defer mu.Unlock()
if c.objectKept {
name := sdkstep.OutputObjectName(a.ID)
if end["output-object"] != name || end["output"] != nil || string(kept[name]) != big {
t.Fatalf("end %v and kept %d bytes, want the object %s holding the whole answer and no inline output", end["output-object"], len(kept[name]), name)
}
} else if end["output-object"] != nil || len(kept) != 0 {
t.Fatalf("an object %v was named or kept (%d), want none", end["output-object"], len(kept))
}
})
}
}