go.mway.dev/x@v0.0.0-20240520034138-950aede9a3fb/container/tree/tree_test.go (about)

     1  // Copyright (c) 2024 Matt Way
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to
     5  // deal in the Software without restriction, including without limitation the
     6  // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
     7  // sell copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    18  // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
    19  // IN THE THE SOFTWARE.
    20  
    21  package tree_test
    22  
    23  import (
    24  	"io"
    25  	"strings"
    26  	"testing"
    27  
    28  	"github.com/stretchr/testify/require"
    29  	"go.mway.dev/x/container/tree"
    30  )
    31  
    32  func TestBasicNode(t *testing.T) {
    33  	var zero tree.BasicNode[string, string]
    34  	require.Zero(t, zero.Key())
    35  	require.Zero(t, zero.Value())
    36  	zero.SetValue(t.Name())
    37  	require.Equal(t, t.Name(), zero.Value())
    38  
    39  	require.NotPanics(t, func() {
    40  		var node *tree.BasicNode[string, string]
    41  		node.SetParent(nil)
    42  		node.SetValue("foo")
    43  		require.Equal(t, 0, node.Len())
    44  	})
    45  
    46  	root := tree.NewBasicNode(t.Name(), t.Name())
    47  	require.Equal(t, t.Name(), root.Key())
    48  	require.Equal(t, t.Name(), root.Value())
    49  	require.Nil(t, root.Parent())
    50  	require.Nil(t, root.Children())
    51  	require.Nil(t, root.Child("child1"))
    52  	require.Equal(t, 1, root.Len())
    53  
    54  	child1 := root.Add("child1", "child1")
    55  	require.Equal(t, root, child1.Parent())
    56  	require.Equal(t, child1, root.Child("child1"))
    57  	child1.Add("grandchild11", "grandchild11")
    58  	child1.Add("grandchild12", "grandchild12")
    59  	require.Len(t, child1.Children(), 2)
    60  	require.Equal(t, 3, child1.Len())
    61  	require.Equal(t, 4, root.Len())
    62  
    63  	child2 := root.Add("child2", "child2")
    64  	require.Equal(t, root, child2.Parent())
    65  	require.Equal(t, child2, root.Child("child2"))
    66  	require.Len(t, root.Children(), 2)
    67  	child2.Add("grandchild21", "grandchild21")
    68  	child2.Add("grandchild22", "grandchild22")
    69  	require.Len(t, child2.Children(), 2)
    70  	require.Equal(t, 3, child2.Len())
    71  	require.Equal(t, 7, root.Len())
    72  
    73  	var (
    74  		wantKeys = []string{
    75  			t.Name(),
    76  			"child1",
    77  			"grandchild11",
    78  			"grandchild12",
    79  			"child2",
    80  			"grandchild21",
    81  			"grandchild22",
    82  		}
    83  		haveKeys []string
    84  	)
    85  	err := root.Walk(func(node *tree.BasicNode[string, string]) error {
    86  		haveKeys = append(haveKeys, node.Key())
    87  		return nil
    88  	})
    89  	require.NoError(t, err)
    90  	require.Equal(t, wantKeys, haveKeys)
    91  
    92  	var (
    93  		wantRevKeys = []string{
    94  			"grandchild11",
    95  			"grandchild12",
    96  			"child1",
    97  			"grandchild21",
    98  			"grandchild22",
    99  			"child2",
   100  			t.Name(),
   101  		}
   102  		haveRevKeys []string
   103  	)
   104  	err = root.WalkRev(func(node *tree.BasicNode[string, string]) error {
   105  		haveRevKeys = append(haveRevKeys, node.Key())
   106  		return nil
   107  	})
   108  	require.NoError(t, err)
   109  	require.Equal(t, wantRevKeys, haveRevKeys)
   110  
   111  	var calls int
   112  	err = root.Walk(func(*tree.BasicNode[string, string]) error {
   113  		calls++
   114  		return io.EOF
   115  	})
   116  	require.ErrorIs(t, err, io.EOF)
   117  	err = root.WalkRev(func(*tree.BasicNode[string, string]) error {
   118  		calls++
   119  		return io.EOF
   120  	})
   121  	require.ErrorIs(t, err, io.EOF)
   122  	require.Equal(t, 2, calls)
   123  }
   124  
   125  func TestBasicNode_EmptyOrNil(t *testing.T) {
   126  	cases := map[string]struct {
   127  		node      *tree.BasicNode[string, string]
   128  		wantCalls int
   129  	}{
   130  		"nil": {
   131  			node:      nil,
   132  			wantCalls: 0,
   133  		},
   134  		"empty": {
   135  			node:      &tree.BasicNode[string, string]{},
   136  			wantCalls: 2,
   137  		},
   138  	}
   139  
   140  	for name, tt := range cases {
   141  		t.Run(name, func(t *testing.T) {
   142  			require.NotPanics(t, func() {
   143  				require.Zero(t, tt.node.Key())
   144  				require.Zero(t, tt.node.Value())
   145  				require.Nil(t, tt.node.Parent())
   146  				require.Nil(t, tt.node.Children())
   147  				require.Nil(t, tt.node.Child("foo"))
   148  				require.Equal(t, tt.node == nil, tt.node.Len() == 0)
   149  
   150  				child := tt.node.Add("foo", "bar")
   151  				require.NotNil(t, child)
   152  				require.Equal(t, tt.node, child.Parent())
   153  				require.Equal(t, 1, child.Len())
   154  
   155  				var calls int
   156  				tt.node.Walk(func(*tree.BasicNode[string, string]) error { //nolint:errcheck
   157  					calls++
   158  					return nil
   159  				})
   160  				tt.node.WalkRev(func(*tree.BasicNode[string, string]) error { //nolint:errcheck
   161  					calls++
   162  					return nil
   163  				})
   164  				require.Equal(t, tt.wantCalls*2, calls)
   165  			})
   166  		})
   167  	}
   168  }
   169  
   170  func TestBasicNode_Path(t *testing.T) {
   171  	a := tree.NewBasicNode("a", "a")
   172  	b := a.Add("b", "b")
   173  	b.Add("c", "c")
   174  
   175  	var (
   176  		expect = [][]string{
   177  			{"a"},
   178  			{"a", "b"},
   179  			{"a", "b", "c"},
   180  		}
   181  		expectRev = [][]string{
   182  			{"a"},
   183  			{"b", "a"},
   184  			{"c", "b", "a"},
   185  		}
   186  	)
   187  
   188  	var i int
   189  	a.Walk(func(n *tree.BasicNode[string, string]) error { //nolint:errcheck
   190  		require.Equal(t, expect[i], n.Path())
   191  		require.Equal(t, expectRev[i], n.PathRev())
   192  		i++
   193  		return nil
   194  	})
   195  }
   196  
   197  func TestBasicNode_SetParent(t *testing.T) {
   198  	a := tree.NewBasicNode("a", "b")
   199  	b := tree.NewBasicNode("b", "b")
   200  	c := tree.NewBasicNode("c", "c")
   201  	c.SetParent(b)
   202  	c.SetParent(a)
   203  
   204  	var (
   205  		expect = map[string]int{
   206  			"a": 1,
   207  			"c": 1,
   208  		}
   209  		actual = make(map[string]int)
   210  	)
   211  
   212  	a.Walk(func(n *tree.BasicNode[string, string]) error { //nolint:errcheck
   213  		actual[n.Key()]++
   214  		return nil
   215  	})
   216  
   217  	require.Equal(t, expect, actual)
   218  }
   219  
   220  func TestBasicNode_Remove(t *testing.T) {
   221  	a := tree.NewBasicNode("a", "b")
   222  	b := tree.NewBasicNode("b", "b")
   223  	c := tree.NewBasicNode("c", "c")
   224  	c.SetParent(b)
   225  
   226  	removedC, ok := b.Remove(c.Key())
   227  	require.True(t, ok)
   228  	require.Equal(t, c, removedC)
   229  
   230  	var (
   231  		expect = map[string]int{
   232  			"a": 1,
   233  		}
   234  		actual = make(map[string]int)
   235  	)
   236  
   237  	a.Walk(func(n *tree.BasicNode[string, string]) error { //nolint:errcheck
   238  		actual[n.Key()]++
   239  		return nil
   240  	})
   241  
   242  	require.Equal(t, expect, actual)
   243  }
   244  
   245  func TestBasicNode_WalkErrors(t *testing.T) {
   246  	root := tree.NewBasicNode(t.Name(), t.Name()+"value")
   247  	require.Equal(t, t.Name(), root.Key())
   248  	require.Equal(t, t.Name()+"value", root.Value())
   249  	require.Nil(t, root.Parent())
   250  	require.Nil(t, root.Children())
   251  	require.Nil(t, root.Child("child1"))
   252  	require.Equal(t, 1, root.Len())
   253  
   254  	child1 := root.Add("child1", "value")
   255  	require.Equal(t, root, child1.Parent())
   256  	require.Equal(t, child1, root.Child("child1"))
   257  	child1.Add("grandchild11", "value")
   258  	child1.Add("grandchild12", "value")
   259  	require.Len(t, child1.Children(), 2)
   260  	require.Equal(t, 3, child1.Len())
   261  	require.Equal(t, 4, root.Len())
   262  
   263  	child2 := root.Add("child2", "childv2")
   264  	require.Equal(t, root, child2.Parent())
   265  	require.Equal(t, child2, root.Child("child2"))
   266  	require.Len(t, root.Children(), 2)
   267  	child2.Add("grandchild21", "value")
   268  	child2.Add("grandchild22", "value")
   269  	require.Len(t, child2.Children(), 2)
   270  	require.Equal(t, 3, child2.Len())
   271  	require.Equal(t, 7, root.Len())
   272  
   273  	rootKey := t.Name()
   274  
   275  	cases := map[string]struct { //nolint:govet
   276  		pred    func(key string) error
   277  		want    []string
   278  		wantRev []string
   279  		wantErr error
   280  	}{
   281  		"abort at child2": {
   282  			pred: func(key string) error {
   283  				if key == "child2" {
   284  					return io.EOF
   285  				}
   286  				return nil
   287  			},
   288  			want: []string{
   289  				rootKey,
   290  				"child1",
   291  				"grandchild11",
   292  				"grandchild12",
   293  			},
   294  			wantRev: []string{
   295  				"grandchild11",
   296  				"grandchild12",
   297  				"child1",
   298  				"grandchild21",
   299  				"grandchild22",
   300  			},
   301  			wantErr: io.EOF,
   302  		},
   303  		"no grandchildren": {
   304  			pred: func(key string) error {
   305  				if strings.HasPrefix(key, "grand") {
   306  					return tree.ErrSkipSubtree
   307  				}
   308  				return nil
   309  			},
   310  			want: []string{
   311  				rootKey,
   312  				"child1",
   313  				"child2",
   314  			},
   315  			wantRev: []string{
   316  				"child1",
   317  				"child2",
   318  				rootKey,
   319  			},
   320  			wantErr: nil,
   321  		},
   322  		"root only": {
   323  			pred: func(key string) error {
   324  				if key != rootKey {
   325  					return tree.ErrSkipSubtree
   326  				}
   327  				return nil
   328  			},
   329  			want: []string{
   330  				rootKey,
   331  			},
   332  			wantRev: []string{
   333  				rootKey,
   334  			},
   335  			wantErr: nil,
   336  		},
   337  		"no child1": {
   338  			pred: func(key string) error {
   339  				if key == "child1" {
   340  					return tree.ErrSkipSubtree
   341  				}
   342  				return nil
   343  			},
   344  			want: []string{
   345  				rootKey,
   346  				"child2",
   347  				"grandchild21",
   348  				"grandchild22",
   349  			},
   350  			wantRev: []string{
   351  				"grandchild11",
   352  				"grandchild12",
   353  				"grandchild21",
   354  				"grandchild22",
   355  				"child2",
   356  				rootKey,
   357  			},
   358  			wantErr: nil,
   359  		},
   360  	}
   361  
   362  	for name, tt := range cases {
   363  		t.Run(name, func(t *testing.T) {
   364  			var (
   365  				seen   []string
   366  				walker = func(node *tree.BasicNode[string, string]) (err error) {
   367  					if err = tt.pred(node.Key()); err == nil {
   368  						seen = append(seen, node.Key())
   369  					}
   370  					return err
   371  				}
   372  			)
   373  
   374  			err := root.Walk(walker)
   375  			require.Equal(t, tt.want, seen)
   376  			require.ErrorIs(t, err, tt.wantErr)
   377  
   378  			seen = seen[:0]
   379  			err = root.WalkRev(walker)
   380  			require.Equal(t, tt.wantRev, seen)
   381  			require.ErrorIs(t, err, tt.wantErr)
   382  		})
   383  	}
   384  }