The typed first view and the following of each half sit in the seats' client packages, built on go/views (design 47 G §245), so no part follows the assignment half by a follower of its own: a newer trunk revision heard is waited for, said in the status, asked and held; a candidate's, or one held, asks nothing. Builds: design 47 G §245, §365 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
117 lines
3.4 KiB
Go
117 lines
3.4 KiB
Go
package meshwantsclient
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
seat "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshwants"
|
|
)
|
|
|
|
// fakeWants answers the verb revision with its newest trunk revision, and counts each ask.
|
|
type fakeWants struct {
|
|
mu sync.Mutex
|
|
newest int64
|
|
asks int
|
|
}
|
|
|
|
func (f *fakeWants) call(key string, body any) (json.RawMessage, error) {
|
|
if key != RevisionVerb {
|
|
return nil, fmt.Errorf("asked %s", key)
|
|
}
|
|
if in := body.(seat.RevisionInput); in != (seat.RevisionInput{}) {
|
|
return nil, fmt.Errorf("asked %+v", in)
|
|
}
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.asks++
|
|
r := seat.AssignmentRevision{Revision: f.newest, Kind: kindTrunk, Placements: "p",
|
|
Catalogs: []seat.CatalogOf{{Branch: "main", Revision: 10 + f.newest}},
|
|
Entries: []seat.NodeEntryRef{{Node: "anchor", Module: "nats", Digest: fmt.Sprintf("d%d", f.newest)}}}
|
|
return json.Marshal(seat.RevisionAnswer{Revision: r})
|
|
}
|
|
|
|
func (f *fakeWants) set(newest int64) {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
f.newest = newest
|
|
}
|
|
|
|
func (f *fakeWants) asked() int {
|
|
f.mu.Lock()
|
|
defer f.mu.Unlock()
|
|
return f.asks
|
|
}
|
|
|
|
func revisionEvent(rev int64, kind string) []byte {
|
|
b, _ := json.Marshal(seat.AssignmentRevisionEvent{V: 1, Revision: rev, Kind: kind})
|
|
return b
|
|
}
|
|
|
|
// CT10 (design 47 G §245): a part takes a first view of the assignment half by the verb revision, then follows its
|
|
// event assignment-revision: a newer trunk revision is waited for, said in the status, and held; a candidate's, or one
|
|
// held, asks nothing; what is no such event is refused.
|
|
func TestTheAssignmentHalfIsAFirstViewThenItsEvents(t *testing.T) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
held := make(chan seat.AssignmentRevision, 8)
|
|
next := func() seat.AssignmentRevision {
|
|
t.Helper()
|
|
select {
|
|
case r := <-held:
|
|
return r
|
|
case <-ctx.Done():
|
|
t.Fatal("no revision held")
|
|
return seat.AssignmentRevision{}
|
|
}
|
|
}
|
|
f := &fakeWants{newest: 3}
|
|
a := NewAssignment(f.call)
|
|
a.Follower.Wait = time.Nanosecond
|
|
a.Held = func(r seat.AssignmentRevision) { held <- r }
|
|
if _, ok := a.Revision(); ok {
|
|
t.Fatal("a revision before its first view")
|
|
}
|
|
if err := a.Start(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if r := next(); r.Revision != 3 || len(r.Catalogs) != 1 || r.Catalogs[0].Revision != 13 {
|
|
t.Fatalf("first view holds %+v", r)
|
|
}
|
|
if v, ok := a.Follower.Get("anchor.nats"); !ok || string(v) == "" {
|
|
t.Errorf("the picture lacks anchor.nats: %s", v)
|
|
}
|
|
|
|
for _, ev := range [][]byte{revisionEvent(3, kindTrunk), revisionEvent(2, kindTrunk), revisionEvent(9, "candidate")} {
|
|
if err := a.Hear(ctx, ev); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if n := f.asked(); n != 1 {
|
|
t.Errorf("a revision held, an older one or a candidate's asked again: %d asks", n)
|
|
}
|
|
|
|
f.set(4)
|
|
if err := a.Hear(ctx, revisionEvent(4, kindTrunk)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if r := next(); r.Revision != 4 {
|
|
t.Fatalf("after assignment-revision 4 the view holds %d", r.Revision)
|
|
}
|
|
if s := a.Follower.Status(); s.View != "4" || s.WaitsFor != "" {
|
|
t.Errorf("status view %q, waits for %q", s.View, s.WaitsFor)
|
|
}
|
|
if r, ok := a.Revision(); !ok || r.Revision != 4 {
|
|
t.Errorf("Revision = %d, %v", r.Revision, ok)
|
|
}
|
|
|
|
for _, data := range []string{`{}`, `not json`, `{"kind":"trunk"}`} {
|
|
if err := a.Hear(ctx, []byte(data)); err == nil {
|
|
t.Errorf("Hear(%s) took it", data)
|
|
}
|
|
}
|
|
}
|