btree_test.gno

18.93 Kb ยท 680 lines
  1package btree
  2
  3import (
  4	"fmt"
  5	"sort"
  6	"testing"
  7)
  8
  9// Content represents a key-value pair where the Key can be either an int or string
 10// and the Value can be any type.
 11type Content struct {
 12	Key   any
 13	Value any
 14}
 15
 16// Less compares two Content records by their Keys.
 17// The Key must be either an int or a string.
 18func (c Content) Less(than Record) bool {
 19	other, ok := than.(Content)
 20	if !ok {
 21		panic("cannot compare: incompatible types")
 22	}
 23
 24	switch key := c.Key.(type) {
 25	case int:
 26		switch otherKey := other.Key.(type) {
 27		case int:
 28			return key < otherKey
 29		case string:
 30			return true // ints are always less than strings
 31		default:
 32			panic("unsupported key type: must be int or string")
 33		}
 34	case string:
 35		switch otherKey := other.Key.(type) {
 36		case int:
 37			return false // strings are always greater than ints
 38		case string:
 39			return key < otherKey
 40		default:
 41			panic("unsupported key type: must be int or string")
 42		}
 43	default:
 44		panic("unsupported key type: must be int or string")
 45	}
 46}
 47
 48type ContentSlice []Content
 49
 50func (s ContentSlice) Len() int {
 51	return len(s)
 52}
 53
 54func (s ContentSlice) Less(i, j int) bool {
 55	return s[i].Less(s[j])
 56}
 57
 58func (s ContentSlice) Swap(i, j int) {
 59	s[i], s[j] = s[j], s[i]
 60}
 61
 62func (s ContentSlice) Copy() ContentSlice {
 63	newSlice := make(ContentSlice, len(s))
 64	copy(newSlice, s)
 65	return newSlice
 66}
 67
 68// Ensure Content implements the Record interface.
 69var _ Record = Content{}
 70
 71// ****************************************************************************
 72// Test helpers
 73// ****************************************************************************
 74
 75func genericSeeding(tree *BTree, size int) *BTree {
 76	for i := 0; i < size; i++ {
 77		tree.Insert(Content{Key: i, Value: fmt.Sprintf("Value_%d", i)})
 78	}
 79	return tree
 80}
 81
 82func intSlicesCompare(left, right []int) int {
 83	if len(left) != len(right) {
 84		if len(left) > len(right) {
 85			return 1
 86		} else {
 87			return -1
 88		}
 89	}
 90
 91	for position, leftInt := range left {
 92		if leftInt != right[position] {
 93			if leftInt > right[position] {
 94				return 1
 95			} else {
 96				return -1
 97			}
 98		}
 99	}
100
101	return 0
102}
103
104// ****************************************************************************
105// Tests
106// ****************************************************************************
107
108func TestLen(t *testing.T) {
109	length := genericSeeding(New(WithDegree(10)), 7).Len()
110	if length != 7 {
111		t.Errorf("Length is incorrect. Expected 7, but got %d.", length)
112	}
113
114	length = genericSeeding(New(WithDegree(5)), 111).Len()
115	if length != 111 {
116		t.Errorf("Length is incorrect. Expected 111, but got %d.", length)
117	}
118
119	length = genericSeeding(New(WithDegree(30)), 123).Len()
120	if length != 123 {
121		t.Errorf("Length is incorrect. Expected 123, but got %d.", length)
122	}
123}
124
125func TestHas(t *testing.T) {
126	tree := genericSeeding(New(WithDegree(10)), 40)
127
128	if tree.Has(Content{Key: 7}) != true {
129		t.Errorf("Has(7) reported false, but it should be true.")
130	}
131	if tree.Has(Content{Key: 39}) != true {
132		t.Errorf("Has(40) reported false, but it should be true.")
133	}
134	if tree.Has(Content{Key: 1111}) == true {
135		t.Errorf("Has(1111) reported true, but it should be false.")
136	}
137}
138
139func TestMin(t *testing.T) {
140	min := genericSeeding(New(WithDegree(10)), 53).Min().(Content)
141
142	if min.Key != 0 {
143		t.Errorf("Minimum should have been 0, but it was reported as %d.", min)
144	}
145}
146
147func TestMax(t *testing.T) {
148	max := genericSeeding(New(WithDegree(10)), 53).Max().(Content)
149
150	if max.Key != 52 {
151		t.Errorf("Maximum should have been 52, but it was reported as %d.", max)
152	}
153}
154
155func TestGet(t *testing.T) {
156	tree := genericSeeding(New(WithDegree(10)), 40)
157
158	if val := tree.Get(Content{Key: 7}); val != nil && val.(Content).Value != "Value_7" {
159		t.Errorf("Get(7) should have returned 'Value_7', but it returned %v.", val)
160	}
161	if val := tree.Get(Content{Key: 39}); val != nil && val.(Content).Value != "Value_39" {
162		t.Errorf("Get(39) should have returned 'Value_39', but it returned %v.", val)
163	}
164	if val := tree.Get(Content{Key: 1111}); val != nil {
165		t.Errorf("Get(1111) returned %v, but it should be nil.", val)
166	}
167}
168
169func TestDescend(t *testing.T) {
170	tree := genericSeeding(New(WithDegree(10)), 5)
171
172	expected := []int{4, 3, 2, 1, 0}
173	found := []int{}
174
175	tree.Descend(func(_record Record) bool {
176		record := _record.(Content)
177		found = append(found, record.Key.(int))
178		return true
179	})
180
181	if intSlicesCompare(expected, found) != 0 {
182		t.Errorf("Descend returned the wrong sequence. Expected %v, but got %v.", expected, found)
183	}
184}
185
186func TestDescendGreaterThan(t *testing.T) {
187	tree := genericSeeding(New(WithDegree(10)), 10)
188
189	expected := []int{9, 8, 7, 6, 5}
190	found := []int{}
191
192	tree.DescendGreaterThan(Content{Key: 4}, func(_record Record) bool {
193		record := _record.(Content)
194		found = append(found, record.Key.(int))
195		return true
196	})
197
198	if intSlicesCompare(expected, found) != 0 {
199		t.Errorf("DescendGreaterThan returned the wrong sequence. Expected %v, but got %v.", expected, found)
200	}
201}
202
203func TestDescendLessOrEqual(t *testing.T) {
204	tree := genericSeeding(New(WithDegree(10)), 10)
205
206	expected := []int{4, 3, 2, 1, 0}
207	found := []int{}
208
209	tree.DescendLessOrEqual(Content{Key: 4}, func(_record Record) bool {
210		record := _record.(Content)
211		found = append(found, record.Key.(int))
212		return true
213	})
214
215	if intSlicesCompare(expected, found) != 0 {
216		t.Errorf("DescendLessOrEqual returned the wrong sequence. Expected %v, but got %v.", expected, found)
217	}
218}
219
220func TestDescendRange(t *testing.T) {
221	tree := genericSeeding(New(WithDegree(10)), 10)
222
223	expected := []int{6, 5, 4, 3, 2}
224	found := []int{}
225
226	tree.DescendRange(Content{Key: 6}, Content{Key: 1}, func(_record Record) bool {
227		record := _record.(Content)
228		found = append(found, record.Key.(int))
229		return true
230	})
231
232	if intSlicesCompare(expected, found) != 0 {
233		t.Errorf("DescendRange returned the wrong sequence. Expected %v, but got %v.", expected, found)
234	}
235}
236
237func TestAscend(t *testing.T) {
238	tree := genericSeeding(New(WithDegree(10)), 5)
239
240	expected := []int{0, 1, 2, 3, 4}
241	found := []int{}
242
243	tree.Ascend(func(_record Record) bool {
244		record := _record.(Content)
245		found = append(found, record.Key.(int))
246		return true
247	})
248
249	if intSlicesCompare(expected, found) != 0 {
250		t.Errorf("Ascend returned the wrong sequence. Expected %v, but got %v.", expected, found)
251	}
252}
253
254func TestAscendGreaterOrEqual(t *testing.T) {
255	tree := genericSeeding(New(WithDegree(10)), 10)
256
257	expected := []int{5, 6, 7, 8, 9}
258	found := []int{}
259
260	tree.AscendGreaterOrEqual(Content{Key: 5}, func(_record Record) bool {
261		record := _record.(Content)
262		found = append(found, record.Key.(int))
263		return true
264	})
265
266	if intSlicesCompare(expected, found) != 0 {
267		t.Errorf("AscendGreaterOrEqual returned the wrong sequence. Expected %v, but got %v.", expected, found)
268	}
269}
270
271func TestAscendLessThan(t *testing.T) {
272	tree := genericSeeding(New(WithDegree(10)), 10)
273
274	expected := []int{0, 1, 2, 3, 4}
275	found := []int{}
276
277	tree.AscendLessThan(Content{Key: 5}, func(_record Record) bool {
278		record := _record.(Content)
279		found = append(found, record.Key.(int))
280		return true
281	})
282
283	if intSlicesCompare(expected, found) != 0 {
284		t.Errorf("AscendLessThan returned the wrong sequence. Expected %v, but got %v.", expected, found)
285	}
286}
287
288func TestAscendRange(t *testing.T) {
289	tree := genericSeeding(New(WithDegree(10)), 10)
290
291	expected := []int{2, 3, 4, 5, 6}
292	found := []int{}
293
294	tree.AscendRange(Content{Key: 2}, Content{Key: 7}, func(_record Record) bool {
295		record := _record.(Content)
296		found = append(found, record.Key.(int))
297		return true
298	})
299
300	if intSlicesCompare(expected, found) != 0 {
301		t.Errorf("AscendRange returned the wrong sequence. Expected %v, but got %v.", expected, found)
302	}
303}
304
305func TestDeleteMin(t *testing.T) {
306	tree := genericSeeding(New(WithDegree(3)), 100)
307
308	expected := []int{0, 1, 2, 3, 4}
309	found := []int{}
310
311	found = append(found, tree.DeleteMin().(Content).Key.(int))
312	found = append(found, tree.DeleteMin().(Content).Key.(int))
313	found = append(found, tree.DeleteMin().(Content).Key.(int))
314	found = append(found, tree.DeleteMin().(Content).Key.(int))
315	found = append(found, tree.DeleteMin().(Content).Key.(int))
316
317	if intSlicesCompare(expected, found) != 0 {
318		t.Errorf("5 rounds of DeleteMin returned the wrong elements. Expected  %v, but got %v.", expected, found)
319	}
320}
321
322func TestShift(t *testing.T) {
323	tree := genericSeeding(New(WithDegree(3)), 100)
324
325	expected := []int{0, 1, 2, 3, 4}
326	found := []int{}
327
328	found = append(found, tree.Shift().(Content).Key.(int))
329	found = append(found, tree.Shift().(Content).Key.(int))
330	found = append(found, tree.Shift().(Content).Key.(int))
331	found = append(found, tree.Shift().(Content).Key.(int))
332	found = append(found, tree.Shift().(Content).Key.(int))
333
334	if intSlicesCompare(expected, found) != 0 {
335		t.Errorf("5 rounds of Shift returned the wrong elements. Expected  %v, but got %v.", expected, found)
336	}
337}
338
339func TestDeleteMax(t *testing.T) {
340	tree := genericSeeding(New(WithDegree(3)), 100)
341
342	expected := []int{99, 98, 97, 96, 95}
343	found := []int{}
344
345	found = append(found, tree.DeleteMax().(Content).Key.(int))
346	found = append(found, tree.DeleteMax().(Content).Key.(int))
347	found = append(found, tree.DeleteMax().(Content).Key.(int))
348	found = append(found, tree.DeleteMax().(Content).Key.(int))
349	found = append(found, tree.DeleteMax().(Content).Key.(int))
350
351	if intSlicesCompare(expected, found) != 0 {
352		t.Errorf("5 rounds of DeleteMax returned the wrong elements. Expected  %v, but got %v.", expected, found)
353	}
354}
355
356func TestPop(t *testing.T) {
357	tree := genericSeeding(New(WithDegree(3)), 100)
358
359	expected := []int{99, 98, 97, 96, 95}
360	found := []int{}
361
362	found = append(found, tree.Pop().(Content).Key.(int))
363	found = append(found, tree.Pop().(Content).Key.(int))
364	found = append(found, tree.Pop().(Content).Key.(int))
365	found = append(found, tree.Pop().(Content).Key.(int))
366	found = append(found, tree.Pop().(Content).Key.(int))
367
368	if intSlicesCompare(expected, found) != 0 {
369		t.Errorf("5 rounds of Pop returned the wrong elements. Expected  %v, but got %v.", expected, found)
370	}
371}
372
373func TestInsertGet(t *testing.T) {
374	tree := New(WithDegree(4))
375
376	expected := []Content{}
377
378	for count := 0; count < 20; count++ {
379		value := fmt.Sprintf("Value_%d", count)
380		tree.Insert(Content{Key: count, Value: value})
381		expected = append(expected, Content{Key: count, Value: value})
382	}
383
384	for count := 0; count < 20; count++ {
385		val := tree.Get(Content{Key: count})
386		if val == nil || val.(Content) != expected[count] {
387			t.Errorf("Insert/Get doesn't appear to be working. Expected to retrieve %v with key %d, but got %v.", expected[count], count, val)
388		}
389	}
390}
391
392func TestClone(t *testing.T) {
393	// Implement the clone test
394}
395
396// ***** The following tests are functional or stress testing type tests.
397
398func TestBTree(t *testing.T) {
399	// Create a B-Tree of degree 3
400	tree := New(WithDegree(3))
401
402	// insertData := []Content{}
403	var insertData ContentSlice
404
405	// Insert integer keys
406	intKeys := []int{10, 20, 5, 6, 12, 30, 7, 17}
407	for _, key := range intKeys {
408		content := Content{Key: key, Value: fmt.Sprintf("Value_%d", key)}
409		insertData = append(insertData, content)
410		result := tree.Insert(content)
411		if result != nil {
412			t.Errorf("**** Already in the tree?  %v", result)
413		}
414	}
415
416	// Insert string keys
417	stringKeys := []string{"apple", "banana", "cherry", "date", "fig", "grape"}
418	for _, key := range stringKeys {
419		content := Content{Key: key, Value: fmt.Sprintf("Fruit_%s", key)}
420		insertData = append(insertData, content)
421		tree.Insert(content)
422	}
423
424	if tree.Len() != 14 {
425		t.Errorf("Tree length wrong. Expected 14 but got %d", tree.Len())
426	}
427
428	// Search for existing and non-existing keys
429	searchTests := []struct {
430		test     Content
431		expected bool
432	}{
433		{Content{Key: 10, Value: "Value_10"}, true},
434		{Content{Key: 15, Value: ""}, false},
435		{Content{Key: "banana", Value: "Fruit_banana"}, true},
436		{Content{Key: "kiwi", Value: ""}, false},
437	}
438
439	t.Logf("Search Tests:\n")
440	for _, test := range searchTests {
441		val := tree.Get(test.test)
442
443		if test.expected {
444			if val != nil && val.(Content).Value == test.test.Value {
445				t.Logf("Found expected key:value %v:%v", test.test.Key, test.test.Value)
446			} else {
447				if val == nil {
448					t.Logf("Didn't find %v, but expected", test.test.Key)
449				} else {
450					t.Errorf("Expected key %v:%v, but found %v:%v.", test.test.Key, test.test.Value, val.(Content).Key, val.(Content).Value)
451				}
452			}
453		} else {
454			if val != nil {
455				t.Errorf("Did not expect key %v, but found key:value %v:%v", test.test.Key, val.(Content).Key, val.(Content).Value)
456			} else {
457				t.Logf("Didn't find %v, but wasn't expected", test.test.Key)
458			}
459		}
460	}
461
462	// Iterate in order
463	t.Logf("\nIn-order Iteration:\n")
464	pos := 0
465
466	if tree.Len() != 14 {
467		t.Errorf("Tree length wrong. Expected 14 but got %d", tree.Len())
468	}
469
470	sortedInsertData := insertData.Copy()
471	sort.Sort(sortedInsertData)
472
473	t.Logf("Insert Data Length: %d", len(insertData))
474	t.Logf("Sorted Data Length: %d", len(sortedInsertData))
475	t.Logf("Tree Length: %d", tree.Len())
476
477	tree.Ascend(func(_record Record) bool {
478		record := _record.(Content)
479		t.Logf("Key:Value == %v:%v", record.Key, record.Value)
480		if record.Key != sortedInsertData[pos].Key {
481			t.Errorf("Out of order! Expected %v, but got %v", sortedInsertData[pos].Key, record.Key)
482		}
483		pos++
484		return true
485	})
486	// // Reverse Iterate
487	t.Logf("\nReverse-order Iteration:\n")
488	pos = len(sortedInsertData) - 1
489
490	tree.Descend(func(_record Record) bool {
491		record := _record.(Content)
492		t.Logf("Key:Value == %v:%v", record.Key, record.Value)
493		if record.Key != sortedInsertData[pos].Key {
494			t.Errorf("Out of order! Expected %v, but got %v", sortedInsertData[pos].Key, record.Key)
495		}
496		pos--
497		return true
498	})
499
500	deleteTests := []Content{
501		{Key: 10, Value: "Value_10"},
502		{Key: 15, Value: ""},
503		{Key: "banana", Value: "Fruit_banana"},
504		{Key: "kiwi", Value: ""},
505	}
506	for _, test := range deleteTests {
507		fmt.Printf("\nDeleting %+v\n", test)
508		tree.Delete(test)
509	}
510
511	if tree.Len() != 12 {
512		t.Errorf("Tree length wrong. Expected 12 but got %d", tree.Len())
513	}
514
515	for _, test := range deleteTests {
516		val := tree.Get(test)
517		if val != nil {
518			t.Errorf("Did not expect key %v, but found key:value %v:%v", test.Key, val.(Content).Key, val.(Content).Value)
519		} else {
520			t.Logf("Didn't find %v, but wasn't expected", test.Key)
521		}
522	}
523}
524
525// Write a test that populates a large B-Tree with 1000 records.
526// It should then `Clone` the tree, make some changes to both the original and the clone,
527// And then clone the clone, and make some changes to all three trees, and then check that the changes are isolated
528// to the tree they were made in.
529func TestBTreeCloneIsolation(t *testing.T) {
530	t.Logf("Creating B-Tree of degree 10 with 1000 records\n")
531	size := 1000
532	tree := genericSeeding(New(WithDegree(10)), size)
533
534	// Clone the tree
535	t.Logf("Cloning the tree\n")
536	clone := tree.Clone()
537
538	// Make some changes to the original and the clone
539	t.Logf("Making changes to the original and the clone\n")
540	for i := 0; i < size; i += 2 {
541		content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)}
542		tree.Delete(content)
543		content = Content{Key: i + 1, Value: fmt.Sprintf("Value_%d", i+1)}
544		clone.Delete(content)
545	}
546
547	// Clone the clone
548	t.Logf("Cloning the clone\n")
549	clone2 := clone.Clone()
550
551	// Make some changes to all three trees
552	t.Logf("Making changes to all three trees\n")
553	for i := 0; i < size; i += 3 {
554		content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)}
555		tree.Delete(content)
556		content = Content{Key: i, Value: fmt.Sprintf("Value_%d", i+1)}
557		clone.Delete(content)
558		content = Content{Key: i + 2, Value: fmt.Sprintf("Value_%d", i+2)}
559		clone2.Delete(content)
560	}
561
562	// Check that the changes are isolated to the tree they were made in
563	t.Logf("Checking that the changes are isolated to the tree they were made in\n")
564	for i := 0; i < size; i++ {
565		content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)}
566		val := tree.Get(content)
567
568		if i%3 == 0 || i%2 == 0 {
569			if val != nil {
570				t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value)
571			}
572		} else {
573			if val == nil {
574				t.Errorf("Expected key %v, but didn't find it", content.Key)
575			}
576		}
577
578		val = clone.Get(content)
579		if i%2 != 0 || i%3 == 0 {
580			if val != nil {
581				t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value)
582			}
583		} else {
584			if val == nil {
585				t.Errorf("Expected key %v, but didn't find it", content.Key)
586			}
587		}
588
589		val = clone2.Get(content)
590		if i%2 != 0 || (i-2)%3 == 0 {
591			if val != nil {
592				t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value)
593			}
594		} else {
595			if val == nil {
596				t.Errorf("Expected key %v, but didn't find it", content.Key)
597			}
598		}
599	}
600}
601
602// --------------------
603// Stress tests. Disabled for testing performance
604
605//func TestStress(t *testing.T) {
606//	// Loop through creating B-Trees with a range of degrees from 3 to 12, stepping by 3.
607//	// Insert 1000 records into each tree, then search for each record.
608//	// Delete half of the records, skipping every other one, then search for each record.
609//
610//	for degree := 3; degree <= 12; degree += 3 {
611//		t.Logf("Testing B-Tree of degree %d\n", degree)
612//		tree := New(WithDegree(degree))
613//
614//		// Insert 1000 records
615//		t.Logf("Inserting 1000 records\n")
616//		for i := 0; i < 1000; i++ {
617//			content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)}
618//			tree.Insert(content)
619//		}
620//
621//		// Search for all records
622//		for i := 0; i < 1000; i++ {
623//			content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)}
624//			val := tree.Get(content)
625//			if val == nil {
626//				t.Errorf("Expected key %v, but didn't find it", content.Key)
627//			}
628//		}
629//
630//		// Delete half of the records
631//		for i := 0; i < 1000; i += 2 {
632//			content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)}
633//			tree.Delete(content)
634//		}
635//
636//		// Search for all records
637//		for i := 0; i < 1000; i++ {
638//			content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)}
639//			val := tree.Get(content)
640//			if i%2 == 0 {
641//				if val != nil {
642//					t.Errorf("Didn't expect key %v, but found key:value %v:%v", content.Key, val.(Content).Key, val.(Content).Value)
643//				}
644//			} else {
645//				if val == nil {
646//					t.Errorf("Expected key %v, but didn't find it", content.Key)
647//				}
648//			}
649//		}
650//	}
651//
652//	// Now create a very large tree, with 100000 records
653//	// Then delete roughly one third of them, using a very basic random number generation scheme
654//	// (implement it right here) to determine which records to delete.
655//	// Print a few lines using Logf to let the user know what's happening.
656//
657//	t.Logf("Testing B-Tree of degree 10 with 100000 records\n")
658//	tree := New(WithDegree(10))
659//
660//	// Insert 100000 records
661//	t.Logf("Inserting 100000 records\n")
662//	for i := 0; i < 100000; i++ {
663//		content := Content{Key: i, Value: fmt.Sprintf("Value_%d", i)}
664//		tree.Insert(content)
665//	}
666//
667//	// Implement a very basic random number generator
668//	seed := 0
669//	random := func() int {
670//		seed = (seed*1103515245 + 12345) & 0x7fffffff
671//		return seed
672//	}
673//
674//	// Delete one third of the records
675//	t.Logf("Deleting one third of the records\n")
676//	for i := 0; i < 35000; i++ {
677//		content := Content{Key: random() % 100000, Value: fmt.Sprintf("Value_%d", i)}
678//		tree.Delete(content)
679//	}
680//}