Files
tsnet-proxy/vendor/github.com/gaissmai/bart/internal/nodes/commontests_tmpl.go
T
2026-10-22 12:27:19 +01:00

1267 lines
32 KiB
Go

//go:build generate
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
//go:generate ../../scripts/generate-node-tests.sh
package nodes
// ### GENERATE DELETE START ###
// stub code for generator types and methods
// useful for gopls during development, deleted during go generate
import (
"bytes"
"io"
"iter"
"math/rand/v2"
"net/netip"
"slices"
"strings"
"testing"
"github.com/gaissmai/bart/internal/tests/golden"
"github.com/gaissmai/bart/internal/tests/random"
"github.com/gaissmai/bart/internal/value"
)
type _NODE_TYPE[V any] struct{}
func (n *_NODE_TYPE[V]) StatsRec() (_ StatsT) { return }
func (n *_NODE_TYPE[V]) dump(io.Writer, StridePath, int, bool) { return }
func (n *_NODE_TYPE[V]) DumpRec(io.Writer, StridePath, int, bool) { return }
func (n *_NODE_TYPE[V]) FprintRec(io.Writer, TrieItem[V], string) (_ error) { return }
func (n *_NODE_TYPE[V]) Insert(netip.Prefix, V, int) (_ bool) { return }
func (n *_NODE_TYPE[V]) Delete(netip.Prefix) (_ bool) { return }
func (n *_NODE_TYPE[V]) InsertPersist(func(V) V, netip.Prefix, V, int) (_ bool) { return }
func (n *_NODE_TYPE[V]) DeletePersist(func(V) V, netip.Prefix) (_ bool) { return }
func (n *_NODE_TYPE[V]) Subnets(netip.Prefix, func(netip.Prefix, V) bool) { return }
func (n *_NODE_TYPE[V]) Supernets(netip.Prefix, func(netip.Prefix, V) bool) { return }
func (n *_NODE_TYPE[V]) AllRec(StridePath, int, bool, func(netip.Prefix, V) bool) (_ bool) { return }
func (n *_NODE_TYPE[V]) AllRecSorted(StridePath, int, bool, func(netip.Prefix, V) bool) (_ bool) {
return
}
// ### GENERATE DELETE END ###
// helpers
func (n *_NODE_TYPE[V]) all4() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if n == nil {
return
}
_ = n.AllRec(StridePath{}, 0, true, yield)
}
}
func (n *_NODE_TYPE[V]) all6() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if n == nil {
return
}
_ = n.AllRec(StridePath{}, 0, false, yield)
}
}
func (n *_NODE_TYPE[V]) allSorted4() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if n == nil {
return
}
_ = n.AllRecSorted(StridePath{}, 0, true, yield)
}
}
func (n *_NODE_TYPE[V]) allSorted6() iter.Seq2[netip.Prefix, V] {
return func(yield func(netip.Prefix, V) bool) {
if n == nil {
return
}
_ = n.AllRecSorted(StridePath{}, 0, false, yield)
}
}
func TestInsertDelete__NODE_TYPE(t *testing.T) {
t.Parallel()
zero := 0
testsInsertDelete := []struct {
name string
pfxs []string
is4 bool
wantPfxs int
wantLeaves int
wantFringes int
}{
{
name: "null",
pfxs: []string{},
is4: true,
wantPfxs: 0,
wantLeaves: 0,
wantFringes: 0,
},
{
name: "one prefix in root node",
pfxs: []string{"0.0.0.0/0"},
is4: true,
wantPfxs: 1,
wantLeaves: 0,
wantFringes: 0,
},
{
name: "one prefix in root node IPv6",
pfxs: []string{"::/0"},
is4: false,
wantPfxs: 1,
wantLeaves: 0,
wantFringes: 0,
},
{
name: "one leaf in root node",
pfxs: []string{"0.0.0.0/32"},
is4: true,
wantPfxs: 0,
wantLeaves: 1,
wantFringes: 0,
},
{
name: "one leaf in root node IPv6",
pfxs: []string{"::/32"},
is4: false,
wantPfxs: 0,
wantLeaves: 1,
wantFringes: 0,
},
{
name: "one fringe in root node",
pfxs: []string{"0.0.0.0/8"},
is4: true,
wantPfxs: 0,
wantLeaves: 0,
wantFringes: 1,
},
{
name: "one fringe in root node IPv6",
pfxs: []string{"0::/8"},
is4: false,
wantPfxs: 0,
wantLeaves: 0,
wantFringes: 1,
},
{
name: "many pfxs in root node",
pfxs: []string{"0.0.0.0/0", "0.0.0.0/1", "0.0.0.0/2", "0.0.0.0/3"},
is4: true,
wantPfxs: 4,
wantLeaves: 0,
wantFringes: 0,
},
{
name: "many pfxs in root node IPv6",
pfxs: []string{"::/0", "::/1", "::/2", "::/3"},
is4: false,
wantPfxs: 4,
wantLeaves: 0,
wantFringes: 0,
},
{
name: "many pfxs and leaves in root node",
pfxs: []string{
"0.0.0.0/0", "0.0.0.0/1", "0.0.0.0/2", "0.0.0.0/3", // pfxs
"0.0.0.0/9", "1.0.0.0/9", "2.0.0.0/9", "3.0.0.0/9", // leaves
},
is4: true,
wantPfxs: 4,
wantLeaves: 4,
wantFringes: 0,
},
{
name: "many pfxs and leaves in root node IPv6",
pfxs: []string{
"::/0", "::/1", "::/2", "::/3", // pfxs
"::/9", "0100::/9", "0200::/9", "0300::/9", // leaves
},
is4: false,
wantPfxs: 4,
wantLeaves: 4,
wantFringes: 0,
},
{
name: "many pfxs, leaves and fringes in root node",
pfxs: []string{
"0.0.0.0/0", "0.0.0.0/1", // pfxs
"0.0.0.0/9", "1.0.0.0/19", "2.0.0.0/29", // leaves
"4.0.0.0/8", "5.0.0.0/8", "6.0.0.0/8", "7.0.0.0/8", // fringes
},
is4: true,
wantPfxs: 2,
wantLeaves: 3,
wantFringes: 4,
},
{
name: "many pfxs, leaves and fringes in root node IPv6",
pfxs: []string{
"::/0", "::/1", // pfxs
"::/9", "0100::/19", "0200::/29", // leaves
"0400::/8", "0500::/8", "0600::/8", "0700::/8", // fringes
},
is4: false,
wantPfxs: 2,
wantLeaves: 3,
wantFringes: 4,
},
{
name: "many pfxs, leaves and fringes in deeper level",
pfxs: []string{
"0.0.0.0/9", "0.0.0.0/10", // pfxs in level 1
"0.1.0.0/19", // leaf in level 1
"0.2.0.0/16", // fringe in level 1
},
is4: true,
wantPfxs: 2,
wantLeaves: 1,
wantFringes: 1,
},
{
name: "many pfxs, leaves and fringes in deeper level IPv6",
pfxs: []string{
"::/9", "::/10", // pfxs in level 1
"0010::/19", // leaf in level 1
"0020::/16", // fringe in level 1
},
is4: false,
wantPfxs: 2,
wantLeaves: 1,
wantFringes: 1,
},
{
name: "leaves and fringes in deeper level",
pfxs: []string{
"0.0.0.0/12", // pfx in level 1
"0.0.0.0/16", // fringe in level 1 -> default pfx in level 2
"0.0.0.0/24", // fringe in level 2
},
is4: true,
wantPfxs: 2,
wantLeaves: 0,
wantFringes: 1,
},
{
name: "leaves and fringes in deeper level IPv6",
pfxs: []string{
"::/12", // pfx in level 1
"::/16", // fringe in level 1 -> default pfx in level 2
"::/24", // fringe in level 2
},
is4: false,
wantPfxs: 2,
wantLeaves: 0,
wantFringes: 1,
},
}
for _, tt := range testsInsertDelete {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
n := new(_NODE_TYPE[int])
for _, s := range tt.pfxs {
n.Insert(mpp(s), zero, 0)
n.Insert(mpp(s), zero, 0) // idempotent
}
stats := n.StatsRec()
if pfxs := stats.Prefixes; pfxs != tt.wantPfxs {
t.Errorf("after insert: got num pfxs %d, want %d", pfxs, tt.wantPfxs)
}
if leaves := stats.Leaves; leaves != tt.wantLeaves {
t.Errorf("after insert: got num leaves %d, want %d", leaves, tt.wantLeaves)
}
if fringes := stats.Fringes; fringes != tt.wantFringes {
t.Errorf("after insert: got num fringes %d, want %d", fringes, tt.wantFringes)
}
if t.Failed() {
buf := new(strings.Builder)
n.DumpRec(buf, StridePath{}, 0, tt.is4)
t.Logf("%s:\n%s", tt.name, buf.String())
}
// delete all prefixes
for _, s := range tt.pfxs {
n.Delete(mpp(s))
n.Delete(mpp(s)) // idempotent
}
stats = n.StatsRec()
if num := stats.Prefixes; num != 0 {
t.Errorf("after delete: got num pfxs %d, want 0", num)
}
if num := stats.Leaves; num != 0 {
t.Errorf("after delete: got num leaves %d, want 0", num)
}
if num := stats.Fringes; num != 0 {
t.Errorf("after delete: got num fringes %d, want 0", num)
}
if t.Failed() {
buf := new(strings.Builder)
n.DumpRec(buf, StridePath{}, 0, tt.is4)
t.Logf("%s:\n%s", tt.name, buf.String())
}
})
t.Run("Persist_"+tt.name, func(t *testing.T) {
t.Parallel()
n := new(_NODE_TYPE[int])
for _, s := range tt.pfxs {
n.InsertPersist(nil, mpp(s), zero, 0)
n.InsertPersist(nil, mpp(s), zero, 0) // idempotent
}
stats := n.StatsRec()
if pfxs := stats.Prefixes; pfxs != tt.wantPfxs {
t.Errorf("after insert: got num pfxs %d, want %d", pfxs, tt.wantPfxs)
}
if leaves := stats.Leaves; leaves != tt.wantLeaves {
t.Errorf("after insert: got num leaves %d, want %d", leaves, tt.wantLeaves)
}
if fringes := stats.Fringes; fringes != tt.wantFringes {
t.Errorf("after insert: got num fringes %d, want %d", fringes, tt.wantFringes)
}
if t.Failed() {
buf := new(strings.Builder)
n.DumpRec(buf, StridePath{}, 0, tt.is4)
t.Logf("%s:\n%s", tt.name, buf.String())
}
// delete all prefixes
for _, s := range tt.pfxs {
n.DeletePersist(nil, mpp(s))
n.DeletePersist(nil, mpp(s)) // idempotent
}
stats = n.StatsRec()
if num := stats.Prefixes; num != 0 {
t.Errorf("after delete: got num pfxs %d, want 0", num)
}
if num := stats.Leaves; num != 0 {
t.Errorf("after delete: got num leaves %d, want 0", num)
}
if num := stats.Fringes; num != 0 {
t.Errorf("after delete: got num fringes %d, want 0", num)
}
if t.Failed() {
buf := new(strings.Builder)
n.DumpRec(buf, StridePath{}, 0, tt.is4)
t.Logf("%s:\n%s", tt.name, buf.String())
}
})
}
}
func TestAllIterators__NODE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
for range 10 {
pfxs := random.RealWorldPrefixes4(prng, n)
node := new(_NODE_TYPE[int])
for _, p := range pfxs {
node.Insert(p, 0, 0)
}
// AllRec: collect without order guarantee
var got []netip.Prefix
i := 0
for p := range node.all4() {
i++
got = append(got, p)
if i >= n/2 {
break
}
}
if len(got) != n/2 {
t.Fatalf("AllRec len=%d, want %d", len(got), n/2)
}
got = nil
i = 0
for p := range node.allSorted4() {
i++
got = append(got, p)
if i >= n/2 {
break
}
}
if len(got) != n/2 {
t.Fatalf("AllRecSorted len=%d, want %d", len(got), n/2)
}
slices.SortFunc(pfxs, CmpPrefix)
if !slices.Equal(pfxs[:n/2], got) {
t.Fatal("AllRecSorted is not as expected")
}
}
}
func TestSupernets4__NODE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
for range 10 {
pfxs := random.RealWorldPrefixes4(prng, n)
node := new(_NODE_TYPE[int])
gold := new(golden.Table[int])
for i, pfx := range pfxs {
node.Insert(pfx, i, 0)
gold.Insert(pfx, i)
}
// test with random probes
for _, probe := range pfxs {
goldSupernets := gold.Supernets(probe)
nodeSupernets := []netip.Prefix{}
node.Supernets(probe, func(p netip.Prefix, _ int) bool {
nodeSupernets = append(nodeSupernets, p)
return true
})
if !slices.Equal(goldSupernets, nodeSupernets) {
t.Errorf("Supernets expected equal to golden implementation")
}
}
}
}
func TestSupernets6__NODE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
for range 10 {
pfxs := random.RealWorldPrefixes6(prng, n)
node := new(_NODE_TYPE[int])
gold := new(golden.Table[int])
for i, pfx := range pfxs {
node.Insert(pfx, i, 0)
gold.Insert(pfx, i)
}
// test with random probes
for _, probe := range pfxs {
goldSupernets := gold.Supernets(probe)
nodeSupernets := []netip.Prefix{}
node.Supernets(probe, func(p netip.Prefix, _ int) bool {
nodeSupernets = append(nodeSupernets, p)
return true
})
if !slices.Equal(goldSupernets, nodeSupernets) {
t.Errorf("Supernets expected equal to golden implementation")
}
}
}
}
func TestSubnets4__NODE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
for range 10 {
pfxs := random.RealWorldPrefixes4(prng, n)
node := new(_NODE_TYPE[int])
gold := new(golden.Table[int])
for i, pfx := range pfxs {
node.Insert(pfx, i, 0)
gold.Insert(pfx, i)
}
// the default route must have all pfxs as subnet
defaultRoute := mpp("0.0.0.0/0")
allPfxsSorted := slices.Clone(pfxs)
slices.SortFunc(allPfxsSorted, CmpPrefix)
nodeSubnets := []netip.Prefix{}
node.Subnets(defaultRoute, func(p netip.Prefix, _ int) bool {
nodeSubnets = append(nodeSubnets, p)
return true
})
if !slices.Equal(allPfxsSorted, nodeSubnets) {
t.Errorf("Subnets(%s) not equal to all sorted prefixes", defaultRoute)
}
kMax := max(1, n/10)
somePfxs := make([]netip.Prefix, 0, kMax) // allocate mem 1x
for k := range kMax {
somePfxs = somePfxs[:0] // reset slice
i := 0
node.Subnets(defaultRoute, func(p netip.Prefix, _ int) bool {
if i >= k {
// early-termination: stop after k
return false
}
i++
somePfxs = append(somePfxs, p)
return true
})
if len(somePfxs) != k {
t.Errorf("Subnets early-termination: got %d items, want %d", len(somePfxs), k)
}
if !slices.Equal(somePfxs, allPfxsSorted[:k]) {
t.Errorf("Subnets expected equal")
}
}
// test with random probes
for _, probe := range pfxs {
goldSubnets := gold.Subnets(probe)
nodeSubnets := []netip.Prefix{}
node.Subnets(probe, func(p netip.Prefix, _ int) bool {
nodeSubnets = append(nodeSubnets, p)
return true
})
if !slices.Equal(goldSubnets, nodeSubnets) {
t.Errorf("Subnets expected equal to golden implementation")
}
}
}
}
func TestSubnets6__NODE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
for range 10 {
pfxs := random.RealWorldPrefixes6(prng, n)
node := new(_NODE_TYPE[int])
gold := new(golden.Table[int])
for i, pfx := range pfxs {
node.Insert(pfx, i, 0)
gold.Insert(pfx, i)
}
// the default route must have all pfxs as subnet
defaultRoute := mpp("::/0")
allPfxsSorted := slices.Clone(pfxs)
slices.SortFunc(allPfxsSorted, CmpPrefix)
nodeSubnets := []netip.Prefix{}
node.Subnets(defaultRoute, func(p netip.Prefix, _ int) bool {
nodeSubnets = append(nodeSubnets, p)
return true
})
if !slices.Equal(allPfxsSorted, nodeSubnets) {
t.Errorf("Subnets(%s) not equal to all sorted prefixes", defaultRoute)
}
kMax := max(1, n/10)
somePfxs := make([]netip.Prefix, 0, kMax) // allocate mem 1x
for k := range kMax {
somePfxs = somePfxs[:0] // reset slice
i := 0
node.Subnets(defaultRoute, func(p netip.Prefix, _ int) bool {
if i >= k {
// early-termination: stop after k
return false
}
i++
somePfxs = append(somePfxs, p)
return true
})
if len(somePfxs) != k {
t.Errorf("Subnets early-termination: got %d items, want %d", len(somePfxs), k)
}
if !slices.Equal(somePfxs, allPfxsSorted[:k]) {
t.Errorf("Subnets expected equal")
}
}
// test with random probes
for _, probe := range pfxs {
goldSubnets := gold.Subnets(probe)
nodeSubnets := []netip.Prefix{}
node.Subnets(probe, func(p netip.Prefix, _ int) bool {
nodeSubnets = append(nodeSubnets, p)
return true
})
if !slices.Equal(goldSubnets, nodeSubnets) {
t.Errorf("Subnets expected equal to golden implementation")
}
}
}
}
// TestDump_ZST verifies that dump does not print values when V is a zero-sized type.
func TestDump_ZST__NODE_TYPE(t *testing.T) {
t.Parallel()
node := new(_NODE_TYPE[struct{}])
// Insert prefix to populate the node
pfx := mpp("10.0.0.0/7")
node.Insert(pfx, struct{}{}, 0)
var buf strings.Builder
path := StridePath{}
node.dump(&buf, path, 0, true)
output := buf.String()
// For ZST, dump should print prefixes(#N) but skip the "values(#N):" section
if !strings.Contains(output, "prefxs(") {
t.Errorf("Expected 'prefxs()' section, but not found in:\n%s", output)
}
// For ZST, dump should print prefxs(#N) but skip the "values(#N):" section
if strings.Contains(output, "values(") {
t.Errorf("Expected no 'values()' section for ZST, but found it in:\n%s", output)
}
}
// TestDump_NonZST verifies that dump prints values when V is not a zero-sized type.
func TestDump_NonZST__NODE_TYPE(t *testing.T) {
t.Parallel()
node := new(_NODE_TYPE[int])
// Skip for LiteNode (no real payload)
if _, isLite := any(node).(*LiteNode[int]); isLite {
t.Skip("LiteNode has no real payload")
}
pfx := mpp("10.0.0.0/7")
node.Insert(pfx, 42, 0)
var buf strings.Builder
path := StridePath{}
node.dump(&buf, path, 0, true)
output := buf.String()
// dump should include the "prefxs(#N):" section
if !strings.Contains(output, "prefxs(") {
t.Errorf("Expected 'prefxs()' section, but not found in:\n%s", output)
}
// For non-ZST, dump should include the "values(#N):" section
if !strings.Contains(output, "values(") {
t.Errorf("Expected 'values()' section for non-ZST, but not found in:\n%s", output)
}
// Should contain the actual value
if !strings.Contains(output, "42") {
t.Errorf("Expected value '42' in output, but not found in:\n%s", output)
}
}
// TestFprintRec_ZST verifies FprintRec does not print values for zero-sized types.
func TestFprintRec_ZST__NODE_TYPE(t *testing.T) {
t.Parallel()
node := new(_NODE_TYPE[struct{}])
pfx := mpp("10.0.0.0/7")
node.Insert(pfx, struct{}{}, 0)
parent := TrieItem[struct{}]{
Node: nil,
Is4: true,
Path: StridePath{},
Depth: 0,
Idx: 0,
Cidr: mpp("0.0.0.0/0"),
}
var buf bytes.Buffer
if err := node.FprintRec(&buf, parent, ""); err != nil {
t.Fatalf("FprintRec failed: %v", err)
}
output := buf.String()
// For ZST, output should show prefix but no value in parentheses
if strings.Contains(output, "10.0.0.0/7 (") || strings.Contains(output, "10.0.0.0/7(") {
t.Errorf("Expected no value in parentheses for ZST prefix, but found in:\n%s", output)
}
}
// TestFprintRec_NonZST verifies FprintRec prints values for non-zero-sized types.
func TestFprintRec_NonZST__NODE_TYPE(t *testing.T) {
t.Parallel()
node := new(_NODE_TYPE[string])
// Skip for LiteNode (no real payload)
if _, isLite := any(node).(*LiteNode[string]); isLite {
t.Skip("LiteNode has no real payload")
}
pfx := mpp("10.0.0.0/7")
node.Insert(pfx, "testval", 0)
parent := TrieItem[string]{
Node: nil,
Is4: true,
Path: StridePath{},
Depth: 0,
Idx: 0,
Cidr: mpp("0.0.0.0/0"),
Val: "Default Gateway",
}
var buf bytes.Buffer
if err := node.FprintRec(&buf, parent, ""); err != nil {
t.Fatalf("FprintRec failed: %v", err)
}
output := buf.String()
// For non-ZST, output should show both prefix and value
if !strings.Contains(output, "10.0.0.0/7") || !strings.Contains(output, "testval") {
t.Errorf("Expected prefix and value 'testval' in output, but got:\n%s", output)
}
}
// TestEqualRec__NODE_TYPE tests the recursive node equality comparison.
func TestEqualRec__NODE_TYPE(t *testing.T) {
t.Parallel()
// nil checks
var nilNode1, nilNode2 *_NODE_TYPE[int]
if !nilNode1.EqualRec(nilNode2) {
t.Error("nil nodes should be equal")
}
n1 := new(_NODE_TYPE[int])
if n1.EqualRec(nilNode1) {
t.Error("non-nil node should not be equal to nil")
}
if nilNode1.EqualRec(n1) {
t.Error("nil node should not be equal to non-nil")
}
// identical nodes
if !n1.EqualRec(n1) {
t.Error("node should be equal to itself")
}
// different prefix bitsets
n2 := new(_NODE_TYPE[int])
n1.Insert(mpp("10.0.0.0/7"), 42, 0)
if n1.EqualRec(n2) {
t.Error("nodes with different prefixes should not be equal")
}
// different values
// Skip for LiteNode because it has no real payload values
if _, isLite := any(n1).(*LiteNode[int]); !isLite {
n2.Insert(mpp("10.0.0.0/7"), 43, 0)
if n1.EqualRec(n2) {
t.Error("nodes with same prefixes but different values should not be equal")
}
}
// same prefix and value
n2_same := new(_NODE_TYPE[int])
n2_same.Insert(mpp("10.0.0.0/7"), 42, 0)
if _, isLite := any(n1).(*LiteNode[int]); !isLite {
if !n1.EqualRec(n2_same) {
t.Error("nodes with same prefixes and values should be equal")
}
}
// different children bitsets
n3 := new(_NODE_TYPE[int])
n4 := new(_NODE_TYPE[int])
n3.Insert(mpp("10.0.0.0/8"), 42, 0) // this is a fringe node at depth 1, and inserts into Children
if n3.EqualRec(n4) {
t.Error("nodes with different children should not be equal")
}
// fringe nodes with different values
if _, isLite := any(n3).(*LiteNode[int]); !isLite {
n4.Insert(mpp("10.0.0.0/8"), 43, 0)
if n3.EqualRec(n4) {
t.Error("nodes with different fringe values should not be equal")
}
}
// leaf nodes with different prefixes/values
n5 := new(_NODE_TYPE[int])
n6 := new(_NODE_TYPE[int])
n5.Insert(mpp("10.20.0.0/15"), 42, 0) // leaf node at depth 1
n6.Insert(mpp("10.30.0.0/15"), 42, 0) // different leaf node prefix
if n5.EqualRec(n6) {
t.Error("nodes with different leaf prefixes should not be equal")
}
if _, isLite := any(n5).(*LiteNode[int]); !isLite {
n7 := new(_NODE_TYPE[int])
n7.Insert(mpp("10.20.0.0/15"), 43, 0) // different leaf value
if n5.EqualRec(n7) {
t.Error("nodes with different leaf values should not be equal")
}
}
}
// TestOverlapsExtra__NODE_TYPE tests specific edge cases in trie overlap detection.
func TestOverlapsExtra__NODE_TYPE(t *testing.T) {
t.Parallel()
// 1. Test OverlapsSameChildren loop continuation & completion (returning false)
t.Run("same_children_no_overlap", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n2 := new(_NODE_TYPE[int])
// Insert disjoint child subnets at address 10 and 20
n1.Insert(mpp("10.10.1.0/24"), 42, 0)
n1.Insert(mpp("10.20.1.0/24"), 42, 0)
n2.Insert(mpp("10.10.2.0/24"), 42, 0)
n2.Insert(mpp("10.20.2.0/24"), 42, 0)
if n1.Overlaps(n2, 0) {
t.Error("expected no overlap between disjoint child subnets")
}
})
// 2. Test OverlapsSameChildren loop continuation & early exit (returning true)
t.Run("same_children_first_no_overlap_second_overlap", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n2 := new(_NODE_TYPE[int])
// Address 10 does not overlap, address 20 does overlap
n1.Insert(mpp("10.10.1.0/24"), 42, 0)
n1.Insert(mpp("10.20.1.0/24"), 42, 0)
n2.Insert(mpp("10.10.2.0/24"), 42, 0)
n2.Insert(mpp("10.20.1.0/24"), 42, 0) // overlap here
if !n1.Overlaps(n2, 0) {
t.Error("expected overlap since second child subnet overlaps")
}
})
// 3. Test OverlapsSameChildren with matching child at address 255 that does not overlap
t.Run("same_children_boundary_255_no_overlap", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n2 := new(_NODE_TYPE[int])
n1.Insert(mpp("10.255.1.0/24"), 42, 0)
n2.Insert(mpp("10.255.2.0/24"), 42, 0)
if n1.Overlaps(n2, 0) {
t.Error("expected no overlap on boundary 255")
}
})
// 4. Test OverlapsPrefixAtDepth with LeafNode and FringeNode
t.Run("prefix_overlaps_leaf_node_true", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n1.Insert(mpp("10.20.0.0/15"), 42, 0)
if !n1.OverlapsPrefixAtDepth(mpp("10.20.0.0/16"), 0) {
t.Error("expected overlap with leaf node prefix")
}
})
t.Run("prefix_overlaps_leaf_node_false", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n1.Insert(mpp("10.20.0.0/15"), 42, 0)
if n1.OverlapsPrefixAtDepth(mpp("10.30.0.0/16"), 0) {
t.Error("expected no overlap with disjoint prefix")
}
})
t.Run("prefix_overlaps_fringe_node", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n1.Insert(mpp("10.0.0.0/8"), 42, 0)
if !n1.OverlapsPrefixAtDepth(mpp("10.0.0.0/16"), 0) {
t.Error("expected overlap with fringe node")
}
})
// 5. Test OverlapsPrefixAtDepth with deeper path walk
t.Run("prefix_overlaps_idx_at_last_octet", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n1.Insert(mpp("10.20.30.0/24"), 42, 0)
if !n1.OverlapsPrefixAtDepth(mpp("10.20.0.0/16"), 0) {
t.Error("expected overlap at last octet")
}
})
t.Run("prefix_overlaps_contains_at_depth", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n1.Insert(mpp("10.128.30.0/24"), 42, 0)
n1.Insert(mpp("10.128.0.0/9"), 42, 0)
if !n1.OverlapsPrefixAtDepth(mpp("10.128.30.0/24"), 0) {
t.Error("expected overlap with contains check at depth 1")
}
})
}
// TestDumpStringExtra__NODE_TYPE tests type classification in DumpString and DumpRec.
func TestDumpStringExtra__NODE_TYPE(t *testing.T) {
t.Parallel()
// 1. Create a halfNode at the root
n1 := new(_NODE_TYPE[int])
// LeafNode under child slot 10:
n1.Insert(mpp("10.20.0.0/15"), 42, 0)
// Internal node under child slot 11 (requires branching to prevent path compression):
n1.Insert(mpp("11.30.40.0/24"), 42, 0)
n1.Insert(mpp("11.30.50.0/24"), 42, 0)
dump1 := n1.DumpString(nil, 0, true)
if !strings.Contains(dump1, "HALF") {
t.Errorf("expected HALF node type in dump, got:\n%s", dump1)
}
// 2. Create a pathNode at the root
n2 := new(_NODE_TYPE[int])
// Branching paths under child slot 10 to create an internal node at root child 10
n2.Insert(mpp("10.20.30.0/24"), 42, 0)
n2.Insert(mpp("10.20.40.0/24"), 42, 0)
dump2 := n2.DumpString(nil, 0, true)
if !strings.Contains(dump2, "PATH") {
t.Errorf("expected PATH node type in dump, got:\n%s", dump2)
}
// 3. Test DumpRec on nil/empty node
var nilNode *_NODE_TYPE[int]
var buf bytes.Buffer
nilNode.DumpRec(&buf, StridePath{}, 0, true)
if buf.Len() != 0 {
t.Error("expected empty buffer for nil DumpRec")
}
buf.Reset()
emptyNode := new(_NODE_TYPE[int])
emptyNode.DumpRec(&buf, StridePath{}, 0, true)
if buf.Len() != 0 {
t.Error("expected empty buffer for empty DumpRec")
}
}
// TestDeletePurgeExtra__NODE_TYPE tests the bottom-up trie compression on delete.
func TestDeletePurgeExtra__NODE_TYPE(t *testing.T) {
t.Parallel()
// 1. Trigger PurgeAndCompress: case childCount == 1 with LeafNode
t.Run("purge_leaf_node", func(t *testing.T) {
t.Parallel()
n := new(_NODE_TYPE[int])
n.Insert(mpp("10.20.10.0/24"), 42, 0)
n.Insert(mpp("10.20.20.0/24"), 42, 0)
n.Delete(mpp("10.20.20.0/24"))
if _, ok := n.Get(mpp("10.20.10.0/24")); !ok {
t.Error("expected remaining leaf node to be present")
}
})
// 2. Trigger PurgeAndCompress: case childCount == 1 with FringeNode
t.Run("purge_fringe_node", func(t *testing.T) {
t.Parallel()
n := new(_NODE_TYPE[int])
n.Insert(mpp("10.20.0.0/16"), 42, 0)
n.Insert(mpp("10.30.0.0/16"), 42, 0)
n.Delete(mpp("10.30.0.0/16"))
if _, ok := n.Get(mpp("10.20.0.0/16")); !ok {
t.Error("expected remaining fringe node to be present")
}
})
// 3. Trigger PurgeAndCompress: case pfxCount == 1
t.Run("purge_single_prefix", func(t *testing.T) {
t.Parallel()
n := new(_NODE_TYPE[int])
n.Insert(mpp("10.20.0.0/16"), 42, 0)
n.Insert(mpp("10.0.0.0/8"), 42, 0)
n.Delete(mpp("10.20.0.0/16"))
if _, ok := n.Get(mpp("10.0.0.0/8")); !ok {
t.Error("expected remaining prefix to be present")
}
})
}
// TestUnionRecExtra__NODE_TYPE tests specific edge cases in trie Union operations.
func TestUnionRecExtra__NODE_TYPE(t *testing.T) {
t.Parallel()
_, isLite := any(new(_NODE_TYPE[int])).(*LiteNode[int])
// 1. Cover handleMatrix case 2: thisIsNode && otherIsLeaf, where leaf already exists in thisNode
t.Run("node_leaf_exists", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n2 := new(_NODE_TYPE[int])
// Both n1 and n2 have child at octet 10, but:
// n1's child is an internal node (requires branching to prevent path compression)
n1.Insert(mpp("10.10.10.0/24"), 42, 0)
n1.Insert(mpp("10.10.20.0/24"), 42, 0)
// n2's child is a LeafNode with "10.10.20.0/24"
n2.Insert(mpp("10.10.20.0/24"), 43, 0)
n1.UnionRec(value.CloneFnFactory[int](), n2, 0)
val, ok := n1.Get(mpp("10.10.20.0/24"))
if !ok {
t.Fatal("expected prefix to exist")
}
if !isLite && val != 43 {
t.Errorf("expected value 43, got %v", val)
}
})
// 2. Cover handleMatrix case 2: thisIsNode && otherIsFringe, where fringe already exists in thisNode
t.Run("node_fringe_exists", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n2 := new(_NODE_TYPE[int])
// Both n1 and n2 have child at octet 10.
// n1's child is an internal node and contains the default route/fringe "10.0.0.0/8"
n1.Insert(mpp("10.10.10.0/24"), 42, 0)
n1.Insert(mpp("10.0.0.0/8"), 42, 0)
// n2's child is a FringeNode "10.0.0.0/8"
n2.Insert(mpp("10.0.0.0/8"), 43, 0)
n1.UnionRec(value.CloneFnFactory[int](), n2, 0)
val, ok := n1.Get(mpp("10.0.0.0/8"))
if !ok {
t.Fatal("expected prefix to exist")
}
if !isLite && val != 43 {
t.Errorf("expected value 43, got %v", val)
}
})
// 3. Cover handleMatrixPersist case 2 counterpart
t.Run("node_leaf_exists_persist", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n2 := new(_NODE_TYPE[int])
n1.Insert(mpp("10.10.10.0/24"), 42, 0)
n1.Insert(mpp("10.10.20.0/24"), 42, 0)
n2.Insert(mpp("10.10.20.0/24"), 43, 0)
n1.UnionRecPersist(value.CloneFnFactory[int](), n2, 0)
val, ok := n1.Get(mpp("10.10.20.0/24"))
if !ok {
t.Fatal("expected prefix to exist")
}
if !isLite && val != 43 {
t.Errorf("expected value 43, got %v", val)
}
})
t.Run("node_fringe_exists_persist", func(t *testing.T) {
t.Parallel()
n1 := new(_NODE_TYPE[int])
n2 := new(_NODE_TYPE[int])
n1.Insert(mpp("10.10.10.0/24"), 42, 0)
n1.Insert(mpp("10.0.0.0/8"), 42, 0)
n2.Insert(mpp("10.0.0.0/8"), 43, 0)
n1.UnionRecPersist(value.CloneFnFactory[int](), n2, 0)
val, ok := n1.Get(mpp("10.0.0.0/8"))
if !ok {
t.Fatal("expected prefix to exist")
}
if !isLite && val != 43 {
t.Errorf("expected value 43, got %v", val)
}
})
}
// TestSubnetsEarlyExit__NODE_TYPE tests early termination in Subnets traversal.
func TestSubnetsEarlyExit__NODE_TYPE(t *testing.T) {
t.Parallel()
n := new(_NODE_TYPE[int])
n.Insert(mpp("10.0.0.0/8"), 1, 0)
n.Insert(mpp("10.10.10.0/24"), 2, 0)
n.Insert(mpp("10.10.20.0/24"), 3, 0)
n.Insert(mpp("10.20.0.0/15"), 4, 0)
// Traversal 1: yield returns false immediately
yieldCount := 0
n.Subnets(mpp("10.0.0.0/8"), func(pfx netip.Prefix, val int) bool {
yieldCount++
return false // stop immediately
})
if yieldCount != 1 {
t.Errorf("expected yieldCount to be 1, got %d", yieldCount)
}
// Traversal 2: yield returns false after 2 items
yieldCount = 0
n.Subnets(mpp("10.0.0.0/8"), func(pfx netip.Prefix, val int) bool {
yieldCount++
if yieldCount == 2 {
return false
}
return true
})
if yieldCount != 2 {
t.Errorf("expected yieldCount to be 2, got %d", yieldCount)
}
}
// TestSupernetsExtra__NODE_TYPE tests specific edge cases and early exits in Supernets traversal.
func TestSupernetsExtra__NODE_TYPE(t *testing.T) {
t.Parallel()
n := new(_NODE_TYPE[int])
// LeafNode at depth 1:
n.Insert(mpp("10.20.0.0/15"), 1, 0)
// FringeNode at depth 1:
n.Insert(mpp("10.30.0.0/16"), 2, 0)
// Normal prefixes:
n.Insert(mpp("10.0.0.0/8"), 3, 0)
n.Insert(mpp("10.40.50.0/24"), 4, 0)
// 1. LeafNode longer prefix check
yielded := []netip.Prefix{}
n.Supernets(mpp("10.20.0.0/15"), func(pfx netip.Prefix, val int) bool {
yielded = append(yielded, pfx)
return true
})
if len(yielded) != 2 {
t.Errorf("expected 2 supernets, got %v", yielded)
}
// 2. FringeNode longer prefix check
yielded = []netip.Prefix{}
n.Supernets(mpp("10.30.0.0/16"), func(pfx netip.Prefix, val int) bool {
yielded = append(yielded, pfx)
return true
})
if len(yielded) != 2 {
t.Errorf("expected 2 supernets, got %v", yielded)
}
// 3. Early exits on yield
// LeafNode yield false
yieldCount := 0
n.Supernets(mpp("10.20.0.0/15"), func(pfx netip.Prefix, val int) bool {
yieldCount++
if pfx == mpp("10.20.0.0/15") {
return false
}
return true
})
if yieldCount != 1 {
t.Errorf("expected yieldCount 1 on leaf early exit, got %d", yieldCount)
}
// FringeNode yield false
yieldCount = 0
n.Supernets(mpp("10.30.0.0/16"), func(pfx netip.Prefix, val int) bool {
yieldCount++
if pfx == mpp("10.30.0.0/16") {
return false
}
return true
})
if yieldCount != 1 {
t.Errorf("expected yieldCount 1 on fringe early exit, got %d", yieldCount)
}
// Backtracking yield false
yieldCount = 0
n.Supernets(mpp("10.40.50.0/24"), func(pfx netip.Prefix, val int) bool {
yieldCount++
return false
})
if yieldCount != 1 {
t.Errorf("expected yieldCount 1 on backtracking early exit, got %d", yieldCount)
}
// 4. Trigger depth > strideCount
yielded = []netip.Prefix{}
n.Supernets(mpp("10.40.0.0/16"), func(pfx netip.Prefix, val int) bool {
yielded = append(yielded, pfx)
return true
})
if len(yielded) != 1 || yielded[0] != mpp("10.0.0.0/8") {
t.Errorf("expected only 10.0.0.0/8, got %v", yielded)
}
}