mirror of
https://github.com/coredns/coredns.git
synced 2025-10-27 08:14:18 -04:00
test(plugin): add tests for pkg/rand (#7320)
Add test suite covering thread-safe random number generator with tests for: - Constructor with various seed values (positive, zero, negative) - Deterministic behavior verification with same seeds - Permutation generation and validation - Concurrent access safety with multiple goroutines - Mixed operations under concurrent load Also clarify package documentation to explicitly state this is for load balancing and server selection, not cryptographic use. The math/rand usage is intentional for performance in non-security contexts like upstream server selection and DNS record shuffling. Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
This commit is contained in:
@@ -1,4 +1,7 @@
|
||||
// Package rand is used for concurrency safe random number generator.
|
||||
// This package provides a thread-safe wrapper around math/rand for use in
|
||||
// load balancing and server selection. It is NOT suitable for cryptographic
|
||||
// purposes and should not be used for security-sensitive operations.
|
||||
package rand
|
||||
|
||||
import (
|
||||
|
||||
206
plugin/pkg/rand/rand_test.go
Normal file
206
plugin/pkg/rand/rand_test.go
Normal file
@@ -0,0 +1,206 @@
|
||||
package rand
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
seed int64
|
||||
}{
|
||||
{
|
||||
name: "positive seed",
|
||||
seed: 12345,
|
||||
},
|
||||
{
|
||||
name: "zero seed",
|
||||
seed: 0,
|
||||
},
|
||||
{
|
||||
name: "negative seed",
|
||||
seed: -12345,
|
||||
},
|
||||
{
|
||||
name: "current time seed",
|
||||
seed: time.Now().UnixNano(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := New(test.seed)
|
||||
if r.r == nil {
|
||||
t.Error("internal rand.Rand is nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntDeterministic(t *testing.T) {
|
||||
seed := int64(42)
|
||||
|
||||
// Create two generators with the same seed
|
||||
r1 := New(seed)
|
||||
r2 := New(seed)
|
||||
|
||||
// They should produce the same sequence
|
||||
for i := 0; i < 10; i++ {
|
||||
val1 := r1.Int()
|
||||
val2 := r2.Int()
|
||||
if val1 != val2 {
|
||||
t.Errorf("generators with same seed produced different values at iteration %d: %d != %d", i, val1, val2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPerm(t *testing.T) {
|
||||
r := New(12345)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
n int
|
||||
}{
|
||||
{
|
||||
name: "empty permutation",
|
||||
n: 0,
|
||||
},
|
||||
{
|
||||
name: "single element",
|
||||
n: 1,
|
||||
},
|
||||
{
|
||||
name: "small permutation",
|
||||
n: 5,
|
||||
},
|
||||
{
|
||||
name: "larger permutation",
|
||||
n: 20,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
perm := r.Perm(test.n)
|
||||
if len(perm) != test.n {
|
||||
t.Errorf("Perm(%d) returned slice of length %d", test.n, len(perm))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPermDeterministic(t *testing.T) {
|
||||
seed := int64(42)
|
||||
n := 10
|
||||
|
||||
// Create two generators with the same seed
|
||||
r1 := New(seed)
|
||||
r2 := New(seed)
|
||||
|
||||
perm1 := r1.Perm(n)
|
||||
perm2 := r2.Perm(n)
|
||||
|
||||
// They should produce the same permutation
|
||||
if len(perm1) != len(perm2) {
|
||||
t.Errorf("permutations have different lengths: %d != %d", len(perm1), len(perm2))
|
||||
}
|
||||
|
||||
for i := 0; i < len(perm1) && i < len(perm2); i++ {
|
||||
if perm1[i] != perm2[i] {
|
||||
t.Errorf("permutations differ at index %d: %d != %d", i, perm1[i], perm2[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentAccess(t *testing.T) {
|
||||
r := New(12345)
|
||||
numGoroutines := 10
|
||||
numOperations := 100
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, numGoroutines*numOperations)
|
||||
|
||||
// Test concurrent Int() calls
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < numOperations; j++ {
|
||||
val := r.Int()
|
||||
if val < 0 {
|
||||
errors <- nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Test concurrent Perm() calls
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < numOperations; j++ {
|
||||
perm := r.Perm(5)
|
||||
if len(perm) != 5 {
|
||||
errors <- nil
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
errorCount := 0
|
||||
for range errors {
|
||||
errorCount++
|
||||
}
|
||||
|
||||
if errorCount > 0 {
|
||||
t.Errorf("concurrent access resulted in %d errors", errorCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcurrentMixedOperations(t *testing.T) {
|
||||
r := New(time.Now().UnixNano())
|
||||
numGoroutines := 5
|
||||
numOperations := 50
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, numGoroutines*numOperations)
|
||||
|
||||
// Mix of Int() and Perm() operations running concurrently
|
||||
for i := 0; i < numGoroutines; i++ {
|
||||
wg.Add(1)
|
||||
go func(id int) {
|
||||
defer wg.Done()
|
||||
for j := 0; j < numOperations; j++ {
|
||||
if j%2 == 0 {
|
||||
val := r.Int()
|
||||
if val < 0 {
|
||||
errors <- nil
|
||||
}
|
||||
} else {
|
||||
perm := r.Perm(3)
|
||||
if len(perm) != 3 {
|
||||
errors <- nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
|
||||
errorCount := 0
|
||||
for range errors {
|
||||
errorCount++
|
||||
}
|
||||
|
||||
if errorCount > 0 {
|
||||
t.Errorf("concurrent mixed operations resulted in %d errors", errorCount)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user