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

2300 lines
58 KiB
Go

//go:build generate
// Copyright (c) 2026 Karl Gaissmaier
// SPDX-License-Identifier: MIT
//go:generate ./scripts/generate-table-tests.sh
package bart
// ### GENERATE DELETE START ###
// stub code for generator types and methods
// useful for gopls during development, deleted during go generate
import (
"encoding/json"
"io"
"iter"
"math/rand/v2"
"net/netip"
"slices"
"strings"
"testing"
"github.com/gaissmai/bart/internal/nodes"
"github.com/gaissmai/bart/internal/tests/golden"
"github.com/gaissmai/bart/internal/tests/random"
)
type (
_NODE_TYPE[V any] struct{}
_TABLE_TYPE[V any] struct {
root4 _NODE_TYPE[V]
root6 _NODE_TYPE[V]
}
)
func (*_NODE_TYPE[V]) StatsRec() (_ nodes.StatsT) { return }
func (*_TABLE_TYPE[V]) rootNodeByVersion(bool) (_ *_NODE_TYPE[V]) { return }
func (*_TABLE_TYPE[V]) sizeUpdate(bool, int) { return }
func (*_TABLE_TYPE[V]) dump(io.Writer) { return }
func (*_TABLE_TYPE[V]) dumpString() (_ string) { return }
func (*_TABLE_TYPE[V]) fprint(io.Writer, bool) (_ error) { return }
func (*_TABLE_TYPE[V]) Fprint(io.Writer) (_ error) { return }
func (*_TABLE_TYPE[V]) Size() (_ int) { return }
func (*_TABLE_TYPE[V]) Size4() (_ int) { return }
func (*_TABLE_TYPE[V]) Size6() (_ int) { return }
func (*_TABLE_TYPE[V]) Insert(netip.Prefix, V) { return }
func (*_TABLE_TYPE[V]) Get(netip.Prefix) (_ V, _ bool) { return }
func (*_TABLE_TYPE[V]) Delete(netip.Prefix) { return }
func (*_TABLE_TYPE[V]) Modify(netip.Prefix, func(V, bool) (V, bool)) { return }
func (*_TABLE_TYPE[V]) Clone() (_ *_TABLE_TYPE[V]) { return }
func (*_TABLE_TYPE[V]) Union(*_TABLE_TYPE[V]) { return }
func (*_TABLE_TYPE[V]) Equal(*_TABLE_TYPE[V]) (_ bool) { return }
func (*_TABLE_TYPE[V]) OverlapsPrefix(netip.Prefix) (_ bool) { return }
func (*_TABLE_TYPE[V]) Overlaps(*_TABLE_TYPE[V]) (_ bool) { return }
func (*_TABLE_TYPE[V]) Overlaps4(*_TABLE_TYPE[V]) (_ bool) { return }
func (*_TABLE_TYPE[V]) Overlaps6(*_TABLE_TYPE[V]) (_ bool) { return }
func (*_TABLE_TYPE[V]) Contains(netip.Addr) (_ bool) { return }
func (*_TABLE_TYPE[V]) Lookup(netip.Addr) (_ V, _ bool) { return }
func (*_TABLE_TYPE[V]) LookupPrefix(netip.Prefix) (_ V, _ bool) { return }
func (*_TABLE_TYPE[V]) LookupPrefixLPM(netip.Prefix) (_ netip.Prefix, _ V, _ bool) { return }
func (*_TABLE_TYPE[V]) InsertPersist(netip.Prefix, V) (_ *_TABLE_TYPE[V]) { return }
func (*_TABLE_TYPE[V]) DeletePersist(netip.Prefix) (_ *_TABLE_TYPE[V]) { return }
func (*_TABLE_TYPE[V]) UnionPersist(*_TABLE_TYPE[V]) (_ *_TABLE_TYPE[V]) { return }
func (*_TABLE_TYPE[V]) ModifyPersist(netip.Prefix, func(V, bool) (V, bool)) (_ *_TABLE_TYPE[V]) {
return
}
func (*_TABLE_TYPE[V]) MarshalText() (_ []byte, _ error) { return }
func (*_TABLE_TYPE[V]) MarshalJSON() (_ []byte, _ error) { return }
func (*_TABLE_TYPE[V]) DumpList4() (_ []DumpListNode[V]) { return }
func (*_TABLE_TYPE[V]) DumpList6() (_ []DumpListNode[V]) { return }
func (*_TABLE_TYPE[V]) All() (_ iter.Seq2[netip.Prefix, V]) { return }
func (*_TABLE_TYPE[V]) All4() (_ iter.Seq2[netip.Prefix, V]) { return }
func (*_TABLE_TYPE[V]) All6() (_ iter.Seq2[netip.Prefix, V]) { return }
func (*_TABLE_TYPE[V]) AllSorted() (_ iter.Seq2[netip.Prefix, V]) { return }
func (*_TABLE_TYPE[V]) AllSorted4() (_ iter.Seq2[netip.Prefix, V]) { return }
func (*_TABLE_TYPE[V]) AllSorted6() (_ iter.Seq2[netip.Prefix, V]) { return }
func (*_TABLE_TYPE[V]) Subnets(netip.Prefix) (_ iter.Seq2[netip.Prefix, V]) { return }
func (*_TABLE_TYPE[V]) Supernets(netip.Prefix) (_ iter.Seq2[netip.Prefix, V]) { return }
// ### GENERATE DELETE END ###
// ############ tests ################################
// flatSorted, just a helper to compare with golden table.
func (t *_TABLE_TYPE[V]) flatSorted() golden.Table[V] {
var flat golden.Table[V]
for p, v := range t.AllSorted() {
flat = append(flat, golden.TableItem[V]{Pfx: p, Val: v})
}
return flat
}
func TestTableNilReceiver__TABLE_TYPE(t *testing.T) {
t.Parallel()
var tbl1 *_TABLE_TYPE[any] = nil
ip4 := mpa("127.0.0.1")
ip6 := mpa("::1")
pfx4 := mpp("127.0.0.0/8")
pfx6 := mpp("::1/128")
tbl2 := new(_TABLE_TYPE[any])
tbl2.Insert(pfx4, true)
tbl2.Insert(pfx6, true)
t.Run("mustPanicNilReceiver", func(t *testing.T) {
t.Parallel()
mustPanic(t, "Size", func() { tbl1.Size() })
mustPanic(t, "Size4", func() { tbl1.Size4() })
mustPanic(t, "Size6", func() { tbl1.Size6() })
mustPanic(t, "Get", func() { tbl1.Get(pfx4) })
mustPanic(t, "Insert", func() { tbl1.Insert(pfx4, nil) })
mustPanic(t, "InsertPersist", func() { tbl1.InsertPersist(pfx4, nil) })
mustPanic(t, "Delete", func() { tbl1.Delete(pfx4) })
mustPanic(t, "DeletePersist", func() { tbl1.DeletePersist(pfx4) })
mustPanic(t, "Modify", func() { tbl1.Modify(pfx4, nil) })
mustPanic(t, "ModifyPersist", func() { tbl1.ModifyPersist(pfx4, nil) })
mustPanic(t, "Contains", func() { tbl1.Contains(ip4) })
mustPanic(t, "Lookup", func() { tbl1.Lookup(ip6) })
mustPanic(t, "LookupPrefix", func() { tbl1.LookupPrefix(pfx4) })
mustPanic(t, "LookupPrefixLPM", func() { tbl1.LookupPrefixLPM(pfx4) })
mustPanic(t, "Clone", func() { tbl1.Clone() })
mustPanic(t, "Union", func() { tbl1.Union(tbl2) })
mustPanic(t, "UnionPersist", func() { tbl1.UnionPersist(tbl2) })
mustPanic(t, "Overlaps", func() { tbl1.Overlaps(tbl2) })
mustPanic(t, "Overlaps4", func() { tbl1.Overlaps4(tbl2) })
mustPanic(t, "Overlaps6", func() { tbl1.Overlaps6(tbl2) })
mustPanic(t, "OverlapsPrefix", func() { tbl1.OverlapsPrefix(pfx4) })
mustPanic(t, "OverlapsPrefix", func() { tbl1.OverlapsPrefix(pfx6) })
mustPanic(t, "Equal", func() { tbl1.Equal(tbl2) })
mustPanic(t, "DumpList4", func() { tbl1.DumpList4() })
mustPanic(t, "DumpList6", func() { tbl1.DumpList6() })
mustPanic(t, "Fprint", func() { tbl1.Fprint(nil) })
mustPanic(t, "MarshalJSON", func() { _, _ = tbl1.MarshalJSON() })
mustPanic(t, "MarshalText", func() { _, _ = tbl1.MarshalText() })
mustPanicRangeOverFunc[any](t, "All", tbl1.All)
mustPanicRangeOverFunc[any](t, "All4", tbl1.All4)
mustPanicRangeOverFunc[any](t, "All6", tbl1.All6)
mustPanicRangeOverFunc[any](t, "AllSorted", tbl1.AllSorted)
mustPanicRangeOverFunc[any](t, "AllSorted4", tbl1.AllSorted4)
mustPanicRangeOverFunc[any](t, "AllSorted6", tbl1.AllSorted6)
mustPanicRangeOverFunc[any](t, "Subnets", tbl1.Subnets)
mustPanicRangeOverFunc[any](t, "Supernets", tbl1.Supernets)
})
}
func TestTableNilArguments__TABLE_TYPE(t *testing.T) {
t.Parallel()
t.Run("mustPanicNilArgs", func(t *testing.T) {
t.Parallel()
tbl := new(_TABLE_TYPE[bool])
tbl.Insert(mpp("127.0.0.1/32"), true)
tbl.Insert(mpp("::1/128"), true)
mustPanic(t, "Union(nil)", func() { tbl.Union(nil) })
mustPanic(t, "UnionPersist(nil)", func() { tbl.UnionPersist(nil) })
mustPanic(t, "Overlaps(nil)", func() { tbl.Overlaps(nil) })
mustPanic(t, "Overlaps4(nil)", func() { tbl.Overlaps4(nil) })
mustPanic(t, "Overlaps6(nil)", func() { tbl.Overlaps6(nil) })
mustPanic(t, "Equal(nil)", func() { tbl.Equal(nil) })
})
}
func TestTableInvalid__TABLE_TYPE(t *testing.T) {
t.Parallel()
tbl1 := new(_TABLE_TYPE[any])
tbl2 := new(_TABLE_TYPE[any])
var zeroIP netip.Addr
var zeroPfx netip.Prefix
noPanic(t, "All", func() { tbl1.All() })
noPanic(t, "All4", func() { tbl1.All4() })
noPanic(t, "All6", func() { tbl1.All6() })
noPanic(t, "AllSorted", func() { tbl1.AllSorted() })
noPanic(t, "AllSorted4", func() { tbl1.AllSorted4() })
noPanic(t, "AllSorted6", func() { tbl1.AllSorted6() })
noPanic(t, "Clone", func() { tbl1.Clone() })
noPanic(t, "Contains", func() { tbl1.Contains(zeroIP) })
noPanic(t, "Delete", func() { tbl1.Delete(zeroPfx) })
noPanic(t, "DeletePersist", func() { tbl1.DeletePersist(zeroPfx) })
noPanic(t, "DumpList4", func() { tbl1.DumpList4() })
noPanic(t, "DumpList6", func() { tbl1.DumpList6() })
noPanic(t, "Equal", func() { tbl1.Equal(tbl2) })
noPanic(t, "Fprint", func() { tbl1.Fprint(nil) })
noPanic(t, "Get", func() { tbl1.Get(zeroPfx) })
noPanic(t, "Insert", func() { tbl1.Insert(zeroPfx, nil) })
noPanic(t, "InsertPersist", func() { tbl1.InsertPersist(zeroPfx, nil) })
noPanic(t, "Lookup", func() { tbl1.Lookup(zeroIP) })
noPanic(t, "LookupPrefix", func() { tbl1.LookupPrefix(zeroPfx) })
noPanic(t, "LookupPrefixLPM", func() { tbl1.LookupPrefixLPM(zeroPfx) })
noPanic(t, "MarshalJSON", func() { _, _ = tbl1.MarshalJSON() })
noPanic(t, "MarshalText", func() { _, _ = tbl1.MarshalText() })
noPanic(t, "Modify", func() { tbl1.Modify(zeroPfx, nil) })
noPanic(t, "ModifyPersist", func() { tbl1.ModifyPersist(zeroPfx, nil) })
noPanic(t, "Overlaps", func() { tbl1.Overlaps(tbl2) })
noPanic(t, "Overlaps4", func() { tbl1.Overlaps4(tbl2) })
noPanic(t, "Overlaps6", func() { tbl1.Overlaps6(tbl2) })
noPanic(t, "OverlapsPrefix", func() { tbl1.OverlapsPrefix(zeroPfx) })
noPanic(t, "Size", func() { tbl1.Size() })
noPanic(t, "Size4", func() { tbl1.Size4() })
noPanic(t, "Size6", func() { tbl1.Size6() })
noPanic(t, "Subnets", func() { tbl1.Subnets(zeroPfx) })
noPanic(t, "Supernets", func() { tbl1.Supernets(zeroPfx) })
noPanic(t, "Union", func() { tbl1.Union(tbl2) })
noPanic(t, "UnionPersist", func() { tbl1.UnionPersist(tbl2) })
}
func TestTableContainsCompare__TABLE_TYPE(t *testing.T) {
// Create large route tables repeatedly, and compare Table's
// behavior to a naive and slow but correct implementation.
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
gold := new(golden.Table[int])
tbl := new(_TABLE_TYPE[int])
for i, p := range pfxs {
gold.Insert(p, i)
tbl.Insert(p, i)
}
for range n {
ip := random.IP(prng)
_, goldOK := gold.Lookup(ip)
tblOK := tbl.Contains(ip)
if goldOK != tblOK {
t.Fatalf("Contains(%q) = %v, want %v", ip, tblOK, goldOK)
}
}
}
func TestTableLookupCompare__TABLE_TYPE(t *testing.T) {
// Create large route tables repeatedly, and compare Table's
// behavior to a naive and slow but correct implementation.
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
gold := new(golden.Table[int])
tbl := new(_TABLE_TYPE[int])
for i, pfx := range pfxs {
gold.Insert(pfx, i)
tbl.Insert(pfx, i)
}
for range n {
ip := random.IP(prng)
goldVal, goldOK := gold.Lookup(ip)
tblVal, tblOK := tbl.Lookup(ip)
if goldOK != tblOK {
t.Fatalf("Lookup(%q) = (_, %v), want %v", ip, tblOK, goldOK)
}
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[int]); !isLite {
if goldVal != tblVal {
t.Fatalf("Lookup(%q) = (%v, %v), want (%v, %v)", ip, tblVal, tblOK, goldVal, goldOK)
}
}
}
}
func TestTableLookupPrefixUnmasked__TABLE_TYPE(t *testing.T) {
// test that the pfx must not be masked on input for LookupPrefix
t.Parallel()
tbl := new(_TABLE_TYPE[any])
tbl.Insert(mpp("10.20.30.0/24"), nil)
tbl.Insert(mpp("2001:db8::/32"), nil)
// not normalized pfxs
tests := []struct {
probe netip.Prefix
wantLPM netip.Prefix
wantOk bool
}{
{
probe: netip.MustParsePrefix("10.20.30.40/0"),
wantLPM: netip.Prefix{},
wantOk: false,
},
{
probe: netip.MustParsePrefix("10.20.30.40/23"),
wantLPM: netip.Prefix{},
wantOk: false,
},
{
probe: netip.MustParsePrefix("10.20.30.40/24"),
wantLPM: mpp("10.20.30.0/24"),
wantOk: true,
},
{
probe: netip.MustParsePrefix("10.20.30.40/25"),
wantLPM: mpp("10.20.30.0/24"),
wantOk: true,
},
{
probe: netip.MustParsePrefix("10.20.30.40/32"),
wantLPM: mpp("10.20.30.0/24"),
wantOk: true,
},
// IPv6 counterparts
{
probe: netip.MustParsePrefix("2001:db8::1/0"),
wantLPM: netip.Prefix{},
wantOk: false,
},
{
probe: netip.MustParsePrefix("2001:db8::1/31"),
wantLPM: netip.Prefix{},
wantOk: false,
},
{
probe: netip.MustParsePrefix("2001:db8::1/32"),
wantLPM: mpp("2001:db8::/32"),
wantOk: true,
},
{
probe: netip.MustParsePrefix("2001:db8::1/64"),
wantLPM: mpp("2001:db8::/32"),
wantOk: true,
},
}
for _, tc := range tests {
_, got := tbl.LookupPrefix(tc.probe)
if got != tc.wantOk {
t.Errorf("LookupPrefix non canonical prefix (%s), got: %v, want: %v", tc.probe, got, tc.wantOk)
}
lpm, _, got := tbl.LookupPrefixLPM(tc.probe)
if got != tc.wantOk {
t.Errorf("LookupPrefixLPM non canonical prefix (%s), got: %v, want: %v", tc.probe, got, tc.wantOk)
}
if lpm != tc.wantLPM {
t.Errorf("LookupPrefixLPM non canonical prefix (%s), got: %v, want: %v", tc.probe, lpm, tc.wantLPM)
}
}
}
func TestTableLookupPrefixCompare__TABLE_TYPE(t *testing.T) {
// Create large route tables repeatedly, and compare Table's
// behavior to a naive and slow but correct implementation.
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
gold := new(golden.Table[int])
tbl := new(_TABLE_TYPE[int])
for i, pfx := range pfxs {
gold.Insert(pfx, i)
tbl.Insert(pfx, i)
}
for range n {
pfx := random.Prefix(prng)
goldVal, goldOK := gold.LookupPrefix(pfx)
tblVal, tblOK := tbl.LookupPrefix(pfx)
if goldOK != tblOK {
t.Fatalf("LookupPrefix(%q) = (_, %v), want (_, %v)", pfx, tblOK, goldOK)
}
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[int]); !isLite {
if goldVal != tblVal {
t.Fatalf("LookupPrefix(%q) = (%v, %v), want (%v, %v)", pfx, tblVal, tblOK, goldVal, goldOK)
}
}
}
}
func TestTableLookupPrefixLPMCompare__TABLE_TYPE(t *testing.T) {
// Create large route tables repeatedly, and compare Table's
// behavior to a naive and slow but correct implementation.
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
gold := new(golden.Table[int])
tbl := new(_TABLE_TYPE[int])
for i, pfx := range pfxs {
gold.Insert(pfx, i)
tbl.Insert(pfx, i)
}
for range n {
pfx := random.Prefix(prng)
goldLPM, goldVal, goldOK := gold.LookupPrefixLPM(pfx)
tblLPM, tblVal, tblOK := tbl.LookupPrefixLPM(pfx)
if goldOK != tblOK {
t.Fatalf("LookupPrefixLPM(%q) = (_, _, %v), want (_, _, %v)", pfx, tblOK, goldOK)
}
if goldLPM != tblLPM {
t.Fatalf("LookupPrefixLPM(%q) = ( %v, _, _), want ( %v, _, _)", pfx, tblLPM, goldLPM)
}
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[int]); !isLite {
if goldVal != tblVal {
t.Fatalf("LookupPrefixLPM(%q) = (_, %v, _), want (_, %v, _)", pfx, tblVal, goldVal)
}
}
}
}
func TestTableInsertShuffled__TABLE_TYPE(t *testing.T) {
// The order in which you insert prefixes into a route table
// should not matter, as long as you're inserting the same set of
// routes.
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
for range 10 {
pfxs2 := slices.Clone(pfxs)
prng.Shuffle(len(pfxs2), func(i, j int) { pfxs2[i], pfxs2[j] = pfxs2[j], pfxs2[i] })
tbl1 := new(_TABLE_TYPE[string])
tbl2 := new(_TABLE_TYPE[string])
for _, pfx := range pfxs {
tbl1.Insert(pfx, pfx.String())
tbl1.Insert(pfx, pfx.String()) // idempotent
}
for _, pfx := range pfxs2 {
tbl2.Insert(pfx, pfx.String()) // idempotent
}
if tbl1.dumpString() != tbl2.dumpString() {
t.Fatal("tbl1 and tbl2 have different dumpString representation")
}
if !tbl1.Equal(tbl2) {
t.Fatal("expected Equal")
}
}
}
func TestTableInsertPersistShuffled__TABLE_TYPE(t *testing.T) {
// The order in which you insert prefixes into a route table
// should not matter, as long as you're inserting the same set of
// routes.
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
for range 10 {
pfxs2 := slices.Clone(pfxs)
prng.Shuffle(len(pfxs2), func(i, j int) { pfxs2[i], pfxs2[j] = pfxs2[j], pfxs2[i] })
tbl1 := new(_TABLE_TYPE[string])
tbl2 := new(_TABLE_TYPE[string])
// bart1 is mutable
for _, pfx := range pfxs {
tbl1.Insert(pfx, pfx.String())
}
// bart2 is persistent
for _, pfx := range pfxs2 {
tbl2 = tbl2.InsertPersist(pfx, pfx.String())
}
if tbl1.dumpString() != tbl2.dumpString() {
t.Fatal("mutable and immutable table have different dumpString representation")
}
if !tbl1.Equal(tbl2) {
t.Fatal("expected Equal")
}
}
}
func TestTableDeleteCompare__TABLE_TYPE(t *testing.T) {
// Create large route tables repeatedly, delete half of their
// prefixes, and compare Table's behavior to a naive and slow but
// correct implementation.
t.Parallel()
var (
n = workLoadN()
prng = rand.New(rand.NewPCG(42, 42))
numPrefixes = n // total prefixes to insert (test deletes 50% of them)
numPerFamily = numPrefixes / 2
deleteCut = numPerFamily / 2
probes = 3
)
for range probes {
all4 := random.RealWorldPrefixes4(prng, numPerFamily)
all6 := random.RealWorldPrefixes6(prng, numPerFamily)
pfxs := slices.Concat(all4, all6)
toDelete := slices.Concat(all4[deleteCut:], all6[deleteCut:])
gold := new(golden.Table[string])
tbl := new(_TABLE_TYPE[string])
for _, pfx := range pfxs {
gold.Insert(pfx, pfx.String())
tbl.Insert(pfx, pfx.String())
}
for _, pfx := range toDelete {
gold.Delete(pfx)
tbl.Delete(pfx)
}
gold.Sort()
tblFlat := tbl.flatSorted()
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[string]); isLite {
if !slices.Equal(gold.AllSorted(), tblFlat.AllSorted()) {
t.Fatal("expected Equal")
}
} else {
if !slices.Equal(*gold, tblFlat) {
t.Fatal("expected Equal")
}
}
}
}
func TestTableDeleteShuffled__TABLE_TYPE(t *testing.T) {
// The order in which you delete prefixes from a route table
// should not matter, as long as you're deleting the same set of
// routes.
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
var (
numPrefixes = n // prefixes to insert (test deletes 50% of them)
numPerFamily = numPrefixes / 2
deleteCut = numPerFamily / 2
)
for range 10 {
all4 := random.RealWorldPrefixes4(prng, numPerFamily)
all6 := random.RealWorldPrefixes6(prng, numPerFamily)
pfxs := slices.Concat(all4, all6)
toDelete := slices.Concat(all4[deleteCut:], all6[deleteCut:])
tbl := new(_TABLE_TYPE[string])
// insert
for _, pfx := range pfxs {
tbl.Insert(pfx, pfx.String())
}
// delete
for _, pfx := range toDelete {
tbl.Delete(pfx)
}
pfxs2 := slices.Clone(pfxs)
toDelete2 := slices.Clone(toDelete)
prng.Shuffle(len(toDelete2), func(i, j int) { toDelete2[i], toDelete2[j] = toDelete2[j], toDelete2[i] })
tbl2 := new(_TABLE_TYPE[string])
// insert
for _, pfx := range pfxs2 {
tbl2.Insert(pfx, pfx.String())
}
// delete
for _, pfx := range toDelete2 {
tbl2.Delete(pfx)
}
if !tbl.Equal(tbl2) {
t.Fatal("expect equal")
}
}
}
func TestTableDeleteIsReverseOfInsert__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
// Insert n prefixes, then delete those same prefixes in reverse
// order. Each deletion should exactly undo the internal structure
// changes that each insert did.
tbl := new(_TABLE_TYPE[string])
want := tbl.dumpString()
prefixes := random.RealWorldPrefixes(prng, n)
defer func() {
if t.Failed() {
t.Logf("the prefixes that fail the test: %v\n", prefixes)
}
}()
for _, p := range prefixes {
tbl.Insert(p, p.String())
}
for i := len(prefixes) - 1; i >= 0; i-- {
tbl.Delete(prefixes[i])
}
if got := tbl.dumpString(); got != want {
t.Fatalf("after delete, mismatch:\n\n got: %s\n\nwant: %s", got, want)
}
}
func TestTableDeleteButOne__TABLE_TYPE(t *testing.T) {
t.Parallel()
prng := rand.New(rand.NewPCG(42, 42))
// Insert n prefixes, then delete all but one
n := workLoadN()
for range 10 {
tbl := new(_TABLE_TYPE[any])
prefixes := random.RealWorldPrefixes(prng, n)
for _, p := range prefixes {
tbl.Insert(p, nil)
}
// shuffle the prefixes
prng.Shuffle(n, func(i, j int) {
prefixes[i], prefixes[j] = prefixes[j], prefixes[i]
})
// skip the first
for i := 1; i < len(prefixes); i++ {
tbl.Delete(prefixes[i])
}
if size := tbl.Size(); size != 1 {
t.Fatalf("Size(), got %d, want 1", size)
}
stats4 := tbl.root4.StatsRec()
stats6 := tbl.root6.StatsRec()
if nodes := stats4.SubNodes + stats6.SubNodes; nodes != 1 {
t.Fatalf("delete but one, want nodes: 1, got: %d\n%s", nodes, tbl.dumpString())
}
sum := stats4.Prefixes + stats4.Leaves + stats4.Fringes +
stats6.Prefixes + stats6.Leaves + stats6.Fringes
if sum != 1 {
t.Fatalf("delete but one, only one item must be left, but: %d\n%s", sum, tbl.dumpString())
}
}
}
func TestTableGet__TABLE_TYPE(t *testing.T) {
t.Parallel()
prng := rand.New(rand.NewPCG(42, 42))
pfx := random.Prefix(prng)
tests := []struct {
name string
pfx netip.Prefix
val int
}{
{
name: "default route v4",
pfx: mpp("0.0.0.0/0"),
val: 0,
},
{
name: "default route v6",
pfx: mpp("::/0"),
val: 0,
},
{
name: "set v4",
pfx: mpp("1.2.3.4/32"),
val: 1234,
},
{
name: "set v6",
pfx: mpp("2001:db8::/32"),
val: 2001,
},
}
tbl := new(_TABLE_TYPE[int])
if _, ok := tbl.Get(pfx); ok {
t.Errorf("empty table: Get(%v), ok=%v, expected: %v", pfx, ok, false)
}
for _, tt := range tests {
tbl.Insert(tt.pfx, tt.val)
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
got, ok := tbl.Get(tt.pfx)
if !ok {
t.Errorf("%s: ok=%v, expected: %v", tt.name, ok, true)
}
if got != tt.val {
t.Errorf("%s: val=%v, expected: %v", tt.name, got, tt.val)
}
})
}
}
func TestTableGetCompare__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
gold := new(golden.Table[string])
tbl := new(_TABLE_TYPE[string])
for _, pfx := range pfxs {
gold.Insert(pfx, pfx.String())
tbl.Insert(pfx, pfx.String())
}
for _, pfx := range pfxs {
goldVal, goldOK := gold.Get(pfx)
tblVal, tblOK := tbl.Get(pfx)
if goldOK != tblOK {
t.Fatalf("Get(%q) = (_, %v), want (_, %v)", pfx, tblOK, goldOK)
}
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[string]); !isLite {
if goldVal != tblVal {
t.Fatalf("Get(%q) = (%v, %v), want (%v, %v)", pfx, tblVal, tblOK, goldVal, goldOK)
}
}
}
}
func TestTableModifySemantics__TABLE_TYPE(t *testing.T) {
t.Parallel()
type args struct {
pfx netip.Prefix
cb func(val int, found bool) (_ int, del bool)
}
tests := []struct {
name string
prepare map[netip.Prefix]int // entries to pre-populate the table
args args
finalData map[netip.Prefix]int // expected table contents after the operation
}{
{
name: "Delete existing entry",
prepare: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42, mpp("2001:db8::/32"): 4242},
args: args{
pfx: mpp("10.0.0.0/8"),
cb: func(val int, found bool) (_ int, del bool) { return 0, true },
},
finalData: map[netip.Prefix]int{mpp("2001:db8::/32"): 4242},
},
{
name: "Insert new entry",
prepare: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42},
args: args{
pfx: mpp("2001:db8::/32"),
cb: func(val int, found bool) (_ int, del bool) { return 4242, false },
},
finalData: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42, mpp("2001:db8::/32"): 4242},
},
{
name: "Update existing entry",
prepare: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42, mpp("2001:db8::/32"): 4242},
args: args{
pfx: mpp("10.0.0.0/8"),
cb: func(val int, found bool) (_ int, del bool) { return -1, false },
},
finalData: map[netip.Prefix]int{mpp("10.0.0.0/8"): -1, mpp("2001:db8::/32"): 4242},
},
{
name: "No-op on missing entry",
prepare: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42},
args: args{
pfx: mpp("2001:db8::/32"),
cb: func(val int, found bool) (_ int, del bool) { return 0, true },
},
finalData: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42},
},
}
for _, tt := range tests {
tbl := new(_TABLE_TYPE[int])
// Insert initial entries using Modify
for pfx, v := range tt.prepare {
tbl.Modify(pfx, func(_ int, _ bool) (_ int, del bool) { return v, false })
}
tbl.Modify(tt.args.pfx, tt.args.cb)
// Check the final state of the table using Get, compares expected and actual table
got := make(map[netip.Prefix]int, len(tt.finalData))
for pfx, val := range tbl.All() {
got[pfx] = val
}
if len(got) != len(tt.finalData) {
t.Fatalf("[%s] final table size mismatch: got %d, want %d", tt.name, len(got), len(tt.finalData))
}
for pfx, wantVal := range tt.finalData {
gotVal, ok := got[pfx]
if !ok || gotVal != wantVal {
t.Fatalf("[%s] final table: key %v = %v (present=%v), want %v", tt.name, pfx, gotVal, ok, wantVal)
}
}
}
}
func TestTableModifyPersistSemantics__TABLE_TYPE(t *testing.T) {
t.Parallel()
if _, isLite := any(new(_TABLE_TYPE[any])).(*liteTable[any]); isLite {
t.Skip("liteNode has no real payload")
}
type args struct {
pfx netip.Prefix
cb func(val int, found bool) (_ int, del bool)
}
tests := []struct {
name string
prepare map[netip.Prefix]int // entries to pre-populate the table
args args
finalData map[netip.Prefix]int // expected table contents after the operation
}{
{
name: "Delete existing entry",
prepare: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42, mpp("2001:db8::/32"): 4242},
args: args{
pfx: mpp("10.0.0.0/8"),
cb: func(val int, found bool) (_ int, del bool) { return 0, true },
},
finalData: map[netip.Prefix]int{mpp("2001:db8::/32"): 4242},
},
{
name: "Insert new entry",
prepare: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42},
args: args{
pfx: mpp("2001:db8::/32"),
cb: func(val int, found bool) (_ int, del bool) { return 4242, false },
},
finalData: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42, mpp("2001:db8::/32"): 4242},
},
{
name: "Update existing entry",
prepare: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42, mpp("2001:db8::/32"): 4242},
args: args{
pfx: mpp("10.0.0.0/8"),
cb: func(val int, found bool) (_ int, del bool) { return -1, false },
},
finalData: map[netip.Prefix]int{mpp("10.0.0.0/8"): -1, mpp("2001:db8::/32"): 4242},
},
{
name: "No-op on missing entry",
prepare: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42},
args: args{
pfx: mpp("2001:db8::/32"),
cb: func(val int, found bool) (_ int, del bool) { return 0, true },
},
finalData: map[netip.Prefix]int{mpp("10.0.0.0/8"): 42},
},
}
for _, tt := range tests {
tbl := new(_TABLE_TYPE[int])
// Insert initial entries using Modify
for pfx, v := range tt.prepare {
tbl.Modify(pfx, func(_ int, _ bool) (_ int, del bool) { return v, false })
}
prt := tbl.ModifyPersist(tt.args.pfx, tt.args.cb)
// Check the final state of the table using Get, compares expected and actual table
for pfx, wantVal := range tt.finalData {
gotVal, ok := prt.Get(pfx)
if !ok || gotVal != wantVal {
t.Errorf("[%s] final table: key %v = %v (ok=%v), want %v (ok=true)", tt.name, pfx, gotVal, ok, wantVal)
}
}
// Ensure there are no unexpected entries
for pfx := range tt.prepare {
if _, expect := tt.finalData[pfx]; !expect {
if _, ok := prt.Get(pfx); ok {
t.Errorf("[%s] final table: key %v should not be present", tt.name, pfx)
}
}
}
// Ensure the original table isn't modified
for pfx, wantVal := range tt.prepare {
val, ok := tbl.Get(pfx)
if !ok {
t.Errorf("[%s] original table: key %v should be present", tt.name, pfx)
}
if val != wantVal {
t.Errorf("[%s] original table: val %v is not as expected %v", tt.name, val, wantVal)
}
}
}
}
func TestTableModifyCompare__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
gold := new(golden.Table[int])
tbl := new(_TABLE_TYPE[int])
// Update as insert
for i, pfx := range pfxs {
gold.Insert(pfx, i)
tbl.Modify(pfx, func(int, bool) (int, bool) { return i, false })
}
gold.Sort()
tblFlat := tbl.flatSorted()
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[int]); isLite {
if !slices.Equal(gold.AllSorted(), tblFlat.AllSorted()) {
t.Fatal("expected Equal")
}
} else {
if !slices.Equal(*gold, tblFlat) {
t.Fatal("expected Equal")
}
}
cb1 := func(val int, _ bool) int { return val + 1 }
cb2 := func(val int, _ bool) (int, bool) { return val + 1, false }
// Update as update
for _, pfx := range pfxs[:len(pfxs)/2] {
gold.Update(pfx, cb1)
tbl.Modify(pfx, cb2)
}
gold.Sort()
tblFlat = tbl.flatSorted()
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[int]); isLite {
if !slices.Equal(gold.AllSorted(), tblFlat.AllSorted()) {
t.Fatal("expected Equal")
}
} else {
if !slices.Equal(*gold, tblFlat) {
t.Fatal("expected Equal")
}
}
}
func TestTableModifyPersistCompare__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
mut := new(_TABLE_TYPE[int])
imu := new(_TABLE_TYPE[int])
// Update as insert
for i, pfx := range pfxs {
mut.Modify(pfx, func(int, bool) (int, bool) { return i, false })
imu = imu.ModifyPersist(pfx, func(int, bool) (int, bool) { return i, false })
}
if !mut.Equal(imu) {
t.Fatal("expected Equal")
}
cb := func(val int, _ bool) (int, bool) { return val + 1, false }
// Update as update
for _, pfx := range pfxs[:len(pfxs)/2] {
mut.Modify(pfx, cb)
imu = imu.ModifyPersist(pfx, cb)
}
if !mut.Equal(imu) {
t.Fatal("expected Equal")
}
}
func TestTableModifyShuffled__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
var (
numPrefixes = n // prefixes to insert (test deletes 50% of them)
numPerFamily = numPrefixes / 2
deleteCut = numPerFamily / 2
)
for range 10 {
all4 := random.RealWorldPrefixes4(prng, numPerFamily)
all6 := random.RealWorldPrefixes6(prng, numPerFamily)
// pfxs toDelete should be non-overlapping sets
pfxs := slices.Concat(all4, all6)
toDelete := slices.Concat(all4[deleteCut:], all6[deleteCut:])
tbl1 := new(_TABLE_TYPE[string])
// insert
for _, pfx := range pfxs {
tbl1.Insert(pfx, pfx.String())
}
// this callback deletes unconditionally
cb := func(string, bool) (string, bool) { return "", true }
// delete
for _, pfx := range toDelete {
tbl1.Modify(pfx, cb)
}
pfxs2 := slices.Clone(pfxs)
toDelete2 := slices.Clone(toDelete)
prng.Shuffle(len(toDelete2), func(i, j int) { toDelete2[i], toDelete2[j] = toDelete2[j], toDelete2[i] })
tbl2 := new(_TABLE_TYPE[string])
// insert
for _, pfx := range pfxs2 {
tbl2.Insert(pfx, pfx.String())
}
// delete
for _, pfx := range toDelete2 {
tbl2.Modify(pfx, cb)
}
if !tbl1.Equal(tbl2) {
t.Fatal("expected equal")
}
}
}
func TestTableModifyPersistShuffled__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
var (
numPrefixes = n // prefixes to insert (test deletes 50% of them)
numPerFamily = numPrefixes / 2
deleteCut = numPerFamily / 2
)
for range 10 {
all4 := random.RealWorldPrefixes4(prng, numPerFamily)
all6 := random.RealWorldPrefixes6(prng, numPerFamily)
// pfxs toDelete should be non-overlapping sets
pfxs := slices.Concat(all4, all6)
toDelete := slices.Concat(all4[deleteCut:], all6[deleteCut:])
tbl1 := new(_TABLE_TYPE[string])
// insert
for _, pfx := range pfxs {
tbl1.Insert(pfx, pfx.String())
}
// this callback deletes unconditionally
cb := func(string, bool) (string, bool) { return "", true }
// delete
for _, pfx := range toDelete {
tbl1 = tbl1.ModifyPersist(pfx, cb)
}
pfxs2 := slices.Clone(pfxs)
toDelete2 := slices.Clone(toDelete)
prng.Shuffle(len(toDelete2), func(i, j int) { toDelete2[i], toDelete2[j] = toDelete2[j], toDelete2[i] })
tbl2 := new(_TABLE_TYPE[string])
// insert
for _, pfx := range pfxs2 {
tbl2.Insert(pfx, pfx.String())
}
// delete
for _, pfx := range toDelete2 {
tbl2 = tbl2.ModifyPersist(pfx, cb)
}
if !tbl1.Equal(tbl2) {
t.Fatal("expected equal")
}
}
}
// TestUnionMemoryAliasing tests that the Union method does not alias memory
// between the two tables.
func TestTableUnionMemoryAliasing__TABLE_TYPE(t *testing.T) {
t.Parallel()
newTable := func(pfx ...string) *_TABLE_TYPE[any] {
tbl := new(_TABLE_TYPE[any])
for _, s := range pfx {
tbl.Insert(mpp(s), nil)
}
return tbl
}
// First create two tables with disjoint prefixes.
stable := newTable("0.0.0.0/24")
temp := newTable("100.69.1.0/24")
// Verify that the tables are disjoint.
if stable.Overlaps(temp) {
t.Error("stable should not overlap temp")
}
// Now union them.
temp.Union(stable)
// Add a new prefix to temp.
temp.Insert(mpp("0.0.1.0/24"), struct{}{})
// Ensure that stable is unchanged.
_, ok := stable.Lookup(mpa("0.0.1.1"))
if ok {
t.Error("stable should not contain 0.0.1.1")
}
if stable.OverlapsPrefix(mpp("0.0.1.1/32")) {
t.Error("stable should not overlap 0.0.1.1/32")
}
}
// TestUnionPersistMemoryAliasing tests that the Union method does not alias memory
// between the tables.
func TestTableUnionPersistMemoryAliasing__TABLE_TYPE(t *testing.T) {
t.Parallel()
newTable := func(pfx ...string) *_TABLE_TYPE[any] {
tbl := new(_TABLE_TYPE[any])
for _, s := range pfx {
tbl.Insert(mpp(s), nil)
}
return tbl
}
// First create two tables with disjoint prefixes.
a := newTable("100.69.1.0/24")
b := newTable("0.0.0.0/24")
// Verify that the tables are disjoint.
if a.Overlaps(b) {
t.Error("this should not overlap other")
}
// Now union them with copy-on-write.
pTbl := a.UnionPersist(b)
// Add a new prefix to new union
pTbl.Insert(mpp("0.0.1.0/24"), struct{}{})
// Ensure that a is unchanged.
_, ok := a.Lookup(mpa("0.0.1.1"))
if ok {
t.Error("a should not contain 0.0.1.1")
}
if a.OverlapsPrefix(mpp("0.0.1.1/32")) {
t.Error("a should not overlap 0.0.1.1/32")
}
}
func TestTableUnionCompare__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
for range 3 {
pfxs := random.RealWorldPrefixes(prng, n)
gold := new(golden.Table[string])
tbl := new(_TABLE_TYPE[string])
for _, pfx := range pfxs {
gold.Insert(pfx, pfx.String())
tbl.Insert(pfx, pfx.String())
}
pfxs2 := random.RealWorldPrefixes(prng, n)
gold2 := new(golden.Table[string])
tbl2 := new(_TABLE_TYPE[string])
for _, pfx := range pfxs2 {
gold2.Insert(pfx, pfx.String())
tbl2.Insert(pfx, pfx.String())
}
gold.Union(gold2)
tbl.Union(tbl2)
// dump as slow table for comparison
tblFlat := tbl.flatSorted()
// sort for comparison
gold.Sort()
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[string]); isLite {
if !slices.Equal(gold.AllSorted(), tblFlat.AllSorted()) {
t.Fatal("expected Equal")
}
} else {
if !slices.Equal(*gold, tblFlat) {
t.Fatal("expected Equal")
}
}
}
}
func TestTableUnionPersistCompare__TABLE_TYPE(t *testing.T) {
t.Parallel()
prng := rand.New(rand.NewPCG(42, 42))
n := workLoadN()
for range 3 {
pfxs := random.RealWorldPrefixes(prng, n)
gold := new(golden.Table[int])
tbl := new(_TABLE_TYPE[int])
for i, pfx := range pfxs {
gold.Insert(pfx, i)
tbl.Insert(pfx, i)
}
pfxs2 := random.RealWorldPrefixes(prng, n)
gold2 := new(golden.Table[int])
tbl2 := new(_TABLE_TYPE[int])
for i, pfx := range pfxs2 {
gold2.Insert(pfx, i)
tbl2.Insert(pfx, i)
}
gold.Union(gold2)
tblP := tbl.UnionPersist(tbl2)
// dump as slow table for comparison
flatP := tblP.flatSorted()
// sort for comparison
gold.Sort()
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[int]); isLite {
if !slices.Equal(gold.AllSorted(), flatP.AllSorted()) {
t.Fatal("expected Equal")
}
} else {
if !slices.Equal(*gold, flatP) {
t.Fatal("expected Equal")
}
}
}
}
func TestTableClone__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
tbl := new(_TABLE_TYPE[int])
for i, pfx := range pfxs {
tbl.Insert(pfx, i)
}
clone := tbl.Clone()
if !tbl.Equal(clone) {
t.Fatal("expected equal")
}
}
func TestTableCloneShallow__TABLE_TYPE(t *testing.T) {
t.Parallel()
tbl := new(_TABLE_TYPE[*int])
if _, isLite := any(tbl).(*liteTable[*int]); isLite {
t.Skip("liteNode has no real payload")
}
clone := tbl.Clone()
if tbl.dumpString() != clone.dumpString() {
t.Errorf("empty Clone: got:\n%swant:\n%s", clone.dumpString(), tbl.dumpString())
}
val := 1
pfx := mpp("10.0.0.1/32")
tbl.Insert(pfx, &val)
clone = tbl.Clone()
want, _ := tbl.Get(pfx)
got, _ := clone.Get(pfx)
if *got != *want || got != want {
t.Errorf("shallow copy, values and pointers must be equal:\nvalues(%d, %d)\n(ptr(%v, %v)", *got, *want, got, want)
}
// update value, shallow copy of values, clone must be equal
val = 2
want, _ = tbl.Get(pfx)
got, _ = clone.Get(pfx)
if *got != *want {
t.Errorf("memory aliasing after shallow copy, values must be equal:\nvalues(%d, %d)", *got, *want)
}
}
func TestTableCloneDeep__TABLE_TYPE(t *testing.T) {
t.Parallel()
tbl := new(_TABLE_TYPE[*MyInt])
if _, isLite := any(tbl).(*liteTable[*MyInt]); isLite {
t.Skip("liteNode has no real payload")
}
clone := tbl.Clone()
if tbl.dumpString() != clone.dumpString() {
t.Errorf("empty Clone: got:\n%swant:\n%s", clone.dumpString(), tbl.dumpString())
}
val := MyInt(1)
pfx := mpp("10.0.0.1/32")
tbl.Insert(pfx, &val)
clone = tbl.Clone()
want, _ := tbl.Get(pfx)
got, _ := clone.Get(pfx)
if *got != *want || got == want {
t.Errorf("value with Cloner interface, pointers must be different:\nvalues(%d, %d)\n(ptr(%v, %v)", *got, *want, got, want)
}
// update value, deep copy of values, cloned value must now be different
val = 2
want, _ = tbl.Get(pfx)
got, _ = clone.Get(pfx)
if *got == *want {
t.Errorf("memory aliasing after deep copy, values must be different:\nvalues(%d, %d)", *got, *want)
}
}
func TestTableUnionShallow__TABLE_TYPE(t *testing.T) {
t.Parallel()
tbl1 := new(_TABLE_TYPE[*int])
tbl2 := new(_TABLE_TYPE[*int])
if _, isLite := any(tbl1).(*liteTable[*int]); isLite {
t.Skip("liteNode has no real payload")
}
val := 1
pfx := mpp("10.0.0.1/32")
tbl2.Insert(pfx, &val)
tbl1.Union(tbl2)
got, _ := tbl1.Get(pfx)
want, _ := tbl2.Get(pfx)
if *got != *want || got != want {
t.Errorf("shallow copy, values and pointers must be equal:\nvalues(%d, %d)\n(ptr(%v, %v)", *got, *want, got, want)
}
// update value, shallow copy of values, union must be equal
val = 2
got, _ = tbl1.Get(pfx)
want, _ = tbl2.Get(pfx)
if *got != *want {
t.Errorf("memory aliasing after shallow copy, values must be equal:\nvalues(%d, %d)", *got, *want)
}
}
func TestTableUnionDeep__TABLE_TYPE(t *testing.T) {
t.Parallel()
tbl1 := new(_TABLE_TYPE[*MyInt])
tbl2 := new(_TABLE_TYPE[*MyInt])
if _, isLite := any(tbl1).(*liteTable[*MyInt]); isLite {
t.Skip("liteNode has no real payload")
}
val := MyInt(1)
pfx := mpp("10.0.0.1/32")
tbl2.Insert(pfx, &val)
tbl1.Union(tbl2)
got, _ := tbl1.Get(pfx)
want, _ := tbl2.Get(pfx)
if *got != *want || got == want {
t.Errorf("value with Cloner interface, pointers must be different:\nvalues(%d, %d)\n(ptr(%v, %v)", *got, *want, got, want)
}
// update value, shallow copy of values, union must be equal
val = 2
got, _ = tbl1.Get(pfx)
want, _ = tbl2.Get(pfx)
if *got == *want {
t.Errorf("memory aliasing after deep copy, values must be different:\nvalues(%d, %d)", *got, *want)
}
}
// test some edge cases
func TestTableOverlapsPrefixEdgeCases__TABLE_TYPE(t *testing.T) {
t.Parallel()
type probe struct {
pfx netip.Prefix
want bool
}
type probes []probe
type pfxs []netip.Prefix
type test struct {
name string
insert pfxs
probes probes
}
tests := []test{
{
name: "empty table",
insert: nil,
probes: probes{{mpp("0.0.0.0/0"), false}, {mpp("::/0"), false}},
},
{
name: "default route I",
insert: pfxs{mpp("10.0.0.0/9"), mpp("2001:db8::/32")},
probes: probes{{mpp("0.0.0.0/0"), true}, {mpp("::/0"), true}},
},
{
name: "default route II",
insert: pfxs{mpp("0.0.0.0/0"), mpp("::/0")},
probes: probes{{mpp("10.0.0.0/9"), true}, {mpp("2001:db8::/32"), true}},
},
{
name: "single IP I",
insert: pfxs{mpp("10.0.0.0/7"), mpp("2001::/16")},
probes: probes{{mpp("10.1.2.3/32"), true}, {mpp("2001:db8:affe::cafe/128"), true}},
},
{
name: "single IP II",
insert: pfxs{mpp("10.1.2.3/32"), mpp("2001:db8:affe::cafe/128")},
probes: probes{{mpp("10.0.0.0/7"), true}, {mpp("2001::/16"), true}},
},
{
name: "same IP",
insert: pfxs{mpp("10.1.2.3/32"), mpp("2001:db8:affe::cafe/128")},
probes: probes{{mpp("10.1.2.3/32"), true}, {mpp("2001:db8:affe::cafe/128"), true}},
},
}
for _, tt := range tests {
tbl := new(_TABLE_TYPE[int])
for _, pfx := range tt.insert {
tbl.Insert(pfx, 0)
}
for _, probe := range tt.probes {
got := tbl.OverlapsPrefix(probe.pfx)
if got != probe.want {
t.Errorf("[%s] OverlapsPrefix(%v) = %v, want %v", tt.name, probe.pfx, got, probe.want)
}
}
}
}
func TestTableSize__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
tbl := new(_TABLE_TYPE[any])
if tbl.Size() != 0 {
t.Errorf("empty Table: want: 0, got: %d", tbl.Size())
}
if tbl.Size4() != 0 {
t.Errorf("empty Table: want: 0, got: %d", tbl.Size4())
}
if tbl.Size6() != 0 {
t.Errorf("empty Table: want: 0, got: %d", tbl.Size6())
}
pfxs1 := random.RealWorldPrefixes(prng, n)
pfxs2 := random.RealWorldPrefixes(prng, n)
for _, pfx := range pfxs1 {
tbl.Insert(pfx, nil)
}
for _, pfx := range pfxs2 {
tbl.Modify(pfx, func(any, bool) (any, bool) { return nil, false })
}
pfxs1 = append(pfxs1, pfxs2...)
for _, pfx := range pfxs1[:n] {
tbl.Modify(pfx, func(any, bool) (any, bool) { return nil, false })
}
for _, pfx := range random.RealWorldPrefixes(prng, n) {
tbl.Delete(pfx)
}
var allCount4 int
var allCount6 int
for range tbl.AllSorted4() {
allCount4++
}
for range tbl.AllSorted6() {
allCount6++
}
if allCount4 != tbl.Size4() {
t.Errorf("Size4: want: %d, got: %d", allCount4, tbl.Size4())
}
if allCount6 != tbl.Size6() {
t.Errorf("Size6: want: %d, got: %d", allCount6, tbl.Size6())
}
}
// TestAll tests All with random samples
func TestTableAll__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
tbl := new(_TABLE_TYPE[string])
// Insert all prefixes with their values
for _, pfx := range pfxs {
tbl.Insert(pfx, pfx.String())
}
// Collect all prefixes from All
gotPrefixes := make([]netip.Prefix, 0, n)
gotValues := make([]string, 0, n)
for pfx, val := range tbl.All() {
gotPrefixes = append(gotPrefixes, pfx)
gotValues = append(gotValues, val)
}
// Collect all prefixes from All4 and All6
got4Prefixes := make([]netip.Prefix, 0, n)
got4Values := make([]string, 0, n)
for pfx, val := range tbl.All4() {
got4Prefixes = append(got4Prefixes, pfx)
got4Values = append(got4Values, val)
}
got6Prefixes := make([]netip.Prefix, 0, n)
got6Values := make([]string, 0, n)
for pfx, val := range tbl.All6() {
got6Prefixes = append(got6Prefixes, pfx)
got6Values = append(got6Values, val)
}
// Verify we got exactly the same number of prefixes
if len(gotPrefixes) != len(pfxs) {
t.Fatalf("Expected %d prefixes, got %d", len(pfxs), len(gotPrefixes))
}
if len(got4Prefixes)+len(got6Prefixes) != len(pfxs) {
t.Fatalf("Expected %d prefixes, got %d", len(pfxs), len(got4Prefixes)+len(got6Prefixes))
}
if !slices.Equal(slices.Concat(got4Prefixes, got6Prefixes), gotPrefixes) {
t.Fatal("Prefixes: All4 + All6 != All")
}
if !slices.Equal(slices.Concat(got4Values, got6Values), gotValues) {
t.Fatal("Values: All4 + All6 != All")
}
// Verify we can find each original prefix in the results
// Create a map for O(1) lookup verification
resultMap := make(map[netip.Prefix]string, n)
for i, pfx := range gotPrefixes {
resultMap[pfx] = gotValues[i]
}
for _, pfx := range pfxs {
val, found := resultMap[pfx]
if !found {
t.Fatalf("Original prefix %v not found in All results", pfx)
}
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[string]); !isLite {
if val != pfx.String() {
t.Fatalf("Original prefix %v has wrong value: expected %s, got %s",
pfx, pfx.String(), val)
}
}
}
// Verify no duplicates in results
seen := make(map[netip.Prefix]bool, n)
for _, pfx := range gotPrefixes {
if seen[pfx] {
t.Fatalf("Duplicate prefix %v found in All results", pfx)
}
seen[pfx] = true
}
}
func TestTableAllSorted__TABLE_TYPE(t *testing.T) {
t.Parallel()
// Test cases with known CIDR sort order
testCases := []struct {
name string
prefixes []string
expected []string // Expected order after sorting
}{
{
name: "Mixed IPv4 addresses and prefix lengths",
prefixes: []string{
"10.0.0.0/16",
"10.0.0.0/8",
"192.168.1.0/24",
"10.0.0.0/24",
"172.16.0.0/12",
},
expected: []string{
"10.0.0.0/8", // Same address, shorter prefix first
"10.0.0.0/16", // Same address, longer prefix
"10.0.0.0/24", // Same address, longest prefix
"172.16.0.0/12", // Next address
"192.168.1.0/24", // Highest address
},
},
{
name: "Mixed IPv6 addresses and prefix lengths",
prefixes: []string{
"2001:db8::/32",
"2001:db8::/64",
"2000::/16",
"2001:db8:1::/48",
},
expected: []string{
"2000::/16", // Lowest address
"2001:db8::/32", // Same address, shorter prefix first
"2001:db8::/64", // Same address, longer prefix
"2001:db8:1::/48", // Higher address
},
},
{
name: "Mixed IPv4 and IPv6",
prefixes: []string{
"192.168.1.0/24",
"2001:db8::/32",
"10.0.0.0/8",
"::1/128",
},
expected: []string{
"10.0.0.0/8", // IPv4 addresses come first (lower in comparison)
"192.168.1.0/24", // Next IPv4 address
"::1/128", // IPv6 addresses after IPv4
"2001:db8::/32", // Higher IPv6 address
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
tbl := new(_TABLE_TYPE[int])
// Insert prefixes with index as value
for i, prefixStr := range tc.prefixes {
pfx := netip.MustParsePrefix(prefixStr)
tbl.Insert(pfx, i)
}
// Collect sorted results
var actualOrder []string
for pfx := range tbl.AllSorted() {
actualOrder = append(actualOrder, pfx.String())
}
// Verify the order matches expected
if len(actualOrder) != len(tc.expected) {
t.Fatalf("%s: Expected %d results, got %d", tc.name, len(tc.expected), len(actualOrder))
}
// Collect sorted 4 results
var actual4Order []string
for pfx := range tbl.AllSorted4() {
actual4Order = append(actual4Order, pfx.String())
}
// Collect sorted 6 results
var actual6Order []string
for pfx := range tbl.AllSorted6() {
actual6Order = append(actual6Order, pfx.String())
}
if !slices.Equal(slices.Concat(actual4Order, actual6Order), actualOrder) {
t.Fatalf("%s: Prefixes: AllSorted4 + AllSorted6 != AllSorted", tc.name)
}
for i, expected := range tc.expected {
if actualOrder[i] != expected {
t.Errorf("%s:At position %d: expected %s, got %s", tc.name, i, expected, actualOrder[i])
t.Errorf("%s:Full expected order: %v", tc.name, tc.expected)
t.Errorf("%s:Full actual order: %v", tc.name, actualOrder)
break
}
}
})
}
}
func TestTableSubnets__TABLE_TYPE(t *testing.T) {
t.Parallel()
var zeroPfx netip.Prefix
t.Run("empty table", func(t *testing.T) {
t.Parallel()
tbl := new(_TABLE_TYPE[string])
pfx := mpp("::1/128")
for range tbl.Subnets(pfx) {
t.Errorf("empty table, must not range over")
}
})
t.Run("invalid prefix", func(t *testing.T) {
tbl := new(_TABLE_TYPE[string])
pfx := mpp("::1/128")
val := "foo"
tbl.Insert(pfx, val)
for range tbl.Subnets(zeroPfx) {
t.Errorf("invalid prefix, must not range over")
}
})
t.Run("identity", func(t *testing.T) {
tbl := new(_TABLE_TYPE[string])
pfx := mpp("::1/128")
val := "foo"
tbl.Insert(pfx, val)
for p, v := range tbl.Subnets(pfx) {
if p != pfx {
t.Errorf("Subnet(%v), got: %v, want: %v", pfx, p, pfx)
}
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[string]); !isLite {
if v != val {
t.Errorf("Subnet(%v), got: %v, want: %v", pfx, v, val)
}
}
}
})
t.Run("default gateway", func(t *testing.T) {
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
want4 := n - n/2
want6 := n + n/2
tbl := new(_TABLE_TYPE[int])
for i, pfx := range random.RealWorldPrefixes4(prng, want4) {
tbl.Insert(pfx, i)
}
for i, pfx := range random.RealWorldPrefixes6(prng, want6) {
tbl.Insert(pfx, i)
}
// default gateway v4 covers all v4 prefixes in table
dg4 := mpp("0.0.0.0/0")
got4 := 0
for range tbl.Subnets(dg4) {
got4++
}
// default gateway v6 covers all v6 prefixes in table
dg6 := mpp("::/0")
got6 := 0
for range tbl.Subnets(dg6) {
got6++
}
if got4 != want4 {
t.Errorf("Subnets v4, want: %d, got: %d", want4, got4)
}
if got6 != want6 {
t.Errorf("Subnets v6, want: %d, got: %d", want6, got6)
}
})
}
func TestTableSubnetsCompare__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
gold := new(golden.Table[int])
tbl := new(_TABLE_TYPE[int])
for i, pfx := range pfxs {
gold.Insert(pfx, i)
tbl.Insert(pfx, i)
}
for _, pfx := range random.RealWorldPrefixes(prng, n) {
t.Run("subtest", func(t *testing.T) {
t.Parallel()
gotGold := gold.Subnets(pfx)
gotTbl := []netip.Prefix{}
for pfx := range tbl.Subnets(pfx) {
gotTbl = append(gotTbl, pfx)
}
if !slices.Equal(gotGold, gotTbl) {
t.Fatalf("Subnets(%q) = %v, want %v", pfx, gotTbl, gotGold)
}
})
}
}
func TestTableSupernetsEdgeCase__TABLE_TYPE(t *testing.T) {
t.Parallel()
var zeroPfx netip.Prefix
t.Run("empty table", func(t *testing.T) {
t.Parallel()
tbl := new(_TABLE_TYPE[any])
pfx := mpp("::1/128")
tbl.Supernets(pfx)(func(_ netip.Prefix, _ any) bool {
t.Errorf("empty table, must not range over")
return false
})
})
t.Run("invalid prefix", func(t *testing.T) {
tbl := new(_TABLE_TYPE[any])
pfx := mpp("::1/128")
val := "foo"
tbl.Insert(pfx, val)
tbl.Supernets(zeroPfx)(func(_ netip.Prefix, _ any) bool {
t.Errorf("invalid prefix, must not range over")
return false
})
})
t.Run("identity", func(t *testing.T) {
tbl := new(_TABLE_TYPE[string])
pfx := mpp("::1/128")
val := "foo"
tbl.Insert(pfx, val)
for p, v := range tbl.Supernets(pfx) {
if p != pfx {
t.Errorf("Supernets(%v), got: %v, want: %v", pfx, p, pfx)
}
// Skip value comparison for liteTable (no real payload)
if _, isLite := any(tbl).(*liteTable[string]); !isLite {
if v != val {
t.Errorf("Supernets(%v), got: %v, want: %v", pfx, v, val)
}
}
}
})
}
func TestTableSupernetsCompare__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
pfxs := random.RealWorldPrefixes(prng, n)
gold := new(golden.Table[int])
tbl := new(_TABLE_TYPE[int])
for i, pfx := range pfxs {
gold.Insert(pfx, i)
tbl.Insert(pfx, i)
}
for _, pfx := range random.RealWorldPrefixes(prng, n) {
t.Run("subtest", func(t *testing.T) {
t.Parallel()
gotGold := gold.Supernets(pfx)
gotTbl := []netip.Prefix{}
for p := range tbl.Supernets(pfx) {
gotTbl = append(gotTbl, p)
}
if !slices.Equal(gotGold, gotTbl) {
t.Fatalf("Supernets(%q) = %v, want %v", pfx, gotTbl, gotGold)
}
})
}
}
func TestTableMarshalText__TABLE_TYPE(t *testing.T) {
tests := []struct {
name string
expectedData map[netip.Prefix]string
}{
{
name: "empty",
expectedData: map[netip.Prefix]string{},
},
{
name: "with_data",
expectedData: map[netip.Prefix]string{
mpp("192.168.1.0/24"): "test1",
mpp("10.0.0.0/8"): "test2",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tbl := new(_TABLE_TYPE[string])
// Insert test data
for prefix, value := range tt.expectedData {
tbl.Insert(prefix, value)
}
data, err := tbl.MarshalText()
if err != nil {
t.Errorf("MarshalText failed: %v", err)
}
if len(tt.expectedData) > 0 && len(data) == 0 {
t.Error("Expected non-empty marshaled text")
}
// Check that all expected values appear in marshaled text
text := string(data)
for _, value := range tt.expectedData {
if !strings.Contains(text, value) {
t.Errorf("Marshaled text doesn't contain expected value: %s", value)
}
}
})
}
}
func TestTableMarshalJSON__TABLE_TYPE(t *testing.T) {
tests := []struct {
name string
expectedData map[netip.Prefix]any
}{
{
name: "empty",
expectedData: map[netip.Prefix]any{},
},
{
name: "string_values",
expectedData: map[netip.Prefix]any{
mpp("192.168.1.0/24"): "net1",
mpp("10.0.0.0/8"): "net2",
},
},
{
name: "mixed_values",
expectedData: map[netip.Prefix]any{
mpp("192.168.1.0/24"): "string",
mpp("10.0.0.0/8"): 42,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tbl := new(_TABLE_TYPE[any])
// Insert test data
for prefix, value := range tt.expectedData {
tbl.Insert(prefix, value)
}
jsonData, err := tbl.MarshalJSON()
if err != nil {
t.Errorf("JSON marshaling failed: %v", err)
}
if len(jsonData) == 0 {
t.Error("Expected valid JSON")
}
// Should be valid JSON
var result interface{}
if err := json.Unmarshal(jsonData, &result); err != nil {
t.Errorf("Invalid JSON produced: %v", err)
}
})
}
}
func TestTableDumpList4__TABLE_TYPE(t *testing.T) {
tests := []struct {
name string
expectedData map[netip.Prefix]string
expectItems int
}{
{
name: "empty",
expectedData: map[netip.Prefix]string{},
expectItems: 0,
},
{
name: "single_ipv4",
expectedData: map[netip.Prefix]string{
mpp("192.168.1.0/24"): "lan",
},
expectItems: 1,
},
{
name: "multiple_ipv4",
expectedData: map[netip.Prefix]string{
mpp("192.168.1.0/24"): "lan",
mpp("10.0.0.0/8"): "private",
},
expectItems: 2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tbl := new(_TABLE_TYPE[string])
// Insert test data
for prefix, value := range tt.expectedData {
tbl.Insert(prefix, value)
}
dumpList := tbl.DumpList4()
// Count total nodes in the tree (including nested)
totalNodes := countDumpListNodes(dumpList)
if totalNodes != tt.expectItems {
t.Errorf("DumpList4() total nodes (%d) does not match expected (%d)", totalNodes, tt.expectItems)
}
// Verify all nodes are IPv4
verifyAllIPv4Nodes(t, dumpList)
})
}
}
func TestTableDumpList6__TABLE_TYPE(t *testing.T) {
tests := []struct {
name string
expectedData map[netip.Prefix]string
expectItems int
}{
{
name: "empty",
expectedData: map[netip.Prefix]string{},
expectItems: 0,
},
{
name: "single_ipv6",
expectedData: map[netip.Prefix]string{
mpp("2001:db8::/32"): "doc",
},
expectItems: 1,
},
{
name: "multiple_ipv6",
expectedData: map[netip.Prefix]string{
mpp("2001:db8::/32"): "doc",
mpp("fe80::/10"): "link-local",
},
expectItems: 2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tbl := new(_TABLE_TYPE[string])
// Insert test data
for prefix, value := range tt.expectedData {
tbl.Insert(prefix, value)
}
dumpList := tbl.DumpList6()
// Count total nodes in the tree (including nested)
totalNodes := countDumpListNodes(dumpList)
if totalNodes != tt.expectItems {
t.Errorf("DumpList6() total nodes (%d) does not match expected (%d)", totalNodes, tt.expectItems)
}
// Verify all nodes are IPv6
verifyAllIPv6Nodes(t, dumpList)
})
}
}
func TestTableEqual__TABLE_TYPE(t *testing.T) {
t.Parallel()
n := workLoadN()
prng := rand.New(rand.NewPCG(42, 42))
at := new(_TABLE_TYPE[int])
for i, pfx := range random.RealWorldPrefixes(prng, n) {
at.Insert(pfx, i)
}
t.Run("clone", func(t *testing.T) {
t.Parallel()
bt := at.Clone()
if !at.Equal(bt) {
t.Error("expected true, got false")
}
})
}
func TestTableExtra__TABLE_TYPE(t *testing.T) {
t.Parallel()
// 1. Equal: checking self equality
t1 := new(_TABLE_TYPE[int])
t1.Insert(mpp("10.0.0.0/8"), 42)
t1.Insert(mpp("2001:db8::/32"), 42)
if !t1.Equal(t1) {
t.Error("expected table to be equal to itself")
}
// 2. Equal: checking different size4 / size6
t2 := new(_TABLE_TYPE[int])
t2.Insert(mpp("10.0.0.0/8"), 42)
if t1.Equal(t2) {
t.Error("expected false for different size6")
}
t3 := new(_TABLE_TYPE[int])
t3.Insert(mpp("2001:db8::/32"), 42)
if t1.Equal(t3) {
t.Error("expected false for different size4")
}
// 3. Overlaps6: checking when size6 is 0
emptyT := new(_TABLE_TYPE[int])
if t1.Overlaps6(emptyT) {
t.Error("expected false when other table has no IPv6 prefixes")
}
if emptyT.Overlaps6(t1) {
t.Error("expected false when this table has no IPv6 prefixes")
}
// 4. Fprint: nil writer check
var nilWriter io.Writer = nil
if err := t1.Fprint(nilWriter); err == nil {
t.Error("expected error when writing to nil writer")
}
// 5. ModifyPersist: no-op deletion of non-existing key
t4 := new(_TABLE_TYPE[int])
t5 := t4.ModifyPersist(mpp("10.0.0.0/8"), func(val int, ok bool) (int, bool) {
return 0, true
})
if t5 != t4 {
t.Error("expected same table pointer for no-op ModifyPersist")
}
}