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

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

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}