summaryrefslogtreecommitdiff
path: root/lib/rbtree/rbtree_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rbtree/rbtree_test.go')
-rw-r--r--lib/rbtree/rbtree_test.go126
1 files changed, 126 insertions, 0 deletions
diff --git a/lib/rbtree/rbtree_test.go b/lib/rbtree/rbtree_test.go
new file mode 100644
index 0000000..9b8e02c
--- /dev/null
+++ b/lib/rbtree/rbtree_test.go
@@ -0,0 +1,126 @@
+package rbtree
+
+import (
+ "sort"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ "golang.org/x/exp/constraints"
+)
+
+func checkTree[K constraints.Ordered, V any](t *testing.T, expectedSet map[K]struct{}, tree *Tree[K, V]) {
+ // 1. Every node is either red or black
+
+ // 2. The root is black.
+ require.Equal(t, Black, tree.root.getColor())
+
+ // 3. Every nil is black.
+
+ // 4. If a node is red, then both its children are black.
+ require.NoError(t, tree.Walk(func(node *Node[V]) error {
+ if node.getColor() == Red {
+ require.Equal(t, Black, node.Left.getColor())
+ require.Equal(t, Black, node.Right.getColor())
+ }
+ return nil
+ }))
+
+ // 5. For each node, all simple paths from the node to
+ // descendent leaves contain the same number of black
+ // nodes.
+ var walkCnt func(node *Node[V], cnt int, leafFn func(int))
+ walkCnt = func(node *Node[V], cnt int, leafFn func(int)) {
+ if node.getColor() == Black {
+ cnt++
+ }
+ if node == nil {
+ leafFn(cnt)
+ return
+ }
+ walkCnt(node.Left, cnt, leafFn)
+ walkCnt(node.Right, cnt, leafFn)
+ }
+ require.NoError(t, tree.Walk(func(node *Node[V]) error {
+ var cnts []int
+ walkCnt(node, 0, func(cnt int) {
+ cnts = append(cnts, cnt)
+ })
+ for i := range cnts {
+ if cnts[0] != cnts[i] {
+ require.Truef(t, false, "node %v: not all leafs have same black-count: %v", node.Value, cnts)
+ break
+ }
+ }
+ return nil
+ }))
+
+ // expected contents
+ expectedOrder := make([]K, 0, len(expectedSet))
+ for k := range expectedSet {
+ expectedOrder = append(expectedOrder, k)
+ node := tree.Lookup(k)
+ require.NotNil(t, tree, node)
+ require.Equal(t, k, tree.KeyFn(node.Value))
+ }
+ sort.Slice(expectedOrder, func(i, j int) bool {
+ return expectedOrder[i] < expectedOrder[j]
+ })
+ actOrder := make([]K, 0, len(expectedSet))
+ require.NoError(t, tree.Walk(func(node *Node[V]) error {
+ actOrder = append(actOrder, tree.KeyFn(node.Value))
+ return nil
+ }))
+ require.Equal(t, expectedOrder, actOrder)
+}
+
+func FuzzTree(f *testing.F) {
+ Ins := uint8(0b0100_0000)
+ Del := uint8(0)
+
+ f.Add([]uint8{})
+ f.Add([]uint8{Ins | 5, Del | 5})
+ f.Add([]uint8{Ins | 5, Del | 6})
+ f.Add([]uint8{Del | 6})
+
+ f.Add([]uint8{ // CLRS Figure 14.4
+ Ins | 1,
+ Ins | 2,
+ Ins | 5,
+ Ins | 7,
+ Ins | 8,
+ Ins | 11,
+ Ins | 14,
+ Ins | 15,
+
+ Ins | 4,
+ })
+
+ f.Fuzz(func(t *testing.T, dat []uint8) {
+ tree := &Tree[uint8, uint8]{
+ KeyFn: func(x uint8) uint8 { return x },
+ }
+ set := make(map[uint8]struct{})
+ checkTree(t, set, tree)
+ t.Logf("\n%s\n", tree.ASCIIArt())
+ for _, b := range dat {
+ ins := (b & 0b0100_0000) != 0
+ val := (b & 0b0011_1111)
+ if ins {
+ t.Logf("Insert(%v)", val)
+ tree.Insert(val)
+ set[val] = struct{}{}
+ t.Logf("\n%s\n", tree.ASCIIArt())
+ node := tree.Lookup(val)
+ require.NotNil(t, node)
+ require.Equal(t, val, node.Value)
+ } else {
+ t.Logf("Delete(%v)", val)
+ tree.Delete(val)
+ delete(set, val)
+ t.Logf("\n%s\n", tree.ASCIIArt())
+ require.Nil(t, tree.Lookup(val))
+ }
+ checkTree(t, set, tree)
+ }
+ })
+}