Main reverted G's half merge (#189, reverted by8afc3ebin #191) and then took the reader release's S1-S5, which copied G's code. G's form holds where they differ (process_bus.go, seats.go, state.go and their tests); the release's kept names stay so main's callers keep building: SeatViewPrefix beside SeatReadPrefix, and QueueInvoke's empty strings on not-ok (with its test TestQueueInvoke). The tree differs from G's headf30dee54only by those and the release's two tests (objects cap option, seat states and stream kinds). Builds: design 47 G (the milestone's sdk part, as atf30dee54) Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
110 lines
3.9 KiB
Go
110 lines
3.9 KiB
Go
package busstate
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"io"
|
|
"testing"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/buscalls"
|
|
)
|
|
|
|
// A bundle keeps objects in its own object store through the runtime (ADR 0595 §2, §9; A3's request for distribution's
|
|
// incoming): each call is the runtime's method of buscalls, naming the store by its local name, and answers as the
|
|
// runtime answered; a method that is no bus-state call is refused before the runtime is asked.
|
|
func subABundleKeepsObjectsThroughTheRuntime(t *testing.T) {
|
|
t.Setenv(stdio.ServedAs, "distribution")
|
|
inR, inW := io.Pipe()
|
|
outR, outW := io.Pipe()
|
|
type result struct {
|
|
what string
|
|
err error
|
|
got any
|
|
}
|
|
results := make(chan result, 8)
|
|
go func() {
|
|
_ = stdio.ServeOn("", []stdio.Tool{{Name: "keep", Run: func(map[string]any) (any, error) {
|
|
go func() {
|
|
in := ObjectsOf("incoming")
|
|
info, err := in.Put("t1.part0", []byte("hello"), "a part")
|
|
results <- result{"put", err, info.Digest}
|
|
data, err := in.Get("t1.part0")
|
|
results <- result{"get", err, string(data.Data)}
|
|
list, err := in.List()
|
|
results <- result{"list", err, len(list)}
|
|
results <- result{"delete", in.Delete("t1.part0"), nil}
|
|
fill, err := in.Fill()
|
|
results <- result{"fill", err, fill.MaxBytes}
|
|
_, err = stdio.BusState("mesh/state.get", nil)
|
|
results <- result{"refused", err, nil}
|
|
}()
|
|
return "started", nil
|
|
}}}, inR, outW)
|
|
}()
|
|
lines := bufio.NewScanner(outR)
|
|
next := func() map[string]any {
|
|
done := make(chan map[string]any, 1)
|
|
go func() {
|
|
if lines.Scan() {
|
|
var m map[string]any
|
|
_ = json.Unmarshal(lines.Bytes(), &m)
|
|
done <- m
|
|
}
|
|
}()
|
|
select {
|
|
case m := <-done:
|
|
return m
|
|
case <-time.After(3 * time.Second):
|
|
t.Fatal("the bundle said nothing")
|
|
return nil
|
|
}
|
|
}
|
|
send := func(m map[string]any) { b, _ := json.Marshal(m); _, _ = inW.Write(append(b, '\n')) }
|
|
send(map[string]any{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": map[string]any{"name": "keep"}})
|
|
answers := map[string]any{
|
|
buscalls.ObjectsPut: buscalls.ObjectInfo{Name: "t1.part0", Size: 5, Digest: "SHA-256=x"},
|
|
buscalls.ObjectsGet: buscalls.ObjectData{Info: buscalls.ObjectInfo{Name: "t1.part0"}, Data: []byte("hello")},
|
|
buscalls.ObjectsList: buscalls.ObjectsListed{Objects: []buscalls.ObjectInfo{{Name: "t1.part0"}}},
|
|
buscalls.ObjectsDelete: buscalls.Done{},
|
|
buscalls.ObjectsInfo: buscalls.Fill{Count: 0, MaxBytes: 20 << 30},
|
|
}
|
|
asked := map[string]map[string]any{}
|
|
for len(asked) < len(answers) {
|
|
m := next()
|
|
method, _ := m["method"].(string)
|
|
if method == "" {
|
|
continue // the tool's own answer
|
|
}
|
|
p, _ := m["params"].(map[string]any)
|
|
asked[method] = p
|
|
send(map[string]any{"jsonrpc": "2.0", "id": m["id"], "result": answers[method]})
|
|
}
|
|
want := map[string]any{"put": "SHA-256=x", "get": "hello", "list": 1, "delete": nil, "fill": uint64(20 << 30)}
|
|
for range 5 {
|
|
r := <-results
|
|
if r.err != nil || r.got != want[r.what] {
|
|
t.Errorf("%s answered %v, %v; want %v", r.what, r.got, r.err, want[r.what])
|
|
}
|
|
}
|
|
if r := <-results; r.err == nil {
|
|
t.Error("a call that is not on the bundle's bus state was asked of the runtime")
|
|
}
|
|
for method, p := range asked {
|
|
if p["objects"] != "incoming" {
|
|
t.Errorf("%s named the store %v, not its local name", method, p["objects"])
|
|
}
|
|
}
|
|
if p := asked[buscalls.ObjectsPut]; p["name"] != "t1.part0" || p["data"] != "aGVsbG8=" || p["description"] != "a part" {
|
|
t.Errorf("put asked %v", p)
|
|
}
|
|
}
|
|
|
|
// TestABundleKeepsObjectsThroughTheRuntime runs the tests of this package that were merged to keep the module within its test budget (design 47 G §419), each as a subtest.
|
|
func TestABundleKeepsObjectsThroughTheRuntime(t *testing.T) {
|
|
t.Run("ABundleKeepsObjectsThroughTheRuntime", subABundleKeepsObjectsThroughTheRuntime)
|
|
t.Run("TheLedgerUpkeepCallsAndTheirWords", subTheLedgerUpkeepCallsAndTheirWords)
|
|
t.Run("AValueLargerThanOneChunkIsPutAndGotBySession", subAValueLargerThanOneChunkIsPutAndGotBySession)
|
|
}
|