sub/ipalloc/ipalloc_test.go

305 lines
7.1 KiB
Go

package ipalloc
import (
"fmt"
"log"
"net/netip"
"testing"
)
func TestSplitPrefix(t *testing.T) {
tests := []struct{ in, lo, hi string }{
{"0.0.0.0/0", "0.0.0.0/1", "128.0.0.0/1"},
{"0.0.0.0/29", "0.0.0.0/30", "0.0.0.4/30"},
{"0.0.0.0/31", "0.0.0.0/32", "0.0.0.1/32"},
{"0.0.0.1/0", "0.0.0.0/1", "128.0.0.0/1"},
}
for _, test := range tests {
lo, hi := splitPrefix(netip.MustParsePrefix(test.in))
if lo != netip.MustParsePrefix(test.lo) || hi != netip.MustParsePrefix(test.hi) {
t.Fatal(test.in, lo, hi)
}
}
singles := []string{"0.0.0.0/32", "::/128"}
for _, test := range singles {
func() {
defer func() {
_ = recover()
}()
_, _ = splitPrefix(netip.MustParsePrefix(test))
t.Fatalf("Expected panic when splitting %q", test)
}()
}
}
func TestTreeAllocSmall(t *testing.T) {
tests := []struct {
bits int
best string
}{
{32, "0.0.0.0/32"},
{32, "0.0.0.1/32"},
{31, "0.0.0.2/31"},
{32, "0.0.0.4/32"},
}
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/28")}
for _, test := range tests {
//t.Log("searching for", test.bits)
best := root.Alloc(test.bits)
if best == nil {
log.Print(root.Dot())
t.Fatal(root, test.bits)
}
//t.Log(test, "->", best.prefix)
if best.prefix != netip.MustParsePrefix(test.best) {
log.Print(root.Dot())
t.Fatal(root, test.bits, best.prefix.String())
}
}
}
func TestTreeAlloc(t *testing.T) {
tests := []struct {
bits int
best string
}{
{32, "0.0.0.0/32"},
{32, "0.0.0.1/32"},
{1, "128.0.0.0/1"},
{2, "64.0.0.0/2"},
{31, "0.0.0.2/31"},
{32, "0.0.0.4/32"},
}
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/0")}
for _, test := range tests {
best := root.Alloc(test.bits)
if best == nil {
t.Fatal(root, test.bits)
}
if best.prefix != netip.MustParsePrefix(test.best) {
log.Print(root.Dot())
t.Fatal(root, test.bits, best.prefix.String())
}
}
}
func TestTreeDealloc(t *testing.T) {
tests := []struct {
bits int
best string
}{
{32, "0.0.0.0/32"},
{32, "0.0.0.1/32"},
{1, "128.0.0.0/1"},
{2, "64.0.0.0/2"},
{31, "0.0.0.2/31"},
{32, "0.0.0.4/32"},
}
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/0")}
for _, test := range tests {
best := root.Alloc(test.bits)
if best == nil {
t.Fatal(root, test.bits)
}
if best.prefix != netip.MustParsePrefix(test.best) {
t.Fatal(root, test.bits, best.prefix.String())
}
}
deallocs := []string{
"0.0.0.0/32",
"0.0.0.1/32",
"128.0.0.0/1",
"64.0.0.0/2",
"0.0.0.2/31",
"0.0.0.4/32",
}
for _, test := range deallocs {
if err := root.Dealloc(netip.MustParsePrefix(test)); err != nil {
t.Fatal(test, err)
}
}
if !root.Leaf() {
root.Walk(func(t *tree) bool {
log.Println(t.prefix, t.Leaf())
return true
})
t.Fatalf("root not empty: %#v", root)
}
first := root.Alloc(2).prefix
root.Alloc(2)
if err := root.Dealloc(first); err != nil {
t.Fatal("failed to remove", first)
}
if first != root.Alloc(2).prefix {
root.Walk(func(t *tree) bool {
log.Println(t.prefix, t.Leaf())
return true
})
t.Fatal(first)
}
}
func TestFindBestPrefix(t *testing.T) {
tests := []struct {
bits int
best string
}{
{32, "0.0.0.0/32"},
{32, "0.0.0.1/32"},
{1, "128.0.0.0/1"},
{2, "64.0.0.0/2"},
{31, "0.0.0.2/31"},
{32, "0.0.0.4/32"},
}
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/0")}
for _, test := range tests {
best := root.Alloc(test.bits)
if best == nil {
t.Fatal(root, test.bits)
}
if best.prefix != netip.MustParsePrefix(test.best) {
t.Fatal(root, test.bits, best.prefix.String())
}
}
best := [][2]string{
{"128.0.0.0/32", "128.0.0.0/1"},
{"128.0.0.1/32", "128.0.0.0/1"},
}
for _, test := range best {
best := root.FindBestPrefix(netip.MustParsePrefix(test[0]))
want := netip.MustParsePrefix(test[1])
if best.prefix != want {
t.Fatal(best, want)
}
}
}
func TestInsert(t *testing.T) {
tests := []string{
"0.0.0.1/32",
"0.0.0.0/32",
"128.0.0.0/1",
"64.0.0.0/2",
"0.0.0.4/32",
"0.0.0.2/31",
}
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/0")}
for _, test := range tests {
p := netip.MustParsePrefix(test)
insert := root.Insert(p)
if insert == nil {
t.Fatal(root, test)
}
match := root.Find(p)
if match.prefix != p {
t.Fatal("unable to find", p, "in", root)
}
}
for _, test := range tests {
p := netip.MustParsePrefix(test)
insert := root.Insert(p)
if insert != nil {
t.Fatal(root, test)
}
}
}
func expectAlloc(t *testing.T, root *tree, bits int, expect string) {
alloc := root.Alloc(bits)
if alloc == nil {
t.Log("graph:\n", root.Dot())
t.Fatal()
}
if alloc.prefix != netip.MustParsePrefix(expect) {
t.Logf("allocating %d bits failed: got %s, want %s", bits, alloc.prefix, expect)
t.Log("graph:\n", root.Dot())
t.Fatal()
}
}
func TestAllocDealloc(t *testing.T) {
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/30")}
expectAlloc(t, root, 31, "0.0.0.0/31")
expectAlloc(t, root, 32, "0.0.0.2/32")
if err := root.Dealloc(netip.MustParsePrefix("0.0.0.0/31")); err != nil {
t.Fatalf("%s", err)
}
expectAlloc(t, root, 32, "0.0.0.3/32")
expectAlloc(t, root, 32, "0.0.0.0/32")
expectAlloc(t, root, 32, "0.0.0.1/32")
}
func TestAllocDealloc2(t *testing.T) {
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/29")}
expectAlloc(t, root, 31, "0.0.0.0/31")
expectAlloc(t, root, 31, "0.0.0.2/31")
expectAlloc(t, root, 32, "0.0.0.4/32")
if err := root.Dealloc(netip.MustParsePrefix("0.0.0.0/31")); err != nil {
t.Fatalf("%s", err)
}
expectAlloc(t, root, 32, "0.0.0.5/32")
expectAlloc(t, root, 32, "0.0.0.0/32")
expectAlloc(t, root, 32, "0.0.0.1/32")
}
func TestAllocFull(t *testing.T) {
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/31")}
expectAlloc(t, root, 32, "0.0.0.0/32")
expectAlloc(t, root, 32, "0.0.0.1/32")
if alloc := root.Alloc(32); alloc != nil {
t.Fatalf("expected nil for allocation in full tree, got %v", alloc)
}
}
func TestFindNotAvailable(t *testing.T) {
root := &tree{prefix: netip.MustParsePrefix("0.0.0.0/31")}
if match := root.Find(netip.MustParsePrefix("0.0.0.0/32")); match != nil {
t.Fatalf("expected not to find %q in empty tree", match.prefix)
}
if match := root.Find(netip.MustParsePrefix("128.0.0.0/32")); match != nil {
t.Fatalf("expected not to find %q in empty tree", match.prefix)
}
if match := root.Find(netip.MustParsePrefix("::/32")); match != nil {
t.Fatalf("expected not to find %q in empty tree", match.prefix)
}
}
func benchRunN(b *testing.B, base netip.Addr, bits int) {
root := &tree{
prefix: netip.PrefixFrom(base, bits),
}
bitlen := base.BitLen()
for i := 0; i < b.N; i++ {
if alloc := root.Alloc(bitlen); alloc == nil {
root = &tree{
prefix: netip.PrefixFrom(base, bits),
}
}
}
}
func BenchmarkFillv4(b *testing.B) {
for bits := 24; bits >= 0; bits -= 8 {
b.Run(fmt.Sprintf("%s-%02d", b.Name(), bits), func(b *testing.B) {
benchRunN(b, netip.MustParseAddr("0.0.0.0"), bits)
})
}
}
func BenchmarkFillv6(b *testing.B) {
for bits := 96; bits >= 0; bits -= 16 {
b.Run(fmt.Sprintf("%s-%02d", b.Name(), bits), func(b *testing.B) {
benchRunN(b, netip.MustParseAddr("::"), bits)
})
}
}