// Copyright Earl Warren // Copyright Loïc Dachary // 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)) }