249 lines
6.3 KiB
Go
249 lines
6.3 KiB
Go
package nettrie
|
|
|
|
import (
|
|
"net/netip"
|
|
"sort"
|
|
"testing"
|
|
)
|
|
|
|
// TestTrie_InsertAndContains_IPv4 tests insertion and verifies presence
|
|
// using ContainsPrefix and Contains for IPv4.
|
|
func TestTrie_InsertAndContains_IPv4(t *testing.T) {
|
|
trie := New()
|
|
|
|
prefixes := []string{
|
|
"0.0.0.0/0",
|
|
"10.0.0.0/8",
|
|
"192.168.0.0/16",
|
|
"192.168.1.0/24",
|
|
"192.168.1.128/25",
|
|
"192.168.1.200/32",
|
|
}
|
|
for _, s := range prefixes {
|
|
trie.Insert(netip.MustParsePrefix(s))
|
|
}
|
|
|
|
t.Run("Check existing prefixes", func(t *testing.T) {
|
|
for _, s := range prefixes {
|
|
p := netip.MustParsePrefix(s)
|
|
if !trie.ContainsPrefix(p) {
|
|
t.Errorf("expected trie to contain prefix %s, but it did not", p)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Check non-existing prefixes", func(t *testing.T) {
|
|
nonExistentPrefixes := []string{
|
|
"10.0.0.0/9",
|
|
"192.168.1.0/23",
|
|
"1.2.3.4/32",
|
|
}
|
|
for _, s := range nonExistentPrefixes {
|
|
p := netip.MustParsePrefix(s)
|
|
if trie.ContainsPrefix(p) {
|
|
t.Errorf("expected trie to not contain prefix %s, but it did", p)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Check host addresses with Contains", func(t *testing.T) {
|
|
if !trie.Contains(netip.MustParseAddr("192.168.1.200")) {
|
|
t.Error("expected trie to contain host address 192.168.1.200, but it did not")
|
|
}
|
|
if trie.Contains(netip.MustParseAddr("192.168.1.201")) {
|
|
t.Error("expected trie to not contain host address 192.168.1.201, but it did")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestTrie_InsertAndContains_IPv6 tests insertion and verifies presence
|
|
// using ContainsPrefix and Contains for IPv6.
|
|
func TestTrie_InsertAndContains_IPv6(t *testing.T) {
|
|
trie := New()
|
|
|
|
prefixes := []string{
|
|
"::/0",
|
|
"2001:db8::/32",
|
|
"2001:db8:acad::/48",
|
|
"2001:db8:acad:1::/64",
|
|
"2001:db8:acad:1::1/128",
|
|
}
|
|
for _, s := range prefixes {
|
|
trie.Insert(netip.MustParsePrefix(s))
|
|
}
|
|
|
|
t.Run("Check existing prefixes", func(t *testing.T) {
|
|
for _, s := range prefixes {
|
|
p := netip.MustParsePrefix(s)
|
|
if !trie.ContainsPrefix(p) {
|
|
t.Errorf("expected trie to contain prefix %s, but it did not", p)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Check non-existing prefixes", func(t *testing.T) {
|
|
nonExistentPrefixes := []string{
|
|
"2001:db8::/33",
|
|
"2001:db8:acad::/47",
|
|
"2002::/16",
|
|
}
|
|
for _, s := range nonExistentPrefixes {
|
|
p := netip.MustParsePrefix(s)
|
|
if trie.ContainsPrefix(p) {
|
|
t.Errorf("expected trie to not contain prefix %s, but it did", p)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Check host addresses with Contains", func(t *testing.T) {
|
|
if !trie.Contains(netip.MustParseAddr("2001:db8:acad:1::1")) {
|
|
t.Error("expected trie to contain host address 2001:db8:acad:1::1, but it did not")
|
|
}
|
|
if trie.Contains(netip.MustParseAddr("2001:db8:acad:1::2")) {
|
|
t.Error("expected trie to not contain host address 2001:db8:acad:1::2, but it did")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTrie_Delete(t *testing.T) {
|
|
trie := New()
|
|
|
|
p8 := netip.MustParsePrefix("10.0.0.0/8")
|
|
p24 := netip.MustParsePrefix("10.0.0.0/24")
|
|
pOther := netip.MustParsePrefix("10.0.1.0/24")
|
|
|
|
trie.Insert(p8)
|
|
trie.Insert(p24)
|
|
trie.Insert(pOther)
|
|
|
|
t.Run("Delete Non-Existent", func(t *testing.T) {
|
|
if trie.Delete(netip.MustParsePrefix("1.2.3.4/32")) {
|
|
t.Error("Delete returned true for a non-existent prefix")
|
|
}
|
|
})
|
|
|
|
t.Run("Delete Leaf Node", func(t *testing.T) {
|
|
if !trie.Delete(p24) {
|
|
t.Fatal("Delete returned false for an existing prefix")
|
|
}
|
|
if trie.ContainsPrefix(p24) {
|
|
t.Error("ContainsPrefix should be false after delete")
|
|
}
|
|
if !trie.ContainsPrefix(p8) {
|
|
t.Error("parent prefix should not be affected by child delete")
|
|
}
|
|
})
|
|
|
|
t.Run("Delete Intermediate Node", func(t *testing.T) {
|
|
// Re-insert p24 so we can delete its parent p8
|
|
trie.Insert(p24)
|
|
|
|
if !trie.Delete(p8) {
|
|
t.Fatal("Delete returned false for an existing intermediate prefix")
|
|
}
|
|
if trie.ContainsPrefix(p8) {
|
|
t.Error("ContainsPrefix for deleted intermediate node should be false")
|
|
}
|
|
if !trie.ContainsPrefix(p24) {
|
|
t.Error("child prefix should still exist after parent was deleted")
|
|
}
|
|
if !trie.ContainsPrefix(pOther) {
|
|
t.Error("other prefix should still exist after parent was deleted")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTrie_Walk(t *testing.T) {
|
|
trie := New()
|
|
prefixes := []string{
|
|
"10.0.0.0/8",
|
|
"192.168.1.0/24",
|
|
"2001:db8::/32",
|
|
"172.16.0.0/12",
|
|
"2001:db8:acad::/48",
|
|
}
|
|
for _, s := range prefixes {
|
|
trie.Insert(netip.MustParsePrefix(s))
|
|
}
|
|
|
|
t.Run("Walk all prefixes", func(t *testing.T) {
|
|
var walkedPrefixes []string
|
|
trie.Walk(func(p netip.Prefix) bool {
|
|
walkedPrefixes = append(walkedPrefixes, p.String())
|
|
return true
|
|
})
|
|
|
|
if len(walkedPrefixes) != len(prefixes) {
|
|
t.Fatalf("expected to walk %d prefixes, but got %d", len(prefixes), len(walkedPrefixes))
|
|
}
|
|
|
|
sort.Strings(prefixes)
|
|
sort.Strings(walkedPrefixes)
|
|
|
|
for i := range prefixes {
|
|
if prefixes[i] != walkedPrefixes[i] {
|
|
t.Errorf("walked prefixes mismatch: expected %v, got %v", prefixes, walkedPrefixes)
|
|
break
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Stop walk early", func(t *testing.T) {
|
|
count := 0
|
|
trie.Walk(func(p netip.Prefix) bool {
|
|
count++
|
|
return count < 3 // Stop after visiting 3 prefixes
|
|
})
|
|
|
|
if count != 3 {
|
|
t.Errorf("expected walk to stop after 3 prefixes, but it visited %d", count)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestTrie_Merge(t *testing.T) {
|
|
trieA := New()
|
|
trieA.Insert(netip.MustParsePrefix("10.0.0.0/8"))
|
|
trieA.Insert(netip.MustParsePrefix("192.168.1.0/24"))
|
|
trieA.Insert(netip.MustParsePrefix("2001:db8::/32")) // v6
|
|
|
|
trieB := New()
|
|
trieB.Insert(netip.MustParsePrefix("10.1.0.0/16"))
|
|
trieB.Insert(netip.MustParsePrefix("192.168.1.0/24")) // Overlap
|
|
trieB.Insert(netip.MustParsePrefix("172.16.0.0/12"))
|
|
trieB.Insert(netip.MustParsePrefix("2001:db8:acad::/48")) // v6
|
|
|
|
trieA.Merge(trieB)
|
|
|
|
expectedPrefixes := []string{
|
|
// From A
|
|
"10.0.0.0/8",
|
|
"192.168.1.0/24",
|
|
"2001:db8::/32",
|
|
// From B
|
|
"10.1.0.0/16",
|
|
"172.16.0.0/12",
|
|
"2001:db8:acad::/48",
|
|
}
|
|
|
|
for _, s := range expectedPrefixes {
|
|
p := netip.MustParsePrefix(s)
|
|
if !trieA.ContainsPrefix(p) {
|
|
t.Errorf("after merge, trieA is missing prefix %s", p)
|
|
}
|
|
}
|
|
|
|
// Check a prefix that should not be there
|
|
if trieA.ContainsPrefix(netip.MustParsePrefix("9.9.9.9/32")) {
|
|
t.Error("trieA contains a prefix it should not have")
|
|
}
|
|
|
|
// Verify trieB is unchanged
|
|
if !trieB.ContainsPrefix(netip.MustParsePrefix("172.16.0.0/12")) {
|
|
t.Error("trieB was modified during merge")
|
|
}
|
|
if trieB.ContainsPrefix(netip.MustParsePrefix("10.0.0.0/8")) {
|
|
t.Error("trieB contains a prefix from trieA")
|
|
}
|
|
}
|