// Copyright Earl Warren // Copyright Loïc Dachary // SPDX-License-Identifier: MIT package generic import ( "context" "sort" "testing" "code.forgejo.org/f3/gof3/v3/id" "code.forgejo.org/f3/gof3/v3/kind" "code.forgejo.org/f3/gof3/v3/path" "code.forgejo.org/f3/gof3/v3/tree/generic" "code.forgejo.org/f3/gof3/v3/tree/memory" "github.com/stretchr/testify/assert" ) func NewMemoryTree(ctx context.Context, name string) generic.TreeInterface { return generic.GetFactory("memory")(ctx, memory.NewOptions(memory.NewIDAllocatorGenerator(name))) } func TestMemoryTreeIDAllocator(t *testing.T) { ctx := context.Background() name := "T" id := "THEID" for _, testCase := range []struct { idAllocator memory.IDAllocatorInterface setID bool expectedID string }{ { idAllocator: memory.NewIDAllocatorGenerator(name), setID: false, expectedID: name + "-A", }, { idAllocator: memory.NewIDAllocatorNull(), setID: true, expectedID: id, }, } { t.Run(testCase.expectedID, func(t *testing.T) { tree := generic.GetFactory("memory")(ctx, memory.NewOptions(testCase.idAllocator)) node := tree.GetRoot().CreateChild(ctx) f := memory.NewFormat("") if testCase.setID { f.SetID(id) } node.FromFormat(f) node.Upsert(ctx) assert.EqualValues(t, testCase.expectedID, node.GetID()) }) } } func testTreeBuild(t *testing.T, tree generic.TreeInterface, maxDepth int) { ctx := context.Background() insert := func(tree generic.TreeInterface, parent generic.NodeInterface) generic.NodeInterface { node := parent.CreateChild(ctx) node.Upsert(ctx) memory.SetContent(node, "content "+node.GetID().String()) node.Upsert(ctx) return node } var populate func(depth int, tree generic.TreeInterface, parent generic.NodeInterface) populate = func(depth int, tree generic.TreeInterface, parent generic.NodeInterface) { if depth >= maxDepth { return } depth++ for i := 1; i <= 3; i++ { node := insert(tree, parent) populate(depth, tree, node) } } populate(0, tree, insert(tree, tree.GetRoot())) } func TestMemoryTreeBuild(t *testing.T) { ctx := context.Background() verify := func(tree generic.TreeInterface, expected []string) { collected := make([]string, 0, 10) collect := func(ctx context.Context, p path.Path, node generic.NodeInterface) { if node.GetKind() == kind.KindRoot { return } p = p.Append(node) collected = append(collected, p.String()+":"+memory.GetContent(node)) } tree.WalkAndGet(ctx, generic.NewWalkOptions(collect)) sort.Strings(expected) sort.Strings(collected) assert.EqualValues(t, expected, collected) } for _, testCase := range []struct { name string build func(tree generic.TreeInterface) operations func(tree generic.TreeInterface) expected []string }{ { name: "full tree", build: func(tree generic.TreeInterface) { testTreeBuild(t, tree, 2) }, operations: func(tree generic.TreeInterface) {}, expected: []string{"/T-A/T-B/T-C:content T-C", "/T-A/T-B/T-D:content T-D", "/T-A/T-B/T-E:content T-E", "/T-A/T-B:content T-B", "/T-A/T-F/T-G:content T-G", "/T-A/T-F/T-H:content T-H", "/T-A/T-F/T-I:content T-I", "/T-A/T-F:content T-F", "/T-A/T-J/T-K:content T-K", "/T-A/T-J/T-L:content T-L", "/T-A/T-J/T-M:content T-M", "/T-A/T-J:content T-J", "/T-A:content T-A"}, }, { name: "scenario 1", build: func(tree generic.TreeInterface) { testTreeBuild(t, tree, 2) }, operations: func(tree generic.TreeInterface) { root := tree.GetRoot() id0 := id.NewNodeID("T-A") root.List(ctx) zero := root.GetChild(id0) assert.False(t, generic.NilNode == zero) assert.True(t, zero == zero.Get(ctx)) id1 := id.NewNodeID("T-B") zero.List(ctx) one := zero.GetChild(id1) assert.False(t, generic.NilNode == one) one.Get(ctx) memory.SetContent(one, "other one") one.Upsert(ctx) id2 := id.NewNodeID("T-F") two := zero.GetChild(id2) two.Delete(ctx) two.Delete(ctx) assert.True(t, generic.NilNode == zero.GetChild(id2)) }, expected: []string{"/T-A/T-B/T-C:content T-C", "/T-A/T-B/T-D:content T-D", "/T-A/T-B/T-E:content T-E", "/T-A/T-B:other one", "/T-A/T-J/T-K:content T-K", "/T-A/T-J/T-L:content T-L", "/T-A/T-J/T-M:content T-M", "/T-A/T-J:content T-J", "/T-A:content T-A"}, }, { name: "scenario 2", build: func(tree generic.TreeInterface) { testTreeBuild(t, tree, 0) }, operations: func(tree generic.TreeInterface) { root := tree.GetRoot() id0 := id.NewNodeID("T-A") root.List(ctx) zero := root.GetChild(id0) assert.False(t, generic.NilNode == zero) zero.Get(ctx) one := zero.CreateChild(ctx) one.Upsert(ctx) memory.SetContent(one, "ONE") one.Upsert(ctx) two := one.CreateChild(ctx) two.Upsert(ctx) memory.SetContent(two, "SOMETHING") two.Upsert(ctx) memory.SetContent(two, "ONE/TWO") two.Upsert(ctx) one.DeleteChild(two.GetID()) two.Get(ctx) three := two.CreateChild(ctx) three.Upsert(ctx) memory.SetContent(three, "ONE/THREE") three.Upsert(ctx) three.Delete(ctx) }, expected: []string{"/T-A/T-B/T-C:ONE/TWO", "/T-A/T-B:ONE", "/T-A:content T-A"}, }, } { t.Run(testCase.name, func(t *testing.T) { tree := NewMemoryTree(ctx, "T") tree.Trace("========== BUILD") testCase.build(tree) tree.Trace("========== OPERATIONS") testCase.operations(tree) verify(tree, testCase.expected) tree.Trace("========== VERIFY RELOAD") tree.Clear(ctx) verify(tree, testCase.expected) }) } }