Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

compact_test.gno

4.48 Kb · 164 lines
  1package ulist
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/nt/uassert/v0"
  7)
  8
  9// countNodes walks the tree directly, so the tests assert against the real
 10// structure rather than against Compact's own bookkeeping.
 11func countNodes(n *treeNode) int {
 12	if n == nil {
 13		return 0
 14	}
 15	return 1 + countNodes(n.left) + countNodes(n.right)
 16}
 17
 18func TestCompactEmptyAndNil(t *testing.T) {
 19	var nilList *List
 20	uassert.Equal(t, 0, nilList.Compactable())
 21	uassert.Equal(t, 0, nilList.Compact())
 22
 23	l := New()
 24	uassert.Equal(t, 0, l.Compactable())
 25	uassert.Equal(t, 0, l.Compact())
 26}
 27
 28func TestCompactNothingDeleted(t *testing.T) {
 29	l := New()
 30	l.Append(generateSequence(20)...)
 31	before := countNodes(l.root)
 32
 33	uassert.Equal(t, 0, l.Compactable())
 34	uassert.Equal(t, 0, l.Compact())
 35	uassert.Equal(t, before, countNodes(l.root))
 36	uassert.Equal(t, 20, l.Size())
 37	uassert.Equal(t, 20, l.TotalSize())
 38}
 39
 40func TestCompactFreesOnlyFullyDeadSubtrees(t *testing.T) {
 41	tests := []struct {
 42		name    string
 43		size    int
 44		deleted []int
 45	}{
 46		{"nothing deleted", 16, nil},
 47		{"one deleted", 16, []int{9}},
 48		{"leading run", 32, []int{0, 1, 2, 3, 4, 5, 6, 7}},
 49		{"trailing run", 32, []int{24, 25, 26, 27, 28, 29, 30, 31}},
 50		{"every other", 32, []int{1, 3, 5, 7, 9, 11, 13, 15}},
 51		{"all but one", 16, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14}},
 52		{"all of them", 16, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 14}},
 53	}
 54
 55	for _, tt := range tests {
 56		t.Run(tt.name, func(t *testing.T) {
 57			l := New()
 58			l.Append(generateSequence(tt.size)...)
 59			for _, i := range tt.deleted {
 60				uassert.NoError(t, l.Delete(i))
 61			}
 62
 63			// Every surviving element, recorded before the compaction.
 64			survivors := map[int]any{}
 65			for i := 0; i < tt.size; i++ {
 66				if v := l.Get(i); v != nil {
 67					survivors[i] = v
 68				}
 69			}
 70
 71			predicted := l.Compactable()
 72			nodesBefore := countNodes(l.root)
 73			freed := l.Compact()
 74
 75			// Compactable and Compact must agree, and both must match the tree.
 76			uassert.Equal(t, predicted, freed)
 77			uassert.Equal(t, nodesBefore-freed, countNodes(l.root))
 78
 79			// A second pass has nothing left to do: Compact is idempotent.
 80			uassert.Equal(t, 0, l.Compactable())
 81			uassert.Equal(t, 0, l.Compact())
 82
 83			// Indices are preserved: every survivor is still at its own index,
 84			// and the sizes are untouched.
 85			uassert.Equal(t, tt.size, l.TotalSize())
 86			uassert.Equal(t, len(survivors), l.Size())
 87			for i, want := range survivors {
 88				uassert.Equal(t, want, l.Get(i))
 89			}
 90
 91			// Appends continue from the same number.
 92			l.Append("after")
 93			uassert.Equal(t, "after", l.Get(tt.size))
 94			uassert.Equal(t, tt.size+1, l.TotalSize())
 95		})
 96	}
 97}
 98
 99func TestCompactNeverFreesTheRoot(t *testing.T) {
100	// Index 0 is the root. Deleting everything must still leave it, or
101	// findNode would treat the list as empty and write the next append to
102	// index 0.
103	l := New()
104	l.Append("a", "b", "c")
105	uassert.NoError(t, l.Delete(0, 1, 2))
106	l.Compact()
107
108	uassert.True(t, l.root != nil)
109	uassert.Equal(t, 1, countNodes(l.root))
110	uassert.Equal(t, 0, l.Size())
111	uassert.Equal(t, 3, l.TotalSize())
112
113	l.Append("d")
114	uassert.Equal(t, "d", l.Get(3))
115	uassert.Equal(t, nil, l.Get(0))
116}
117
118func TestCompactMakesDeletedElementsUnrestorable(t *testing.T) {
119	// The documented behaviour change against v0: a soft-deleted element can
120	// be restored with Set, but not once its node has been freed and the
121	// storage deposit refunded.
122	l := New()
123	l.Append(generateSequence(8)...)
124	uassert.NoError(t, l.Delete(4, 5, 6, 7))
125
126	// Before compaction: restoring works.
127	uassert.NoError(t, l.Set(5, "restored"))
128	uassert.Equal(t, "restored", l.Get(5))
129	uassert.NoError(t, l.Delete(5))
130
131	uassert.True(t, l.Compact() > 0)
132
133	// After compaction: the node is gone, so there is nothing to restore.
134	uassert.Error(t, l.Set(5, "restored again"))
135	uassert.Equal(t, nil, l.Get(5))
136	// And a live element is still perfectly settable.
137	uassert.NoError(t, l.Set(1, "still here"))
138	uassert.Equal(t, "still here", l.Get(1))
139}
140
141func TestCompactLeavesIterationUnchanged(t *testing.T) {
142	collect := func(l *List) []Entry {
143		var got []Entry
144		l.Iterator(0, l.TotalSize()-1, func(i int, v any) bool {
145			got = append(got, Entry{Index: i, Value: v})
146			return false
147		})
148		return got
149	}
150
151	l := New()
152	l.Append(generateSequence(24)...)
153	uassert.NoError(t, l.Delete(16, 17, 18, 19, 20, 21, 22, 23))
154
155	before := collect(l)
156	l.Compact()
157	after := collect(l)
158
159	uassert.Equal(t, len(before), len(after))
160	for i := range before {
161		uassert.Equal(t, before[i].Index, after[i].Index)
162		uassert.Equal(t, before[i].Value, after[i].Value)
163	}
164}