Files
herolib/lib/data/radixtree/correctness_test.v
2025-10-12 12:30:19 +03:00

272 lines
7.8 KiB
V

module radixtree
import incubaid.herolib.ui.console
// Test for the critical bug: prefix-of-existing edge inserted after the longer key
fn test_prefix_overlap_bug() ! {
console.print_debug('Testing prefix overlap bug fix')
mut rt := new(path: '/tmp/radixtree_prefix_overlap_test', reset: true)!
// Insert longer key first
rt.set('test', 'value1'.bytes())!
rt.set('testing', 'value2'.bytes())!
// Now insert shorter key that is a prefix - this was the bug
rt.set('te', 'value3'.bytes())!
// Verify all keys work regardless of child iteration order
value1 := rt.get('test')!
assert value1.bytestr() == 'value1', 'Failed to get "test"'
value2 := rt.get('testing')!
assert value2.bytestr() == 'value2', 'Failed to get "testing"'
value3 := rt.get('te')!
assert value3.bytestr() == 'value3', 'Failed to get "te"'
// Test that all keys are found in list
all_keys := rt.list('')!
assert 'test' in all_keys, '"test" not found in list'
assert 'testing' in all_keys, '"testing" not found in list'
assert 'te' in all_keys, '"te" not found in list'
console.print_debug('Prefix overlap bug test passed')
}
// Test partial overlap where neither key is a prefix of the other
fn test_partial_overlap_split() ! {
console.print_debug('Testing partial overlap split')
mut rt := new(path: '/tmp/radixtree_partial_overlap_test', reset: true)!
// Insert keys that share a common prefix but neither is a prefix of the other
rt.set('foobar', 'value1'.bytes())!
console.print_debug('After inserting foobar')
rt.print_tree()!
rt.set('foobaz', 'value2'.bytes())!
console.print_debug('After inserting foobaz')
rt.print_tree()!
// Verify both keys work
value1 := rt.get('foobar')!
assert value1.bytestr() == 'value1', 'Failed to get "foobar"'
value2 := rt.get('foobaz')!
assert value2.bytestr() == 'value2', 'Failed to get "foobaz"'
// Test prefix search
foo_keys := rt.list('foo')!
console.print_debug('foo_keys: ${foo_keys}')
assert foo_keys.len == 2, 'Expected 2 keys with prefix "foo"'
assert 'foobar' in foo_keys, '"foobar" not found with prefix "foo"'
assert 'foobaz' in foo_keys, '"foobaz" not found with prefix "foo"'
fooba_keys := rt.list('fooba')!
assert fooba_keys.len == 2, 'Expected 2 keys with prefix "fooba"'
console.print_debug('Partial overlap split test passed')
}
// Test deletion with path compression
fn test_deletion_compression() ! {
console.print_debug('Testing deletion with path compression')
mut rt := new(path: '/tmp/radixtree_deletion_compression_test', reset: true)!
// Insert keys that will create intermediate nodes
rt.set('car', 'value1'.bytes())!
rt.set('cargo', 'value2'.bytes())!
// Verify both keys exist
value1 := rt.get('car')!
assert value1.bytestr() == 'value1', 'Failed to get "car"'
value2 := rt.get('cargo')!
assert value2.bytestr() == 'value2', 'Failed to get "cargo"'
// Delete the shorter key
rt.delete('car')!
// Verify the longer key still works (tests compression)
value2_after := rt.get('cargo')!
assert value2_after.bytestr() == 'value2', 'Failed to get "cargo" after deletion'
// Verify the deleted key is gone
if _ := rt.get('car') {
assert false, 'Expected "car" to be deleted'
}
console.print_debug('Deletion compression test passed')
}
// Test large fan-out to stress the system
fn test_large_fanout() ! {
console.print_debug('Testing large fan-out')
mut rt := new(path: '/tmp/radixtree_large_fanout_test', reset: true)!
// Insert keys with single character differences to create large fan-out
for i in 0 .. 100 {
key := 'prefix${i:03d}'
rt.set(key, 'value${i}'.bytes())!
}
// Verify all keys can be retrieved
for i in 0 .. 100 {
key := 'prefix${i:03d}'
value := rt.get(key)!
expected := 'value${i}'
assert value.bytestr() == expected, 'Failed to get key "${key}"'
}
// Test prefix search
prefix_keys := rt.list('prefix')!
assert prefix_keys.len == 100, 'Expected 100 keys with prefix "prefix"'
console.print_debug('Large fan-out test passed')
}
// Test sorted output
fn test_sorted_output() ! {
console.print_debug('Testing sorted output')
mut rt := new(path: '/tmp/radixtree_sorted_test', reset: true)!
// Insert keys in random order
keys := ['zebra', 'apple', 'banana', 'cherry', 'date']
for key in keys {
rt.set(key, '${key}_value'.bytes())!
}
// Get all keys and verify they are sorted
all_keys := rt.list('')!
assert all_keys.len == keys.len, 'Expected ${keys.len} keys'
// Check if sorted (should be: apple, banana, cherry, date, zebra)
expected_order := ['apple', 'banana', 'cherry', 'date', 'zebra']
for i, expected_key in expected_order {
assert all_keys[i] == expected_key, 'Expected key at position ${i} to be "${expected_key}", got "${all_keys[i]}"'
}
console.print_debug('Sorted output test passed')
}
// Test edge case: empty key
fn test_empty_key() ! {
console.print_debug('Testing empty key')
mut rt := new(path: '/tmp/radixtree_empty_key_test', reset: true)!
// Set empty key
rt.set('', 'empty_value'.bytes())!
// Set regular key
rt.set('regular', 'regular_value'.bytes())!
// Verify both work
empty_value := rt.get('')!
assert empty_value.bytestr() == 'empty_value', 'Failed to get empty key'
regular_value := rt.get('regular')!
assert regular_value.bytestr() == 'regular_value', 'Failed to get "regular"'
// Test list with empty prefix
all_keys := rt.list('')!
assert all_keys.len == 2, 'Expected 2 keys total'
assert '' in all_keys, 'Empty key not found in list'
assert 'regular' in all_keys, '"regular" not found in list'
console.print_debug('Empty key test passed')
}
// Test very long keys
fn test_long_keys() ! {
console.print_debug('Testing very long keys')
mut rt := new(path: '/tmp/radixtree_long_keys_test', reset: true)!
// Create very long keys
long_key1 := 'a'.repeat(1000) + 'key1'
long_key2 := 'a'.repeat(1000) + 'key2'
long_key3 := 'b'.repeat(500) + 'different'
rt.set(long_key1, 'value1'.bytes())!
rt.set(long_key2, 'value2'.bytes())!
rt.set(long_key3, 'value3'.bytes())!
// Verify retrieval
value1 := rt.get(long_key1)!
assert value1.bytestr() == 'value1', 'Failed to get long_key1'
value2 := rt.get(long_key2)!
assert value2.bytestr() == 'value2', 'Failed to get long_key2'
value3 := rt.get(long_key3)!
assert value3.bytestr() == 'value3', 'Failed to get long_key3'
// Test prefix search with long prefix
long_prefix_keys := rt.list('a'.repeat(1000))!
assert long_prefix_keys.len == 2, 'Expected 2 keys with long prefix'
console.print_debug('Long keys test passed')
}
// Test complex overlapping scenarios
fn test_complex_overlaps() ! {
console.print_debug('Testing complex overlapping scenarios')
mut rt := new(path: '/tmp/radixtree_complex_overlaps_test', reset: true)!
// Create a complex set of overlapping keys
keys := [
'a',
'ab',
'abc',
'abcd',
'abcde',
'abcdef',
'abd',
'ac',
'b',
'ba',
'bb',
]
// Insert in random order to test robustness
for i, key in keys {
rt.set(key, 'value${i}'.bytes())!
}
// Verify all keys can be retrieved
for i, key in keys {
value := rt.get(key)!
expected := 'value${i}'
assert value.bytestr() == expected, 'Failed to get key "${key}"'
}
// Test various prefix searches
a_keys := rt.list('a')!
assert a_keys.len == 8, 'Expected 8 keys with prefix "a"'
ab_keys := rt.list('ab')!
assert ab_keys.len == 6, 'Expected 6 keys with prefix "ab"'
abc_keys := rt.list('abc')!
assert abc_keys.len == 4, 'Expected 4 keys with prefix "abc"'
b_keys := rt.list('b')!
assert b_keys.len == 3, 'Expected 3 keys with prefix "b"'
console.print_debug('Complex overlaps test passed')
}
// Run all correctness tests
fn test_all_correctness() ! {
console.print_debug('Running all correctness tests...')
test_prefix_overlap_bug()!
test_partial_overlap_split()!
test_deletion_compression()!
test_large_fanout()!
test_sorted_output()!
test_empty_key()!
test_long_keys()!
test_complex_overlaps()!
console.print_debug('All correctness tests passed!')
}