342 lines
9.7 KiB
Go
342 lines
9.7 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"
|
||
|
"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))
|
||
|
}
|