Adding upstream version 3.10.8.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
37e9b6d587
commit
03bfe4079e
356 changed files with 28857 additions and 0 deletions
74
tree/tests/generic/compare_test.go
Normal file
74
tree/tests/generic/compare_test.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"code.forgejo.org/f3/gof3/v3/id"
|
||||
"code.forgejo.org/f3/gof3/v3/kind"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/memory"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCompare(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
aTree := NewMemoryTree(ctx, "O")
|
||||
testTreeBuild(t, aTree, 2)
|
||||
bTree := NewMemoryTree(ctx, "O")
|
||||
testTreeBuild(t, bTree, 2)
|
||||
|
||||
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
|
||||
|
||||
{
|
||||
toDelete := generic.NewPathFromString("/O-A/O-B")
|
||||
|
||||
aTree.Find(toDelete).Delete(ctx)
|
||||
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
|
||||
|
||||
bTree.Find(toDelete).Delete(ctx)
|
||||
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
|
||||
}
|
||||
|
||||
{
|
||||
content := "OTHER CONTENT"
|
||||
toModify := generic.NewPathFromString("/O-A/O-F")
|
||||
aNode := aTree.Find(toModify)
|
||||
memory.SetContent(aNode, content)
|
||||
aNode.Upsert(ctx)
|
||||
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
|
||||
|
||||
bNode := bTree.Find(toModify)
|
||||
memory.SetContent(bNode, content)
|
||||
bNode.Upsert(ctx)
|
||||
|
||||
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
|
||||
}
|
||||
|
||||
{
|
||||
toModify := generic.NewPathFromString("/O-A/O-F")
|
||||
aTree.Find(toModify).SetKind(kind.Kind("???"))
|
||||
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
|
||||
bTree.Find(toModify).SetKind(kind.Kind("???"))
|
||||
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
|
||||
}
|
||||
|
||||
{
|
||||
pathToMap := generic.NewPathFromString("/O-A/O-J/O-M")
|
||||
mappedID := id.NewNodeID("MAPPED")
|
||||
aTree.Find(pathToMap).SetMappedID(mappedID)
|
||||
assert.False(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
|
||||
|
||||
bNode := bTree.Find(pathToMap).Delete(ctx)
|
||||
bNode.SetID(mappedID)
|
||||
parentPathToMap := generic.NewPathFromString("/O-A/O-J")
|
||||
bTree.Find(parentPathToMap).SetChild(bNode)
|
||||
assert.True(t, generic.TreeCompare(ctx, aTree, generic.NewPathFromString(""), bTree, generic.NewPathFromString("")))
|
||||
}
|
||||
}
|
193
tree/tests/generic/memory_test.go
Normal file
193
tree/tests/generic/memory_test.go
Normal file
|
@ -0,0 +1,193 @@
|
|||
// 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)
|
||||
})
|
||||
}
|
||||
}
|
98
tree/tests/generic/mirror_test.go
Normal file
98
tree/tests/generic/mirror_test.go
Normal file
|
@ -0,0 +1,98 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
type testReference struct {
|
||||
originPath string
|
||||
originReference string
|
||||
destinationPath string
|
||||
destinationReference string
|
||||
}
|
||||
|
||||
func TestMirror(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
start string
|
||||
references []testReference
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
start: "/",
|
||||
expected: []string{":ROOT:", "/D-A:content O-A:", "/D-A/D-B:content O-B:", "/D-A/D-B/D-C:content O-C:", "/D-A/D-B/D-D:content O-D:", "/D-A/D-B/D-E:content O-E:", "/D-A/D-F:content O-F:", "/D-A/D-F/D-G:content O-G:", "/D-A/D-F/D-H:content O-H:", "/D-A/D-F/D-I:content O-I:", "/D-A/D-J:content O-J:", "/D-A/D-J/D-K:content O-K:", "/D-A/D-J/D-L:content O-L:", "/D-A/D-J/D-M:content O-M:"},
|
||||
},
|
||||
{
|
||||
start: "/O-A/O-B",
|
||||
references: []testReference{
|
||||
{
|
||||
originPath: "/O-A/O-B/O-C",
|
||||
originReference: "/O-A/O-F",
|
||||
destinationPath: "/D-A/D-B/D-D",
|
||||
destinationReference: "/D-A/D-C",
|
||||
},
|
||||
},
|
||||
expected: []string{":ROOT:", "/D-A:content O-A:", "/D-A/D-B:content O-B:", "/D-A/D-B/D-D:content O-C:/D-A/D-C", "/D-A/D-B/D-E:content O-D:", "/D-A/D-B/D-F:content O-E:", "/D-A/D-C:content O-F:"},
|
||||
},
|
||||
{
|
||||
start: "/O-A/O-F",
|
||||
references: []testReference{
|
||||
{
|
||||
originPath: "/O-A/O-F/O-G",
|
||||
originReference: "../../O-J",
|
||||
destinationPath: "/D-A/D-B/D-D",
|
||||
destinationReference: "../../D-C",
|
||||
},
|
||||
},
|
||||
expected: []string{":ROOT:", "/D-A:content O-A:", "/D-A/D-B:content O-F:", "/D-A/D-B/D-D:content O-G:../../D-C", "/D-A/D-B/D-E:content O-H:", "/D-A/D-B/D-F:content O-I:", "/D-A/D-C:content O-J:"},
|
||||
},
|
||||
} {
|
||||
t.Run(" "+testCase.start, func(t *testing.T) {
|
||||
originTree := NewMemoryTree(ctx, "O")
|
||||
log := originTree.GetLogger()
|
||||
log.Trace("=========== build")
|
||||
testTreeBuild(t, originTree, 2)
|
||||
|
||||
for _, c := range testCase.references {
|
||||
log.Trace("=========== inject reference %s", c.originReference)
|
||||
assert.True(t, originTree.Apply(ctx, generic.NewPathFromString(c.originPath), generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
|
||||
memory.SetRef(node, c.originReference)
|
||||
})))
|
||||
}
|
||||
|
||||
log.Trace("=========== mirror")
|
||||
destinationTree := NewMemoryTree(ctx, "D")
|
||||
|
||||
generic.TreeMirror(ctx, originTree, destinationTree, generic.NewPathFromString(testCase.start), generic.NewMirrorOptions())
|
||||
log.Trace("=========== verify")
|
||||
collected := make([]string, 0, 10)
|
||||
collect := func(ctx context.Context, parent path.Path, node generic.NodeInterface) {
|
||||
collected = append(collected, node.GetCurrentPath().String()+":"+memory.GetContent(node)+":"+memory.GetRef(node))
|
||||
}
|
||||
destinationTree.Walk(ctx, generic.NewWalkOptions(collect))
|
||||
assert.EqualValues(t, testCase.expected, collected)
|
||||
|
||||
for _, c := range testCase.references {
|
||||
log.Trace("=========== look for reference %s", c.destinationReference)
|
||||
var called bool
|
||||
assert.True(t, destinationTree.Apply(ctx, generic.NewPathFromString(c.destinationPath), generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
|
||||
assert.EqualValues(t, c.destinationReference, memory.GetRef(node))
|
||||
called = true
|
||||
})))
|
||||
assert.True(t, called)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
341
tree/tests/generic/node_walk_test.go
Normal file
341
tree/tests/generic/node_walk_test.go
Normal file
|
@ -0,0 +1,341 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"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"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBothApplyWalk(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tree := NewMemoryTree(ctx, "T")
|
||||
|
||||
for _, testCase := range []struct {
|
||||
path string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
path: "/T-A",
|
||||
expected: []string{"/T-A", "/T-A/T-B", "/T-A/T-B/T-C", "/T-A/T-B/T-D", "/T-A/T-B/T-E", "/T-A/T-F", "/T-A/T-F/T-G", "/T-A/T-F/T-H", "/T-A/T-F/T-I", "/T-A/T-J", "/T-A/T-J/T-K", "/T-A/T-J/T-L", "/T-A/T-J/T-M"},
|
||||
},
|
||||
{
|
||||
path: "/T-A/T-B",
|
||||
expected: []string{"/T-A/T-B", "/T-A/T-B/T-C", "/T-A/T-B/T-D", "/T-A/T-B/T-E"},
|
||||
},
|
||||
{
|
||||
path: "/T-A/T-B/T-C",
|
||||
expected: []string{"/T-A/T-B/T-C"},
|
||||
},
|
||||
} {
|
||||
testTreeBuild(t, tree, 2)
|
||||
|
||||
collected := make([]string, 0, 10)
|
||||
p := generic.NewPathFromString(testCase.path)
|
||||
walk := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
|
||||
collect := func(ctx context.Context, p path.Path, node generic.NodeInterface) {
|
||||
p = p.Append(node)
|
||||
collected = append(collected, p.String())
|
||||
}
|
||||
node.Walk(ctx, parent, generic.NewWalkOptions(collect))
|
||||
}
|
||||
assert.True(t, tree.Apply(ctx, p, generic.NewApplyOptions(walk)))
|
||||
sort.Strings(testCase.expected)
|
||||
sort.Strings(collected)
|
||||
assert.EqualValues(t, testCase.expected, collected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWalk(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tree := NewMemoryTree(ctx, "T")
|
||||
|
||||
expected := []string{"", "/T-A", "/T-A/T-B", "/T-A/T-B/T-C", "/T-A/T-B/T-D", "/T-A/T-B/T-E", "/T-A/T-F", "/T-A/T-F/T-G", "/T-A/T-F/T-H", "/T-A/T-F/T-I", "/T-A/T-J", "/T-A/T-J/T-K", "/T-A/T-J/T-L", "/T-A/T-J/T-M"}
|
||||
testTreeBuild(t, tree, 2)
|
||||
|
||||
collected := make([]string, 0, 10)
|
||||
collect := func(ctx context.Context, p path.Path, node generic.NodeInterface) {
|
||||
p = p.Append(node)
|
||||
collected = append(collected, p.String())
|
||||
}
|
||||
tree.Walk(ctx, generic.NewWalkOptions(collect))
|
||||
sort.Strings(expected)
|
||||
sort.Strings(collected)
|
||||
assert.EqualValues(t, expected, collected)
|
||||
}
|
||||
|
||||
func TestWalkAndGet(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tree := NewMemoryTree(ctx, "T")
|
||||
|
||||
testTreeBuild(t, tree, 1)
|
||||
tree.Clear(ctx)
|
||||
|
||||
collected := make([]string, 0, 10)
|
||||
collect := func(ctx context.Context, p path.Path, node generic.NodeInterface) {
|
||||
p = p.Append(node)
|
||||
collected = append(collected, p.String())
|
||||
}
|
||||
tree.Walk(ctx, generic.NewWalkOptions(collect))
|
||||
assert.EqualValues(t, []string{""}, collected)
|
||||
|
||||
collected = make([]string, 0, 10)
|
||||
tree.WalkAndGet(ctx, generic.NewWalkOptions(collect))
|
||||
expected := []string{"", "/T-A", "/T-A/T-B", "/T-A/T-C", "/T-A/T-D"}
|
||||
sort.Strings(expected)
|
||||
sort.Strings(collected)
|
||||
assert.EqualValues(t, expected, collected)
|
||||
}
|
||||
|
||||
func TestApplyVisitID(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tree := NewMemoryTree(ctx, "T")
|
||||
testTreeBuild(t, tree, 2)
|
||||
|
||||
for _, testPath := range []string{"/T-A", "/T-A/T-B", "/T-A/T-B/T-C"} {
|
||||
|
||||
collected := make([]string, 0, 10)
|
||||
p := generic.NewPathFromString(testPath)
|
||||
collect := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
|
||||
parent = parent.Append(node)
|
||||
assert.False(t, node.GetIsNil(), node.String())
|
||||
assert.EqualValues(t, parent.Length(), node.GetCurrentPath().Length())
|
||||
expected := parent.PathString().Join()
|
||||
actual := node.GetCurrentPath().String()
|
||||
assert.EqualValues(t, actual, expected)
|
||||
collected = append(collected, parent.String())
|
||||
}
|
||||
assert.True(t, tree.Apply(ctx, p, generic.NewApplyOptions(collect)))
|
||||
if assert.EqualValues(t, 1, len(collected)) {
|
||||
assert.EqualValues(t, testPath, collected[0])
|
||||
}
|
||||
}
|
||||
|
||||
p := generic.NewPathFromString("/1/2/3/4")
|
||||
called := false
|
||||
assert.False(t, tree.Apply(ctx, p, generic.NewApplyOptions(func(context.Context, path.Path, path.Path, generic.NodeInterface) { called = true })))
|
||||
assert.False(t, called)
|
||||
}
|
||||
|
||||
func TestApplyVisitByName(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tree := NewMemoryTree(ctx, "T")
|
||||
testTreeBuild(t, tree, 2)
|
||||
|
||||
for _, testCase := range []struct {
|
||||
path string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
path: "/T-A/content T-B/T-C",
|
||||
expected: "/T-A/T-B/T-C",
|
||||
},
|
||||
{
|
||||
path: "/T-A/content T-B/content T-C",
|
||||
expected: "/T-A/T-B/T-C",
|
||||
},
|
||||
{
|
||||
path: "/content T-A/content T-B/content T-C",
|
||||
expected: "/T-A/T-B/T-C",
|
||||
},
|
||||
} {
|
||||
collected := make([]string, 0, 10)
|
||||
p := generic.NewPathFromString(testCase.path)
|
||||
collect := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
|
||||
parent = parent.Append(node)
|
||||
assert.False(t, node.GetIsNil(), node.String())
|
||||
assert.EqualValues(t, parent.Length(), node.GetCurrentPath().Length())
|
||||
expected := parent.PathString().Join()
|
||||
actual := node.GetCurrentPath().String()
|
||||
assert.EqualValues(t, actual, expected)
|
||||
collected = append(collected, parent.String())
|
||||
}
|
||||
assert.True(t, tree.Apply(ctx, p, generic.NewApplyOptions(collect).SetSearch(generic.ApplySearchByName)))
|
||||
if assert.EqualValues(t, 1, len(collected)) {
|
||||
assert.EqualValues(t, testCase.expected, collected[0])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyAndGet(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
path string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
path: "/T-A",
|
||||
expected: []string{"", "/T-A"},
|
||||
},
|
||||
{
|
||||
path: "/T-A/T-B",
|
||||
expected: []string{"", "/T-A", "/T-A/T-B"},
|
||||
},
|
||||
{
|
||||
path: "/T-A/T-B/T-C",
|
||||
expected: []string{"", "/T-A", "/T-A/T-B", "/T-A/T-B/T-C"},
|
||||
},
|
||||
} {
|
||||
tree := NewMemoryTree(ctx, "T")
|
||||
|
||||
testTreeBuild(t, tree, 2)
|
||||
tree.Clear(ctx)
|
||||
|
||||
p := generic.NewPathFromString(testCase.path)
|
||||
|
||||
var collected []string
|
||||
collect := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
|
||||
parent = parent.Append(node)
|
||||
collected = append(collected, parent.String())
|
||||
}
|
||||
|
||||
{
|
||||
collected = make([]string, 0, 10)
|
||||
require.False(t, tree.Apply(ctx, p, generic.NewApplyOptions(collect)))
|
||||
}
|
||||
|
||||
{
|
||||
collected = make([]string, 0, 10)
|
||||
require.True(t, tree.ApplyAndGet(ctx, p, generic.NewApplyOptions(collect)))
|
||||
require.EqualValues(t, 1, len(collected))
|
||||
assert.EqualValues(t, testCase.path, collected[0])
|
||||
}
|
||||
|
||||
{
|
||||
collected = make([]string, 0, 10)
|
||||
require.True(t, tree.ApplyAndGet(ctx, p, generic.NewApplyOptions(collect).SetWhere(generic.ApplyEachNode)))
|
||||
sort.Strings(testCase.expected)
|
||||
sort.Strings(collected)
|
||||
assert.EqualValues(t, testCase.expected, collected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyVisitRelative(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tree := NewMemoryTree(ctx, "T")
|
||||
|
||||
// The "destination" is clean and there is no need to test a/../b which becomes
|
||||
// b etc. Only when the first element of the path is either an id or ..
|
||||
for _, testCase := range []struct {
|
||||
start string
|
||||
destination string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
start: "/T-A",
|
||||
destination: "T-B",
|
||||
expected: "/T-A/T-B",
|
||||
},
|
||||
{
|
||||
start: "/T-A/T-B",
|
||||
destination: ".",
|
||||
expected: "/T-A/T-B",
|
||||
},
|
||||
{
|
||||
start: "/T-A/T-B",
|
||||
destination: "T-C",
|
||||
expected: "/T-A/T-B/T-C",
|
||||
},
|
||||
{
|
||||
start: "/T-A/T-B",
|
||||
destination: "..",
|
||||
expected: "/T-A",
|
||||
},
|
||||
{
|
||||
start: "/T-A/T-B",
|
||||
destination: "../T-F/T-G",
|
||||
expected: "/T-A/T-F/T-G",
|
||||
},
|
||||
{
|
||||
start: "/T-A/T-B/T-C",
|
||||
destination: "../../T-F",
|
||||
expected: "/T-A/T-F",
|
||||
},
|
||||
} {
|
||||
t.Run(" "+testCase.start+" => "+testCase.destination, func(t *testing.T) {
|
||||
testTreeBuild(t, tree, 2)
|
||||
|
||||
collected := make([]string, 0, 10)
|
||||
start := generic.NewPathFromString(testCase.start)
|
||||
collect := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
|
||||
parent = parent.Append(node)
|
||||
assert.False(t, node.GetIsNil(), node.String())
|
||||
assert.EqualValues(t, parent.Length(), node.GetCurrentPath().Length())
|
||||
expected := parent.PathString().Join()
|
||||
actual := node.GetCurrentPath().String()
|
||||
assert.EqualValues(t, actual, expected)
|
||||
collected = append(collected, parent.String())
|
||||
}
|
||||
cd := func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
|
||||
destination := generic.NewPathFromString(testCase.destination)
|
||||
fmt.Println("start ", node.GetCurrentPath().String())
|
||||
assert.True(t, node.Apply(ctx, parent, destination, generic.NewApplyOptions(collect)))
|
||||
}
|
||||
assert.True(t, tree.Apply(ctx, start, generic.NewApplyOptions(cd)))
|
||||
if assert.EqualValues(t, 1, len(collected)) {
|
||||
assert.EqualValues(t, testCase.expected, collected[0])
|
||||
}
|
||||
})
|
||||
|
||||
p := generic.NewPathFromString("/1/2/3/4")
|
||||
called := false
|
||||
assert.False(t, tree.Apply(ctx, p, generic.NewApplyOptions(func(context.Context, path.Path, path.Path, generic.NodeInterface) { called = true })))
|
||||
assert.False(t, called)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyUpsert(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tree := NewMemoryTree(ctx, "T")
|
||||
|
||||
testTreeBuild(t, tree, 2)
|
||||
|
||||
expected := generic.NewPathFromString("/T-A/T-B/T-N")
|
||||
assert.False(t, tree.Exists(ctx, expected))
|
||||
|
||||
assert.True(t, tree.Apply(ctx, generic.NewPathFromString("/T-A/T-B"), generic.NewApplyOptions(func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
|
||||
new := node.CreateChild(ctx)
|
||||
new.Upsert(ctx)
|
||||
memory.SetContent(new, "content "+new.GetID().String())
|
||||
new.Upsert(ctx)
|
||||
})))
|
||||
|
||||
assert.True(t, tree.Exists(ctx, expected))
|
||||
}
|
||||
|
||||
func TestApplyDelete(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
tree := NewMemoryTree(ctx, "T")
|
||||
|
||||
testTreeBuild(t, tree, 2)
|
||||
|
||||
toDelete := generic.NewPathFromString("/T-A/T-B")
|
||||
assert.True(t, tree.Exists(ctx, toDelete))
|
||||
|
||||
assert.True(t, tree.Apply(ctx, toDelete, generic.NewApplyOptions(func(ctx context.Context, parent, p path.Path, node generic.NodeInterface) {
|
||||
node.Delete(ctx)
|
||||
})))
|
||||
|
||||
assert.False(t, tree.Exists(ctx, toDelete))
|
||||
}
|
74
tree/tests/generic/references_test.go
Normal file
74
tree/tests/generic/references_test.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
// 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/path"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/generic"
|
||||
"code.forgejo.org/f3/gof3/v3/tree/memory"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTreeCollectReferences(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
path string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
path: "/O-A/O-B",
|
||||
expected: []string{"/O-A/O-B/O-C"},
|
||||
},
|
||||
{
|
||||
path: "/O-A/O-D",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
path: "/O-A/O-J",
|
||||
expected: []string{"/O-A/O-F", "/O-A/O-J/O-K"},
|
||||
},
|
||||
{
|
||||
path: "/O-A/O-J/O-L",
|
||||
expected: []string{"/O-A/O-J/O-K"},
|
||||
},
|
||||
{
|
||||
path: "/O-A/O-J/O-M",
|
||||
expected: []string{"/O-A/O-F"},
|
||||
},
|
||||
{
|
||||
path: "/O-A",
|
||||
expected: []string{"/O-A/O-B/O-C", "/O-A/O-F", "/O-A/O-F/O-H", "/O-A/O-J/O-K"},
|
||||
},
|
||||
} {
|
||||
t.Run(testCase.path, func(t *testing.T) {
|
||||
tree := NewMemoryTree(ctx, "O")
|
||||
testTreeBuild(t, tree, 2)
|
||||
|
||||
setReference := func(p, reference string) {
|
||||
assert.True(t, tree.Apply(ctx, generic.NewPathFromString(p), generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
|
||||
memory.SetRef(node, reference)
|
||||
})))
|
||||
}
|
||||
setReference("/O-A/O-B", "/O-A/O-B/O-C")
|
||||
setReference("/O-A/O-F/O-G", "/O-A/O-F/O-H")
|
||||
setReference("/O-A/O-J/O-M", "../../O-F")
|
||||
setReference("/O-A/O-J/O-L", "../O-K")
|
||||
|
||||
actual := make([]string, 0, 10)
|
||||
for _, reference := range generic.TreeCollectReferences(ctx, tree, generic.NewPathFromString(testCase.path)) {
|
||||
actual = append(actual, reference.String())
|
||||
}
|
||||
sort.Strings(actual)
|
||||
|
||||
assert.EqualValues(t, testCase.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
626
tree/tests/generic/unify_test.go
Normal file
626
tree/tests/generic/unify_test.go
Normal file
|
@ -0,0 +1,626 @@
|
|||
// Copyright Earl Warren <contact@earl-warren.org>
|
||||
// Copyright Loïc Dachary <loic@dachary.org>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"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 TestUnifyPathSimpleRemap(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
path string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
path: "",
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
path: "/O-A",
|
||||
expected: []string{"/O-A:O-A=content O-A => /D-A:D-A=content O-A"},
|
||||
},
|
||||
{
|
||||
path: "/O-A/O-B",
|
||||
expected: []string{"/O-A:O-A=content O-A => /D-A:D-A=content O-A", "/O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B"},
|
||||
},
|
||||
{
|
||||
path: "/O-A/O-B/O-C",
|
||||
expected: []string{"/O-A:O-A=content O-A => /D-A:D-A=content O-A", "/O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "/O-A/O-B/O-C:O-C=content O-C => /D-A/D-B/D-C:D-C=content O-C"},
|
||||
},
|
||||
} {
|
||||
t.Run(" "+testCase.path, func(t *testing.T) {
|
||||
originTree := NewMemoryTree(ctx, "O")
|
||||
testTreeBuild(t, originTree, 2)
|
||||
destinationTree := NewMemoryTree(ctx, "D")
|
||||
|
||||
collected := make([]string, 0, 10)
|
||||
p := generic.NewPathFromString(testCase.path)
|
||||
upsert := func(ctx context.Context, origin generic.NodeInterface, originPath path.Path, destination generic.NodeInterface, destinationPath path.Path) {
|
||||
fmt.Printf("origin %v destination %v\n", origin, destination)
|
||||
originPath = originPath.Append(origin)
|
||||
destinationPath = destinationPath.Append(destination)
|
||||
collected = append(collected, originPath.String()+":"+origin.String()+" => "+destinationPath.String()+":"+destination.String())
|
||||
}
|
||||
generic.TreeUnifyPath(ctx, originTree, p, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
|
||||
assert.EqualValues(t, testCase.expected, collected)
|
||||
collected = make([]string, 0, 10)
|
||||
generic.TreeUnifyPath(ctx, originTree, p, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
|
||||
assert.EqualValues(t, testCase.expected, collected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnifyPathSimpleNoRemap(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
noremap bool
|
||||
path string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
noremap: false,
|
||||
path: "/O-A/O-B",
|
||||
expected: []string{"/O-A:O-A=content O-A => /D-A:D-A=content O-A", "/O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B"},
|
||||
},
|
||||
{
|
||||
noremap: true,
|
||||
path: "/O-A/O-B",
|
||||
expected: []string{"/O-A:O-A=content O-A => /O-A:O-A=content O-A", "/O-A/O-B:O-B=content O-B => /O-A/O-B:O-B=content O-B"},
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("noremap=%v,path=%s", testCase.noremap, testCase.path), func(t *testing.T) {
|
||||
originName := "O"
|
||||
originTree := NewMemoryTree(ctx, originName)
|
||||
testTreeBuild(t, originTree, 2)
|
||||
var destinationName string
|
||||
if testCase.noremap {
|
||||
destinationName = originName
|
||||
} else {
|
||||
destinationName = "D"
|
||||
}
|
||||
destinationTree := NewMemoryTree(ctx, destinationName)
|
||||
|
||||
collected := make([]string, 0, 10)
|
||||
p := generic.NewPathFromString(testCase.path)
|
||||
upsert := func(ctx context.Context, origin generic.NodeInterface, originPath path.Path, destination generic.NodeInterface, destinationPath path.Path) {
|
||||
fmt.Printf("origin %v destination %v\n", origin, destination)
|
||||
originPath = originPath.Append(origin)
|
||||
destinationPath = destinationPath.Append(destination)
|
||||
collected = append(collected, originPath.String()+":"+origin.String()+" => "+destinationPath.String()+":"+destination.String())
|
||||
}
|
||||
generic.TreeUnifyPath(ctx, originTree, p, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetNoRemap(testCase.noremap))
|
||||
assert.EqualValues(t, testCase.expected, collected)
|
||||
collected = make([]string, 0, 10)
|
||||
generic.TreeUnifyPath(ctx, originTree, p, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetNoRemap(testCase.noremap))
|
||||
assert.EqualValues(t, testCase.expected, collected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnifyPathRelative(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
start string
|
||||
destination string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
start: "/O-A/O-B",
|
||||
destination: "O-C",
|
||||
expected: []string{"cd: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "unify: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "unify: /O-A/O-B/O-C:O-C=content O-C => /D-A/D-B/D-C:D-C=content O-C"},
|
||||
},
|
||||
{
|
||||
start: "/O-A/O-B",
|
||||
destination: ".",
|
||||
expected: []string{"cd: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "unify: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B"},
|
||||
},
|
||||
{
|
||||
start: "/O-A/O-B",
|
||||
destination: "../O-F/O-G",
|
||||
expected: []string{"cd: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "unify: /O-A:O-A=content O-A => /D-A:D-A=content O-A", "unify: /O-A/O-F:O-F=content O-F => /D-A/D-C:D-C=content O-F", "unify: /O-A/O-F/O-G:O-G=content O-G => /D-A/D-C/D-D:D-D=content O-G"},
|
||||
},
|
||||
{
|
||||
start: "/O-A/O-B/O-C",
|
||||
destination: "../O-E",
|
||||
expected: []string{"cd: /O-A/O-B/O-C:O-C=content O-C => /D-A/D-B/D-C:D-C=content O-C", "unify: /O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "unify: /O-A/O-B/O-E:O-E=content O-E => /D-A/D-B/D-D:D-D=content O-E"},
|
||||
},
|
||||
} {
|
||||
t.Run(" "+testCase.start+" => "+testCase.destination, func(t *testing.T) {
|
||||
originTree := NewMemoryTree(ctx, "O")
|
||||
testTreeBuild(t, originTree, 2)
|
||||
destinationTree := NewMemoryTree(ctx, "D")
|
||||
|
||||
var collected []string
|
||||
start := generic.NewPathFromString(testCase.start)
|
||||
collect := func(prefix string, origin, destination generic.NodeInterface) {
|
||||
originPath := origin.GetCurrentPath().String()
|
||||
destinationPath := destination.GetCurrentPath().String()
|
||||
collected = append(collected, prefix+originPath+":"+origin.GetSelf().String()+" => "+destinationPath+":"+destination.GetSelf().String())
|
||||
}
|
||||
//
|
||||
// Unify testCase.start
|
||||
//
|
||||
upsert := func(ctx context.Context, origin generic.NodeInterface, originParent path.Path, destination generic.NodeInterface, destinationParent path.Path) {
|
||||
collect("unify: ", origin, destination)
|
||||
}
|
||||
generic.TreeUnifyPath(ctx, originTree, start, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
|
||||
//
|
||||
// Beginning from testCase.start, unify testCase.destination
|
||||
//
|
||||
cd := func(ctx context.Context, origin, destination generic.NodeInterface) {
|
||||
collect("cd: ", origin, destination)
|
||||
path := generic.NewPathFromString(testCase.destination)
|
||||
originParent := origin.GetParent().GetCurrentPath()
|
||||
destinationParent := destination.GetParent().GetCurrentPath()
|
||||
generic.NodeUnifyPath(ctx, origin.GetSelf(), originParent, path, destinationParent, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
|
||||
}
|
||||
//
|
||||
//
|
||||
//
|
||||
collected = make([]string, 0, 10)
|
||||
generic.TreeParallelApply(ctx, originTree, start, destinationTree, generic.NewParallelApplyOptions(cd))
|
||||
assert.EqualValues(t, testCase.expected, collected)
|
||||
//
|
||||
// Do it twice to verify it is idempotent
|
||||
//
|
||||
collected = make([]string, 0, 10)
|
||||
generic.TreeParallelApply(ctx, originTree, start, destinationTree, generic.NewParallelApplyOptions(cd))
|
||||
assert.EqualValues(t, testCase.expected, collected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnifyPathScenario(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
//
|
||||
// build and populate a tree
|
||||
//
|
||||
originTree := NewMemoryTree(ctx, "O")
|
||||
testTreeBuild(t, originTree, 2)
|
||||
|
||||
//
|
||||
// build an empty tree
|
||||
//
|
||||
destinationTree := NewMemoryTree(ctx, "D")
|
||||
|
||||
//
|
||||
// accumulate the call results for verification
|
||||
//
|
||||
var collected []string
|
||||
upsert := func(ctx context.Context, origin generic.NodeInterface, originPath path.Path, destination generic.NodeInterface, destinationPath path.Path) {
|
||||
originPath = originPath.Append(origin)
|
||||
destinationPath = destinationPath.Append(destination)
|
||||
what := originPath.String() + ":" + origin.String() + " => " + destinationPath.String() + ":" + destination.String()
|
||||
fmt.Printf("unify: %T => %T | %s\n", origin, destination, what)
|
||||
collected = append(collected, what)
|
||||
}
|
||||
|
||||
assertTree := func(tree generic.TreeInterface, expected []string) {
|
||||
collected := make([]string, 0, 10)
|
||||
tree.Walk(ctx, generic.NewWalkOptions(func(ctx context.Context, path path.Path, node generic.NodeInterface) {
|
||||
if node.GetKind() == kind.KindRoot {
|
||||
return
|
||||
}
|
||||
path = path.Append(node)
|
||||
collected = append(collected, path.String()+":"+node.String())
|
||||
}))
|
||||
sort.Strings(collected)
|
||||
assert.EqualValues(t, expected, collected)
|
||||
}
|
||||
|
||||
//
|
||||
// unify the originTree with the destinationTree on the specified path
|
||||
//
|
||||
fullPath := generic.NewPathFromString("/O-A/O-B/O-C")
|
||||
collected = make([]string, 0, 10)
|
||||
generic.TreeUnifyPath(ctx, originTree, fullPath, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
|
||||
sort.Strings(collected)
|
||||
assert.EqualValues(t, []string{"/O-A/O-B/O-C:O-C=content O-C => /D-A/D-B/D-C:D-C=content O-C", "/O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "/O-A:O-A=content O-A => /D-A:D-A=content O-A"}, collected)
|
||||
assertTree(destinationTree, []string{"/D-A/D-B/D-C:D-C=content O-C", "/D-A/D-B:D-B=content O-B", "/D-A:D-A=content O-A"})
|
||||
|
||||
//
|
||||
// Add a node unrelated to the unification path
|
||||
//
|
||||
var unrelatedOriginPath path.Path
|
||||
{
|
||||
originTree.Apply(ctx, generic.NewPathFromString("/O-A/O-B"), generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
|
||||
assert.EqualValues(t, parent.Length()+1, node.GetCurrentPath().Length())
|
||||
unrelated := node.CreateChild(ctx)
|
||||
unrelated.Upsert(ctx)
|
||||
memory.SetContent(unrelated, "content "+unrelated.GetID().String())
|
||||
unrelated.Upsert(ctx)
|
||||
unrelatedOriginPath = unrelated.GetCurrentPath()
|
||||
assert.EqualValues(t, "/O-A/O-B/O-N", (unrelatedOriginPath.PathString().Join()))
|
||||
}))
|
||||
}
|
||||
|
||||
//
|
||||
// Replace the content of the last node
|
||||
//
|
||||
lastContent := "LAST"
|
||||
{
|
||||
lastPath := generic.NewPathFromString("/O-A/O-B/O-C")
|
||||
originTree.Apply(ctx, lastPath, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
|
||||
memory.SetContent(node, lastContent)
|
||||
}))
|
||||
collected = make([]string, 0, 10)
|
||||
generic.TreeUnifyPath(ctx, originTree, lastPath, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
|
||||
sort.Strings(collected)
|
||||
assert.EqualValues(t, []string{"/O-A/O-B/O-C:O-C=LAST => /D-A/D-B/D-C:D-C=LAST", "/O-A/O-B:O-B=content O-B => /D-A/D-B:D-B=content O-B", "/O-A:O-A=content O-A => /D-A:D-A=content O-A"}, collected)
|
||||
assertTree(destinationTree, []string{"/D-A/D-B/D-C:D-C=LAST", "/D-A/D-B:D-B=content O-B", "/D-A:D-A=content O-A"})
|
||||
}
|
||||
//
|
||||
// Replace the content of the first node
|
||||
//
|
||||
firstContent := "FIRST"
|
||||
{
|
||||
firstPath := generic.NewPathFromString("/O-A")
|
||||
originTree.Apply(ctx, firstPath, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
|
||||
memory.SetContent(node, firstContent)
|
||||
}))
|
||||
collected = make([]string, 0, 10)
|
||||
generic.TreeUnifyPath(ctx, originTree, firstPath, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
|
||||
sort.Strings(collected)
|
||||
assert.EqualValues(t, []string{"/O-A:O-A=" + firstContent + " => /D-A:D-A=" + firstContent}, collected)
|
||||
assertTree(destinationTree, []string{"/D-A/D-B/D-C:D-C=LAST", "/D-A/D-B:D-B=content O-B", "/D-A:D-A=FIRST"})
|
||||
}
|
||||
//
|
||||
// Replace the content of the second node
|
||||
//
|
||||
secondContent := "SECOND"
|
||||
{
|
||||
secondPath := generic.NewPathFromString("/O-A/O-B")
|
||||
originTree.Apply(ctx, secondPath, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
|
||||
memory.SetContent(node, secondContent)
|
||||
}))
|
||||
collected = make([]string, 0, 10)
|
||||
generic.TreeUnifyPath(ctx, originTree, secondPath, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert))
|
||||
assert.EqualValues(t, []string{"/O-A:O-A=" + firstContent + " => /D-A:D-A=" + firstContent, "/O-A/O-B:O-B=" + secondContent + " => /D-A/D-B:D-B=" + secondContent}, collected)
|
||||
sort.Strings(collected)
|
||||
assertTree(destinationTree, []string{"/D-A/D-B/D-C:D-C=LAST", "/D-A/D-B:D-B=SECOND", "/D-A:D-A=FIRST"})
|
||||
}
|
||||
//
|
||||
// verify the node unrelated to the unification is still there
|
||||
//
|
||||
{
|
||||
var found bool
|
||||
originTree.Apply(ctx, unrelatedOriginPath, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
|
||||
found = true
|
||||
}))
|
||||
assert.True(t, found)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnifyMirror(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
//
|
||||
// build and populate a tree
|
||||
//
|
||||
originTree := NewMemoryTree(ctx, "O")
|
||||
log := originTree.GetLogger()
|
||||
testTreeBuild(t, originTree, 2)
|
||||
|
||||
//
|
||||
// build an empty tree
|
||||
//
|
||||
destinationTree := NewMemoryTree(ctx, "D")
|
||||
|
||||
upsert := func(ctx context.Context, origin generic.NodeInterface, originPath path.Path, destination generic.NodeInterface, destinationPath path.Path) {
|
||||
assert.NotNil(t, origin.GetDriver().(*memory.Driver))
|
||||
assert.NotNil(t, destination.GetDriver().(*memory.Driver))
|
||||
originPath = originPath.Append(origin)
|
||||
destinationPath = destinationPath.Append(destination)
|
||||
what := fmt.Sprintf("%s:%s => %s:%s", originPath, origin.GetSelf(), destinationPath, destination.GetSelf())
|
||||
log.Trace("mirror upsert: %T => %T | %s", origin, destination, what)
|
||||
}
|
||||
delete := func(ctx context.Context, destination generic.NodeInterface, destinationPath path.Path) {
|
||||
assert.NotNil(t, destination.GetDriver().(*memory.Driver))
|
||||
destinationPath = destinationPath.Append(destination)
|
||||
log.Trace("mirror delete: %T | %s:%s", destination, destinationPath, destination)
|
||||
}
|
||||
|
||||
var sameTree func(origin, destination generic.NodeInterface) bool
|
||||
sameTree = func(origin, destination generic.NodeInterface) bool {
|
||||
what := origin.GetCurrentPath().String() + ":" + origin.GetSelf().String() + " => " + destination.GetCurrentPath().String() + ":" + destination.GetSelf().String()
|
||||
log.Trace("sameTree: %T => %T | %s", origin.GetSelf(), destination.GetSelf(), what)
|
||||
if origin.GetMappedID() != destination.GetID() {
|
||||
log.Trace("sameTree: different: %s != %s", origin.GetMappedID(), destination.GetID())
|
||||
return false
|
||||
}
|
||||
originChildren := origin.GetChildren()
|
||||
destinationChildren := destination.GetChildren()
|
||||
if len(originChildren) != len(destinationChildren) {
|
||||
log.Trace("sameTree: different: length %v != %v", len(originChildren), len(destinationChildren))
|
||||
return false
|
||||
}
|
||||
|
||||
for _, originChild := range originChildren {
|
||||
destinationChild := destination.GetChild(originChild.GetMappedID())
|
||||
if destinationChild == generic.NilNode {
|
||||
log.Trace("sameTree: different: %s not found", originChild.GetMappedID())
|
||||
return false
|
||||
}
|
||||
if !sameTree(originChild, destinationChild) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//
|
||||
// unify the originTree with the destinationTree
|
||||
//
|
||||
assert.False(t, sameTree(originTree.GetRoot(), destinationTree.GetRoot()))
|
||||
generic.TreeUnify(ctx, originTree, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetDelete(delete))
|
||||
assert.True(t, sameTree(originTree.GetRoot(), destinationTree.GetRoot()))
|
||||
generic.TreeUnify(ctx, originTree, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetDelete(delete))
|
||||
assert.True(t, sameTree(originTree.GetRoot(), destinationTree.GetRoot()))
|
||||
|
||||
{
|
||||
addNode := func(tree generic.TreeInterface, pathString string) {
|
||||
tree.Apply(ctx, generic.NewPathFromString(pathString), generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
|
||||
new := node.CreateChild(ctx)
|
||||
new.Upsert(ctx)
|
||||
memory.SetContent(new, "content "+new.GetID().String())
|
||||
new.Upsert(ctx)
|
||||
log.Trace("add: %s", parent.ReadableString())
|
||||
log.Trace("add: %s", node.GetCurrentPath().ReadableString())
|
||||
log.Trace("add: %s", new.GetCurrentPath().ReadableString())
|
||||
}))
|
||||
}
|
||||
|
||||
for _, testCase := range []struct {
|
||||
existingPath string
|
||||
newPath string
|
||||
}{
|
||||
{
|
||||
existingPath: "/O-A/O-B",
|
||||
newPath: "/D-A/D-B/D-N",
|
||||
},
|
||||
{
|
||||
existingPath: "/O-A",
|
||||
newPath: "/D-A/D-O",
|
||||
},
|
||||
{
|
||||
existingPath: "/O-A/O-J/O-K",
|
||||
newPath: "/D-A/D-J/D-K/D-P",
|
||||
},
|
||||
} {
|
||||
t.Run("add"+testCase.newPath, func(t *testing.T) {
|
||||
destinationPath := generic.NewPathFromString(testCase.newPath)
|
||||
addNode(originTree, testCase.existingPath)
|
||||
assert.False(t, sameTree(originTree.GetRoot(), destinationTree.GetRoot()))
|
||||
assert.False(t, destinationTree.Exists(ctx, destinationPath), destinationPath.String())
|
||||
generic.TreeUnify(ctx, originTree, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetDelete(delete))
|
||||
assert.True(t, destinationTree.Exists(ctx, destinationPath), destinationPath.String())
|
||||
assert.True(t, sameTree(originTree.GetRoot(), destinationTree.GetRoot()))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
deleteNode := func(tree generic.TreeInterface, toDelete path.Path) {
|
||||
tree.Apply(ctx, toDelete, generic.NewApplyOptions(func(ctx context.Context, parent, path path.Path, node generic.NodeInterface) {
|
||||
node.Delete(ctx)
|
||||
}))
|
||||
}
|
||||
|
||||
for _, testCase := range []struct {
|
||||
originPath string
|
||||
destinationPath string
|
||||
}{
|
||||
{
|
||||
originPath: "/O-A/O-F",
|
||||
destinationPath: "/D-A/D-F",
|
||||
},
|
||||
} {
|
||||
t.Run("delete"+testCase.originPath, func(t *testing.T) {
|
||||
originPath := generic.NewPathFromString(testCase.originPath)
|
||||
destinationPath := generic.NewPathFromString(testCase.destinationPath)
|
||||
assert.True(t, originTree.Exists(ctx, originPath))
|
||||
assert.True(t, destinationTree.Exists(ctx, destinationPath), destinationPath.String())
|
||||
deleteNode(originTree, originPath)
|
||||
assert.False(t, originTree.Exists(ctx, originPath))
|
||||
generic.TreeUnify(ctx, originTree, destinationTree, generic.NewUnifyOptions(destinationTree).SetUpsert(upsert).SetDelete(delete))
|
||||
assert.False(t, destinationTree.Exists(ctx, destinationPath), destinationPath.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeParallelApplyFound(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
start string
|
||||
destination string
|
||||
expected []string
|
||||
expectedRemapped string
|
||||
}{
|
||||
{
|
||||
start: "/O-A/O-B",
|
||||
destination: "O-C",
|
||||
expected: []string{"/O-A/O-B => /D-A/D-B", "/O-A/O-B/O-C => /D-A/D-B/D-C"},
|
||||
expectedRemapped: "/D-A/D-B/D-C",
|
||||
},
|
||||
{
|
||||
start: "/O-A/O-B/O-D",
|
||||
destination: ".",
|
||||
expected: []string{"/O-A/O-B/O-D => /D-A/D-B/D-D"},
|
||||
expectedRemapped: "/D-A/D-B/D-D",
|
||||
},
|
||||
{
|
||||
start: ".",
|
||||
destination: ".",
|
||||
expected: []string{" => "},
|
||||
expectedRemapped: "",
|
||||
},
|
||||
{
|
||||
start: "/O-A/O-B/O-C",
|
||||
destination: "../O-D",
|
||||
expected: []string{"/O-A/O-B => /D-A/D-B", "/O-A/O-B/O-D => /D-A/D-B/D-D"},
|
||||
expectedRemapped: "/D-A/D-B/D-D",
|
||||
},
|
||||
{
|
||||
start: "/",
|
||||
destination: ".",
|
||||
expected: []string{" => "},
|
||||
expectedRemapped: "",
|
||||
},
|
||||
} {
|
||||
t.Run(" "+testCase.start+" => "+testCase.destination, func(t *testing.T) {
|
||||
originTree := NewMemoryTree(ctx, "O")
|
||||
testTreeBuild(t, originTree, 2)
|
||||
destinationTree := NewMemoryTree(ctx, "D")
|
||||
|
||||
//
|
||||
// Mirror two trees
|
||||
//
|
||||
generic.TreeMirror(ctx, originTree, destinationTree, generic.NewPathFromString("/"), generic.NewMirrorOptions())
|
||||
//
|
||||
// collect all nodes traversed by the apply function along testCase.destination
|
||||
//
|
||||
collected := make([]string, 0, 10)
|
||||
collect := func(ctx context.Context, origin, destination generic.NodeInterface) {
|
||||
collected = append(collected, fmt.Sprintf("%s => %s", origin.GetCurrentPath().String(), destination.GetCurrentPath().String()))
|
||||
}
|
||||
//
|
||||
// get to testCase.start and from there run the apply function to reach testCase.destination
|
||||
//
|
||||
nodeApply := func(ctx context.Context, origin, destination generic.NodeInterface) {
|
||||
assert.True(t, generic.NodeParallelApply(ctx, origin, generic.NewPathFromString(testCase.destination), destination, generic.NewParallelApplyOptions(collect).SetWhere(generic.ApplyEachNode)))
|
||||
}
|
||||
assert.True(t, generic.TreeParallelApply(ctx, originTree, generic.NewPathFromString(testCase.start), destinationTree, generic.NewParallelApplyOptions(nodeApply)))
|
||||
assert.EqualValues(t, testCase.expected, collected)
|
||||
|
||||
//
|
||||
// test TreePathRemap
|
||||
//
|
||||
remappedPath := generic.TreePathRemap(ctx, originTree, generic.NewPathFromString(filepath.Join(testCase.start, testCase.destination)), destinationTree)
|
||||
assert.EqualValues(t, testCase.expectedRemapped, remappedPath.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeParallelApplyNoRemap(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
noremap bool
|
||||
start string
|
||||
expected []string
|
||||
expectedRemapped string
|
||||
}{
|
||||
{
|
||||
noremap: false,
|
||||
start: "/O-A/O-B",
|
||||
expected: []string{" => ", "/O-A => /D-A", "/O-A/O-B => /D-A/D-B"},
|
||||
expectedRemapped: "/D-A/D-B",
|
||||
},
|
||||
{
|
||||
noremap: true,
|
||||
start: "/O-A/O-B",
|
||||
expected: []string{" => ", "/O-A => /O-A", "/O-A/O-B => /O-A/O-B"},
|
||||
expectedRemapped: "/O-A/O-B",
|
||||
},
|
||||
} {
|
||||
t.Run(fmt.Sprintf("noremap=%v,start=%s", testCase.noremap, testCase.start), func(t *testing.T) {
|
||||
originName := "O"
|
||||
originTree := NewMemoryTree(ctx, originName)
|
||||
testTreeBuild(t, originTree, 2)
|
||||
var destinationName string
|
||||
if testCase.noremap {
|
||||
destinationName = originName
|
||||
} else {
|
||||
destinationName = "D"
|
||||
}
|
||||
destinationTree := NewMemoryTree(ctx, destinationName)
|
||||
|
||||
//
|
||||
// Mirror two trees
|
||||
//
|
||||
generic.TreeMirror(ctx, originTree, destinationTree, generic.NewPathFromString("/"), generic.NewMirrorOptions())
|
||||
//
|
||||
// collect all nodes traversed by the apply function along testCase.destination
|
||||
//
|
||||
collected := make([]string, 0, 10)
|
||||
collect := func(ctx context.Context, origin, destination generic.NodeInterface) {
|
||||
collected = append(collected, fmt.Sprintf("%s => %s", origin.GetCurrentPath(), destination.GetCurrentPath()))
|
||||
}
|
||||
//
|
||||
// get to testCase.start and from there run the apply function to reach testCase.destination
|
||||
//
|
||||
assert.True(t, generic.TreeParallelApply(ctx, originTree, generic.NewPathFromString(testCase.start), destinationTree, generic.NewParallelApplyOptions(collect).SetWhere(generic.ApplyEachNode).SetNoRemap(testCase.noremap)))
|
||||
assert.EqualValues(t, testCase.expected, collected)
|
||||
|
||||
//
|
||||
// test TreePathRemap
|
||||
//
|
||||
remappedPath := generic.TreePathRemap(ctx, originTree, generic.NewPathFromString(testCase.start), destinationTree)
|
||||
assert.EqualValues(t, testCase.expectedRemapped, remappedPath.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodeParallelApplyNotFound(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
for _, testCase := range []struct {
|
||||
start string
|
||||
destination string
|
||||
}{
|
||||
{
|
||||
start: "/O-A",
|
||||
destination: "O-B/???",
|
||||
},
|
||||
{
|
||||
start: "/O-A/O-B",
|
||||
destination: "???",
|
||||
},
|
||||
{
|
||||
start: "/O-A/O-B",
|
||||
destination: "../???",
|
||||
},
|
||||
} {
|
||||
t.Run(" "+testCase.start+" => "+testCase.destination, func(t *testing.T) {
|
||||
originTree := NewMemoryTree(ctx, "O")
|
||||
testTreeBuild(t, originTree, 2)
|
||||
destinationTree := NewMemoryTree(ctx, "D")
|
||||
|
||||
//
|
||||
// Mirror two trees
|
||||
//
|
||||
generic.TreeMirror(ctx, originTree, destinationTree, generic.NewPathFromString("/"), generic.NewMirrorOptions())
|
||||
//
|
||||
// get to testCase.start and from there run the apply function to reach testCase.destination
|
||||
//
|
||||
var called bool
|
||||
nodeApply := func(ctx context.Context, origin, destination generic.NodeInterface) {
|
||||
called = true
|
||||
found := generic.NodeParallelApply(ctx, origin, generic.NewPathFromString(testCase.destination), destination, generic.NewParallelApplyOptions(nil))
|
||||
assert.False(t, found)
|
||||
}
|
||||
assert.True(t, generic.TreeParallelApply(ctx, originTree, generic.NewPathFromString(testCase.start), destinationTree, generic.NewParallelApplyOptions(nodeApply)))
|
||||
assert.True(t, called)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue