Merge branch 'feat/47-G-fix11-a5' into feat/47-G-join11
# Conflicts: # node-tools/internal/firstview/firstview_test.go # node-tools/internal/runtime/announce_test.go # node-tools/internal/runtime/busstate.go # node-tools/internal/runtime/ledgers_test.go
This commit is contained in:
@@ -32,12 +32,14 @@ type owners struct {
|
||||
// catalog is the revision of the watched branch main the seat mesh-catalog answers, and modules its entries
|
||||
catalog int64
|
||||
modules []string
|
||||
// holder is the module among them that claims the assigner seat
|
||||
holder string
|
||||
}
|
||||
|
||||
func newOwners() *owners {
|
||||
return &owners{state: map[string]map[string]string{"assignments": {"anchor": `{"p":1}`}, "seat-holders": {"seat": `{"h":1}`}},
|
||||
rev: map[string]uint64{"assignments": 4, "seat-holders": 2}, asked: map[string]int{}, finds: map[string]bool{},
|
||||
catalog: 3, modules: []string{"web"}}
|
||||
catalog: 3, modules: []string{"web", fixtureHolder}, holder: fixtureHolder}
|
||||
}
|
||||
|
||||
// Write and Clear are the runner's finding writer in a test: the findings written and not cleared, by their key.
|
||||
@@ -71,7 +73,11 @@ func (o *owners) ask(address string, _ any) (json.RawMessage, error) {
|
||||
case "seat:mesh-catalog.revision":
|
||||
a := catalogseat.RevisionAnswer{Revision: &catalogseat.CatalogRevision{Revision: o.catalog, Kind: meshcatalogclient.KindTrunk, Branch: "main"}}
|
||||
for _, m := range o.modules {
|
||||
a.Entries = append(a.Entries, catalogseat.CatalogEntry{Module: m, Manifest: map[string]any{"module": m, "tools": []string{m + "_check"}}})
|
||||
e := catalogseat.CatalogEntry{Module: m, Manifest: map[string]any{"module": m, "tools": []string{m + "_check"}}}
|
||||
if m == o.holder {
|
||||
e.Serves = []string{AssignerSeat + ".assignments", AssignerSeat + ".seat-holders"}
|
||||
}
|
||||
a.Entries = append(a.Entries, e)
|
||||
}
|
||||
return json.Marshal(a)
|
||||
}
|
||||
@@ -94,14 +100,10 @@ func (o *owners) say(t *testing.T, state, key, value string, revision uint64, ga
|
||||
}
|
||||
}
|
||||
|
||||
func owner(state string) string {
|
||||
for _, s := range Specs {
|
||||
if s.State == state {
|
||||
return s.Owner
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
// fixtureHolder is the module the fixture catalog half says claims the assigner seat: no name is in the code.
|
||||
const fixtureHolder = "assigner-fixture"
|
||||
|
||||
func owner(state string) string { return fixtureHolder }
|
||||
|
||||
func eventually(t *testing.T, what string, ok func() bool) {
|
||||
t.Helper()
|
||||
@@ -127,39 +129,39 @@ func TestTheRunnerHoldsTheFourStatesFromTheOwnersFirstViewsAndTheirEvents(t *tes
|
||||
defer stop()
|
||||
|
||||
// Down: nothing is claimed, and past the bound each unanswered view is a finding.
|
||||
if _, err := v.State("mesh-delivery-assigner.assignments"); err == nil || !strings.Contains(err.Error(), "not been answered") {
|
||||
t.Fatalf("a state its owner has not answered is claimed: %v", err)
|
||||
if _, err := v.State(AssignmentsRead); err == nil || !strings.Contains(err.Error(), "names no holder") {
|
||||
t.Fatalf("a state whose holder is not known is claimed: %v", err)
|
||||
}
|
||||
eventually(t, "no finding past the bound", func() bool { o.mu.Lock(); defer o.mu.Unlock(); return len(o.finds) == 4 })
|
||||
eventually(t, "no finding past the bound", func() bool { o.mu.Lock(); defer o.mu.Unlock(); return len(o.finds) == 2 })
|
||||
|
||||
// Up: answered, the findings are cleared, and the pictures hold the owners' values.
|
||||
o.mu.Lock()
|
||||
o.down = false
|
||||
o.mu.Unlock()
|
||||
eventually(t, "not answered", func() bool { _, err := v.State("mesh-delivery-assigner.seat-holders"); return err == nil })
|
||||
eventually(t, "not answered", func() bool { _, err := v.State(SeatHoldersRead); return err == nil })
|
||||
eventually(t, "findings not cleared", func() bool { o.mu.Lock(); defer o.mu.Unlock(); return len(o.finds) == 0 })
|
||||
if got, _ := v.Entry("mesh-delivery-assigner.seat-holders", "none"); got != nil {
|
||||
if got, _ := v.Entry(AssignerSeat+".seat-holders", "none"); got != nil {
|
||||
t.Errorf("an absent key is %q", got)
|
||||
}
|
||||
|
||||
// The catalog half is followed by the shared branch follower on the runner's own consumer: the gate's source names main,
|
||||
// main's view is adopted, and its entries are the modules, each with its manifest (design 47 G §196, §614).
|
||||
eventually(t, "the catalog half holds no modules", func() bool { m, err := v.Modules(); return err == nil && len(m) == 1 })
|
||||
eventually(t, "the catalog half holds no modules", func() bool { m, err := v.Modules(); return err == nil && len(m) == 2 })
|
||||
m, _ := v.Modules()
|
||||
if m["web"].Manifest["module"] != "web" {
|
||||
t.Errorf("the module web is %+v", m["web"])
|
||||
}
|
||||
// A catalog-revision of the branch is heard on the same consumer and asks the view once more.
|
||||
o.mu.Lock()
|
||||
o.catalog, o.modules = 4, []string{"web", "db"}
|
||||
o.catalog, o.modules = 4, []string{"web", "db", fixtureHolder}
|
||||
o.mu.Unlock()
|
||||
ev, _ := json.Marshal(catalogseat.CatalogRevisionEvent{V: 1, Branch: "main", Kind: meshcatalogclient.KindTrunk, Revision: 4})
|
||||
if err := o.deliver(bus.Envelope{Key: catalogseat.Seat + "." + catalogseat.EventCatalogRevision, Body: ev}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
eventually(t, "the catalog-revision was not taken", func() bool { m, err := v.Modules(); return err == nil && len(m) == 2 })
|
||||
eventually(t, "the catalog-revision was not taken", func() bool { m, err := v.Modules(); return err == nil && len(m) == 3 })
|
||||
// And the halves by branch, with their revision, which mesh_wait names checking an emitter (design 34 every-event §14).
|
||||
if h, err := v.Halves(); err != nil || h["main"].Revision.Revision != 4 || len(h["main"].Entries) != 2 {
|
||||
if h, err := v.Halves(); err != nil || h["main"].Revision.Revision != 4 || len(h["main"].Entries) != 3 {
|
||||
t.Errorf("the halves: %+v, %v", h, err)
|
||||
}
|
||||
|
||||
@@ -170,7 +172,7 @@ func TestTheRunnerHoldsTheFourStatesFromTheOwnersFirstViewsAndTheirEvents(t *tes
|
||||
o.say(t, "assignments", "laptop", `{"p":0}`, 5, false)
|
||||
o.say(t, "assignments", "anchor", "", 6, false)
|
||||
// (a deleted mark carries no value: Deleted is the encoding's, set below)
|
||||
if got, _ := v.Entry("mesh-delivery-assigner.assignments", "laptop"); string(got) != `{"p":2}` {
|
||||
if got, _ := v.Entry(AssignerSeat+".assignments", "laptop"); string(got) != `{"p":2}` {
|
||||
t.Errorf("the next revision is not applied: %q", got)
|
||||
}
|
||||
if n := asked("assignments"); n != before {
|
||||
@@ -188,7 +190,7 @@ func TestTheRunnerHoldsTheFourStatesFromTheOwnersFirstViewsAndTheirEvents(t *tes
|
||||
assignments = s
|
||||
}
|
||||
}
|
||||
if assignments.Owner != "mesh-delivery-assigner" || assignments.Tool != "assignments" || assignments.Asks < 2 || assignments.Answered.IsZero() {
|
||||
if assignments.Owner != fixtureHolder || assignments.Tool != "assignments" || assignments.Asks < 2 || assignments.Answered.IsZero() {
|
||||
t.Errorf("the status of assignments is %+v", assignments)
|
||||
}
|
||||
manifests := asked("seat-holders")
|
||||
@@ -228,13 +230,19 @@ func TestTheFirstViewsAreTheManifestsViews(t *testing.T) {
|
||||
t.Errorf("the first view %s is read by no name here", s.Name())
|
||||
}
|
||||
delete(want, s.Name())
|
||||
if view := s.Owner + ".views." + s.Tool; !slices.Contains(m.Consumes, view) {
|
||||
// the family is named by the seat, never by its holder (design 47 G §616)
|
||||
if view := "seat:" + AssignerSeat + ".views." + s.Tool; !slices.Contains(m.Consumes, view) {
|
||||
t.Errorf("the first view %s has no family in the manifest's consumes %v", view, m.Consumes)
|
||||
}
|
||||
}
|
||||
if len(want) > 0 {
|
||||
t.Errorf("no first view reads %v", want)
|
||||
}
|
||||
for _, c := range m.Consumes {
|
||||
if strings.Contains(c, "assigner") && !strings.HasPrefix(c, "seat:") {
|
||||
t.Errorf("the manifest names the assigner by a module: %s", c)
|
||||
}
|
||||
}
|
||||
// the catalog half's follower hears these events and asks these verbs, and the manifest says so (design 47 G §614)
|
||||
for _, ev := range []string{"mesh-catalog.catalog-revision", "mesh-catalog.registration-changed", "mesh-delivery.source-moved"} {
|
||||
if !slices.Contains(m.Consumes, ev) {
|
||||
@@ -247,3 +255,42 @@ func TestTheFirstViewsAreTheManifestsViews(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// design 47 G §616: the holder of the assigner seat is whichever module the catalog half says serves it; when another one
|
||||
// claims it the views are of the new holder, and when none does the seat's names answer that the catalog half names no holder.
|
||||
func TestTheAssignersViewsFollowTheModuleThatClaimsTheSeat(t *testing.T) {
|
||||
o := newOwners()
|
||||
deps := Deps{Module: "node-tools", Ask: o.ask, Bound: time.Second, Wait: 10 * time.Millisecond,
|
||||
Consume: func(deliver func(bus.Envelope) error) (func(), error) { o.deliver = deliver; return func() {}, nil }, Findings: o}
|
||||
v, stop := StartWith(context.Background(), deps, func(string, ...any) {})
|
||||
defer stop()
|
||||
eventually(t, "not answered", func() bool { _, err := v.State(AssignmentsRead); return err == nil })
|
||||
ownerOf := func() string {
|
||||
for _, s := range v.Status() {
|
||||
if s.State == "assignments" {
|
||||
return s.Owner
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
if ownerOf() != fixtureHolder {
|
||||
t.Fatalf("the views are of %q", ownerOf())
|
||||
}
|
||||
revise := func(rev int64, holder string, modules ...string) {
|
||||
o.mu.Lock()
|
||||
o.catalog, o.modules, o.holder = rev, modules, holder
|
||||
o.mu.Unlock()
|
||||
ev, _ := json.Marshal(catalogseat.CatalogRevisionEvent{V: 1, Branch: "main", Kind: meshcatalogclient.KindTrunk, Revision: rev})
|
||||
if err := o.deliver(bus.Envelope{Key: catalogseat.Seat + "." + catalogseat.EventCatalogRevision, Body: ev}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
revise(4, "other-holder", "web", "other-holder")
|
||||
eventually(t, "the views did not move to the new holder", func() bool { return ownerOf() == "other-holder" })
|
||||
eventually(t, "not answered", func() bool { _, err := v.State(SeatHoldersRead); return err == nil })
|
||||
revise(5, "", "web")
|
||||
eventually(t, "the views stayed after the claim went", func() bool {
|
||||
_, err := v.State(AssignmentsRead)
|
||||
return err != nil && strings.Contains(err.Error(), "names no holder")
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user