626 lines
24 KiB
Go
626 lines
24 KiB
Go
// 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)
|
|
})
|
|
}
|
|
}
|