194 lines
5.5 KiB
Go
194 lines
5.5 KiB
Go
|
// Copyright Earl Warren <contact@earl-warren.org>
|
||
|
// Copyright Loïc Dachary <loic@dachary.org>
|
||
|
// 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)
|
||
|
})
|
||
|
}
|
||
|
}
|