You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
consul/internal/radix/radix_test.go

412 lines
7.3 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package radix
import (
crand "crypto/rand"
"fmt"
"reflect"
"sort"
"strconv"
"testing"
)
func TestRadix(t *testing.T) {
var min, max string
inp := make(map[string]interface{})
for i := 0; i < 1000; i++ {
gen := generateUUID()
inp[gen] = i
if gen < min || i == 0 {
min = gen
}
if gen > max || i == 0 {
max = gen
}
}
r := NewFromMap(inp)
if r.Len() != len(inp) {
t.Fatalf("bad length: %v %v", r.Len(), len(inp))
}
r.Walk(func(k string, v interface{}) bool {
println(k)
return false
})
for k, v := range inp {
out, ok := r.Get(k)
if !ok {
t.Fatalf("missing key: %v", k)
}
if out != v {
t.Fatalf("value mis-match: %v %v", out, v)
}
}
// Check min and max
outMin, _, _ := r.Minimum()
if outMin != min {
t.Fatalf("bad minimum: %v %v", outMin, min)
}
outMax, _, _ := r.Maximum()
if outMax != max {
t.Fatalf("bad maximum: %v %v", outMax, max)
}
for k, v := range inp {
out, ok := r.Delete(k)
if !ok {
t.Fatalf("missing key: %v", k)
}
if out != v {
t.Fatalf("value mis-match: %v %v", out, v)
}
}
if r.Len() != 0 {
t.Fatalf("bad length: %v", r.Len())
}
}
func TestRoot(t *testing.T) {
r := New[bool]()
_, ok := r.Delete("")
if ok {
t.Fatalf("bad")
}
_, ok = r.Insert("", true)
if ok {
t.Fatalf("bad")
}
val, ok := r.Get("")
if !ok || val != true {
t.Fatalf("bad: %v", val)
}
val, ok = r.Delete("")
if !ok || val != true {
t.Fatalf("bad: %v", val)
}
}
func TestDelete(t *testing.T) {
r := New[bool]()
s := []string{"", "A", "AB"}
for _, ss := range s {
r.Insert(ss, true)
}
for _, ss := range s {
_, ok := r.Delete(ss)
if !ok {
t.Fatalf("bad %q", ss)
}
}
}
func TestDeletePrefix(t *testing.T) {
type exp struct {
inp []string
prefix string
out []string
numDeleted int
}
cases := []exp{
{[]string{"", "A", "AB", "ABC", "R", "S"}, "A", []string{"", "R", "S"}, 3},
{[]string{"", "A", "AB", "ABC", "R", "S"}, "ABC", []string{"", "A", "AB", "R", "S"}, 1},
{[]string{"", "A", "AB", "ABC", "R", "S"}, "", []string{}, 6},
{[]string{"", "A", "AB", "ABC", "R", "S"}, "S", []string{"", "A", "AB", "ABC", "R"}, 1},
{[]string{"", "A", "AB", "ABC", "R", "S"}, "SS", []string{"", "A", "AB", "ABC", "R", "S"}, 0},
}
for _, test := range cases {
r := New[bool]()
for _, ss := range test.inp {
r.Insert(ss, true)
}
deleted := r.DeletePrefix(test.prefix)
if deleted != test.numDeleted {
t.Fatalf("Bad delete, expected %v to be deleted but got %v", test.numDeleted, deleted)
}
out := []string{}
fn := func(s string, v bool) bool {
out = append(out, s)
return false
}
r.Walk(fn)
if !reflect.DeepEqual(out, test.out) {
t.Fatalf("mis-match: %v %v", out, test.out)
}
}
}
func TestLongestPrefix(t *testing.T) {
r := New[interface{}]()
keys := []string{
"",
"foo",
"foobar",
"foobarbaz",
"foobarbazzip",
"foozip",
}
for _, k := range keys {
r.Insert(k, nil)
}
if r.Len() != len(keys) {
t.Fatalf("bad len: %v %v", r.Len(), len(keys))
}
type exp struct {
inp string
out string
}
cases := []exp{
{"a", ""},
{"abc", ""},
{"fo", ""},
{"foo", "foo"},
{"foob", "foo"},
{"foobar", "foobar"},
{"foobarba", "foobar"},
{"foobarbaz", "foobarbaz"},
{"foobarbazzi", "foobarbaz"},
{"foobarbazzip", "foobarbazzip"},
{"foozi", "foo"},
{"foozip", "foozip"},
{"foozipzap", "foozip"},
}
for _, test := range cases {
m, _, ok := r.LongestPrefix(test.inp)
if !ok {
t.Fatalf("no match: %v", test)
}
if m != test.out {
t.Fatalf("mis-match: %v %v", m, test)
}
}
}
func TestWalkPrefix(t *testing.T) {
r := New[interface{}]()
keys := []string{
"foobar",
"foo/bar/baz",
"foo/baz/bar",
"foo/zip/zap",
"zipzap",
}
for _, k := range keys {
r.Insert(k, nil)
}
if r.Len() != len(keys) {
t.Fatalf("bad len: %v %v", r.Len(), len(keys))
}
type exp struct {
inp string
out []string
}
cases := []exp{
{
"f",
[]string{"foobar", "foo/bar/baz", "foo/baz/bar", "foo/zip/zap"},
},
{
"foo",
[]string{"foobar", "foo/bar/baz", "foo/baz/bar", "foo/zip/zap"},
},
{
"foob",
[]string{"foobar"},
},
{
"foo/",
[]string{"foo/bar/baz", "foo/baz/bar", "foo/zip/zap"},
},
{
"foo/b",
[]string{"foo/bar/baz", "foo/baz/bar"},
},
{
"foo/ba",
[]string{"foo/bar/baz", "foo/baz/bar"},
},
{
"foo/bar",
[]string{"foo/bar/baz"},
},
{
"foo/bar/baz",
[]string{"foo/bar/baz"},
},
{
"foo/bar/bazoo",
[]string{},
},
{
"z",
[]string{"zipzap"},
},
}
for _, test := range cases {
out := []string{}
fn := func(s string, v interface{}) bool {
out = append(out, s)
return false
}
r.WalkPrefix(test.inp, fn)
sort.Strings(out)
sort.Strings(test.out)
if !reflect.DeepEqual(out, test.out) {
t.Fatalf("mis-match: %v %v", out, test.out)
}
}
}
func TestWalkPath(t *testing.T) {
r := New[interface{}]()
keys := []string{
"foo",
"foo/bar",
"foo/bar/baz",
"foo/baz/bar",
"foo/zip/zap",
"zipzap",
}
for _, k := range keys {
r.Insert(k, nil)
}
if r.Len() != len(keys) {
t.Fatalf("bad len: %v %v", r.Len(), len(keys))
}
type exp struct {
inp string
out []string
}
cases := []exp{
{
"f",
[]string{},
},
{
"foo",
[]string{"foo"},
},
{
"foo/",
[]string{"foo"},
},
{
"foo/ba",
[]string{"foo"},
},
{
"foo/bar",
[]string{"foo", "foo/bar"},
},
{
"foo/bar/baz",
[]string{"foo", "foo/bar", "foo/bar/baz"},
},
{
"foo/bar/bazoo",
[]string{"foo", "foo/bar", "foo/bar/baz"},
},
{
"z",
[]string{},
},
}
for _, test := range cases {
out := []string{}
fn := func(s string, v interface{}) bool {
out = append(out, s)
return false
}
r.WalkPath(test.inp, fn)
sort.Strings(out)
sort.Strings(test.out)
if !reflect.DeepEqual(out, test.out) {
t.Fatalf("mis-match: %v %v", out, test.out)
}
}
}
func TestWalkDelete(t *testing.T) {
r := New[interface{}]()
r.Insert("init0/0", nil)
r.Insert("init0/1", nil)
r.Insert("init0/2", nil)
r.Insert("init0/3", nil)
r.Insert("init1/0", nil)
r.Insert("init1/1", nil)
r.Insert("init1/2", nil)
r.Insert("init1/3", nil)
r.Insert("init2", nil)
deleteFn := func(s string, v interface{}) bool {
r.Delete(s)
return false
}
r.WalkPrefix("init1", deleteFn)
for _, s := range []string{"init0/0", "init0/1", "init0/2", "init0/3", "init2"} {
if _, ok := r.Get(s); !ok {
t.Fatalf("expecting to still find %q", s)
}
}
if n := r.Len(); n != 5 {
t.Fatalf("expected to find exactly 5 nodes, instead found %d: %v", n, r.ToMap())
}
r.Walk(deleteFn)
if n := r.Len(); n != 0 {
t.Fatalf("expected to find exactly 0 nodes, instead found %d: %v", n, r.ToMap())
}
}
// generateUUID is used to generate a random UUID
func generateUUID() string {
buf := make([]byte, 16)
if _, err := crand.Read(buf); err != nil {
panic(fmt.Errorf("failed to read random bytes: %v", err))
}
return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
buf[0:4],
buf[4:6],
buf[6:8],
buf[8:10],
buf[10:16])
}
func BenchmarkInsert(b *testing.B) {
r := New[bool]()
for i := 0; i < 10000; i++ {
r.Insert(fmt.Sprintf("init%d", i), true)
}
b.ResetTimer()
for n := 0; n < b.N; n++ {
_, updated := r.Insert(strconv.Itoa(n), true)
if updated {
b.Fatal("bad")
}
}
}