202 lines
9.3 KiB
Go
202 lines
9.3 KiB
Go
package index
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/delivery"
|
|
)
|
|
|
|
// ADR 0549 §2 (issue 1126): a merge changing one node's file writes that key alone and says one event; a merge with no
|
|
// placement change writes nothing and says nothing; a node whose file is gone loses its key and says its modules
|
|
// removed; a file that does not read leaves its key; a message that read no placement changes none.
|
|
func placementIsWrittenAtAMergeOnlyWhereItChanged(t *testing.T) {
|
|
ctx := context.Background()
|
|
ix, _, logs := newIndexer(t)
|
|
place := newMem()
|
|
ix.Placement = place
|
|
var said []delivery.PlacementChange
|
|
ix.Say = func(event string, body any) error {
|
|
if event != delivery.PlacementChangedEvent {
|
|
t.Errorf("said %s", event)
|
|
}
|
|
said = append(said, body.(delivery.PlacementChange))
|
|
return nil
|
|
}
|
|
merged := func(merge string, files map[string]string) delivery.Forge {
|
|
return delivery.Forge{V: delivery.Version, Kind: delivery.ForgeMerged, Repository: delivery.CatalogueRepository, Branch: "main",
|
|
Number: 7, Head: "h", Merge: merge, Root: "r", Trees: map[string]string{"": "t"}, Placements: files, PlacementsRead: true}
|
|
}
|
|
ace := `{"v":1,"node":"ace","modules":[{"module":"plex"}]}`
|
|
g14 := `{"v":1,"node":"g14","modules":[{"module":"zsh"}]}`
|
|
written := func(node string) (delivery.Placement, uint64) {
|
|
t.Helper()
|
|
v, ok := place.vals["placement."+node]
|
|
if !ok {
|
|
return delivery.Placement{}, 0
|
|
}
|
|
p, err := delivery.DecodePlacement(v)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return p, place.revs["placement."+node]
|
|
}
|
|
|
|
if err := ix.Apply(ctx, merged("m1", map[string]string{"ace": ace, "g14": g14})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if p, _ := written("ace"); p.Revision != "m1" || !reflect.DeepEqual(p.Assigned(), []string{"plex"}) || len(said) != 2 {
|
|
t.Fatalf("first merge: %+v, said %v", p, said)
|
|
}
|
|
_, aceRev := written("ace")
|
|
|
|
said = nil
|
|
if err := ix.Apply(ctx, merged("m2", map[string]string{"ace": ace, "g14": g14})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if p, rev := written("ace"); rev != aceRev || p.Revision != "m1" || len(said) != 0 {
|
|
t.Errorf("a merge with no placement change wrote %+v (revision %d, was %d) and said %v", p, rev, aceRev, said)
|
|
}
|
|
|
|
said = nil
|
|
if err := ix.Apply(ctx, merged("m3", map[string]string{"ace": ace, "g14": `{"v":1,"node":"g14","modules":[{"module":"zsh"},{"module":"xterm"}]}`})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, rev := written("ace"); rev != aceRev || len(said) != 1 || said[0].Node != "g14" || said[0].Revision != "m3" ||
|
|
!reflect.DeepEqual(said[0].Added, []string{"xterm"}) {
|
|
t.Errorf("one file changed: ace's revision %d (was %d), said %+v", rev, aceRev, said)
|
|
}
|
|
|
|
said = nil
|
|
if err := ix.Apply(ctx, merged("m4", map[string]string{"ace": ace, "g14": `{"v":1,"node":"ace","modules":[]}`})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if p, _ := written("g14"); len(p.Modules) != 2 || len(said) != 0 || !strings.Contains(logs.String(), "the placement file of %s is refused") {
|
|
t.Errorf("a file naming another node replaced the key: %+v, said %v, logs %s", p, said, logs)
|
|
}
|
|
|
|
said = nil
|
|
if err := ix.Apply(ctx, merged("m5", map[string]string{"ace": ace})); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, rev := written("g14"); rev != 0 || len(said) != 1 || !reflect.DeepEqual(said[0].Removed, []string{"xterm", "zsh"}) {
|
|
t.Errorf("a file removed: g14's key at revision %d, said %+v", rev, said)
|
|
}
|
|
|
|
said = nil
|
|
unread := merged("m6", nil)
|
|
unread.PlacementsRead = false
|
|
if err := ix.Apply(ctx, unread); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if p, _ := written("ace"); p.Revision != "m1" || len(said) != 0 {
|
|
t.Errorf("a message that read no placement changed it: %+v, %v", p, said)
|
|
}
|
|
|
|
// Issue 1126 (ADR 0549 §4), placement candidates: the trunk now places ace alone. A catalogue pull request writes
|
|
// `<group>.<node>` for each node whose file at its head differs from the trunk's, or that the head removes, and only
|
|
// when the value changes; a pull message that read no placement, or a pull request changing none, writes nothing; a
|
|
// file that does not read gets no key; a merge, a close or an open message not naming it deletes its keys.
|
|
cands := newMem()
|
|
ix.PlacementCandidates = cands
|
|
said = nil
|
|
pullOf := func(branch string, number int, head string, files map[string]string) delivery.Forge {
|
|
return delivery.Forge{V: delivery.Version, Kind: delivery.ForgePull, Repository: delivery.CatalogueRepository, Branch: branch,
|
|
Number: number, Head: head, Action: delivery.PullUpdated, Placements: files, PlacementsRead: files != nil}
|
|
}
|
|
candidate := func(key string) (delivery.PlacementCandidate, uint64, bool) {
|
|
t.Helper()
|
|
v, ok := cands.vals[key]
|
|
if !ok {
|
|
return delivery.PlacementCandidate{}, 0, false
|
|
}
|
|
c, err := delivery.DecodePlacementCandidate(v)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return c, cands.revs[key], true
|
|
}
|
|
keys := func() string { k, _ := cands.Keys(ctx); slices.Sort(k); return strings.Join(k, ",") }
|
|
g14New := `{"v":1,"node":"g14","modules":[{"module":"zsh"}]}`
|
|
for _, row := range []struct {
|
|
name string
|
|
f delivery.Forge
|
|
keys string
|
|
}{
|
|
{"a pull request adding g14", pullOf("feat/p", 9, h1, map[string]string{"ace": ace, "g14": g14New}), "feat/p.g14"},
|
|
{"the same head again", pullOf("feat/p", 9, h1, map[string]string{"ace": ace, "g14": g14New}), "feat/p.g14"},
|
|
{"a pull message that read no placement", pullOf("feat/p", 9, h2, nil), "feat/p.g14"},
|
|
{"a pull request changing no placement", pullOf("feat/q", 10, h1, map[string]string{"ace": ace}), "feat/p.g14"},
|
|
{"a new head removing ace", pullOf("feat/p", 9, h2, map[string]string{"g14": g14New}), "feat/p.ace,feat/p.g14"},
|
|
{"a head whose g14 file does not read", pullOf("feat/p", 9, h3, map[string]string{"g14": `{"v":1,"node":"ace","modules":[]}`}), "feat/p.ace"},
|
|
{"the head adding g14 again", pullOf("feat/p", 9, h2, map[string]string{"g14": g14New}), "feat/p.ace,feat/p.g14"},
|
|
{"a head the forge says changes no placement file", func() delivery.Forge { f := pullOf("feat/p", 9, h3, nil); f.DirsKnown = true; return f }(), ""},
|
|
{"the head adding g14 and removing ace again", pullOf("feat/p", 9, h2, map[string]string{"g14": g14New}), "feat/p.ace,feat/p.g14"},
|
|
{"an open message not naming it", delivery.Forge{V: delivery.Version, Kind: delivery.ForgeOpen, Open: []delivery.OpenPull{{Repository: delivery.CatalogueRepository, Number: 10}}}, ""},
|
|
{"the pull request heard again", pullOf("feat/p", 9, h2, map[string]string{"g14": g14New}), "feat/p.ace,feat/p.g14"},
|
|
{"its merge", func() delivery.Forge {
|
|
m := merged("m7", map[string]string{"g14": g14New})
|
|
m.Branch, m.Number = "feat/p", 9
|
|
return m
|
|
}(), ""},
|
|
} {
|
|
_, revBefore, _ := candidate("feat/p.g14")
|
|
if err := ix.Apply(ctx, row.f); err != nil {
|
|
t.Fatalf("%s: %v", row.name, err)
|
|
}
|
|
if got := keys(); got != row.keys {
|
|
t.Errorf("%s: keys %q, want %q", row.name, got, row.keys)
|
|
}
|
|
c, rev, ok := candidate("feat/p.g14")
|
|
switch row.name {
|
|
case "a pull request adding g14":
|
|
if !ok || c.Group != "feat/p" || c.Pull != (delivery.Head{Repository: delivery.CatalogueRepository, Number: 9, Head: h1}) || c.Placement == nil ||
|
|
!reflect.DeepEqual(c.Placement.Assigned(), []string{"zsh"}) {
|
|
t.Errorf("%s: %+v", row.name, c)
|
|
}
|
|
case "the same head again", "a pull message that read no placement", "a pull request changing no placement":
|
|
if rev != revBefore {
|
|
t.Errorf("%s: feat/p.g14 written again (revision %d, was %d)", row.name, rev, revBefore)
|
|
}
|
|
case "a new head removing ace":
|
|
if a, _, _ := candidate("feat/p.ace"); a.Placement != nil || a.Pull.Head != h2 || c.Pull.Head != h2 {
|
|
t.Errorf("%s: ace %+v, g14 at %s", row.name, a, c.Pull.Head)
|
|
}
|
|
}
|
|
}
|
|
if p, _ := written("g14"); len(p.Modules) != 1 || p.Revision != "m7" {
|
|
t.Errorf("the merge's trunk placement of g14: %+v", p)
|
|
}
|
|
|
|
// ADR 0559 §8, indexer_placement_reread: a reread with nothing changed writes nothing; one after a missed merge and a
|
|
// missed head writes those keys; one after a missed revert deletes the candidate; a forge that cannot say writes nothing.
|
|
g14Other := `{"v":1,"node":"g14","modules":[{"module":"zsh"},{"module":"htop"}]}`
|
|
for _, row := range []struct {
|
|
name string
|
|
read PlacementRead
|
|
err error
|
|
placement, cands []string
|
|
}{
|
|
{"nothing changed", PlacementRead{Trunk: "m7", Files: map[string]string{"g14": g14New}}, nil, []string{}, []string{}},
|
|
{"a missed merge and a missed head", PlacementRead{Trunk: "m8", Files: map[string]string{"g14": g14New, "ace": ace},
|
|
Pulls: []PullPlacement{{Branch: "feat/r", Number: 11, Head: h1, Files: map[string]string{"g14": g14Other, "ace": ace}}}}, nil,
|
|
[]string{"placement.ace"}, []string{"feat/r.g14"}},
|
|
{"a missed revert", PlacementRead{Trunk: "m8", Files: map[string]string{"g14": g14New, "ace": ace},
|
|
Pulls: []PullPlacement{{Branch: "feat/r", Number: 11, Head: h2, Files: map[string]string{"g14": g14New, "ace": ace}}}}, nil,
|
|
[]string{}, []string{"feat/r.g14"}},
|
|
{"a forge that cannot say", PlacementRead{}, context.DeadlineExceeded, []string{}, []string{}},
|
|
} {
|
|
ix.ReadPlacement = func(context.Context) (PlacementRead, error) { return row.read, row.err }
|
|
r := ix.RereadPlacement(ctx)
|
|
if !reflect.DeepEqual(r.Placement, row.placement) || !reflect.DeepEqual(r.Candidates, row.cands) || r.Answer == "" {
|
|
t.Errorf("%s: %+v", row.name, r)
|
|
}
|
|
}
|
|
if keys() != "" {
|
|
t.Errorf("after the reread of a revert, candidates %q", keys())
|
|
}
|
|
}
|