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
84 lines
3.6 KiB
Go
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))
|
|
}
|
|
})
|
|
}
|
|
}
|