Files

231 lines
3.9 KiB
Go

// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package sampler
import (
"math"
"math/rand"
"strconv"
"testing"
"github.com/stretchr/testify/require"
"github.com/thepudds/fzgen/fuzzer"
"gonum.org/v1/gonum/mathext/prng"
)
type testSource struct {
onInvalid func()
nums []uint64
}
func (s *testSource) Seed(uint64) {
s.onInvalid()
}
func (s *testSource) Uint64() uint64 {
if len(s.nums) == 0 {
s.onInvalid()
return 0
}
num := s.nums[0]
s.nums = s.nums[1:]
return num
}
type testSTDSource struct {
onInvalid func()
nums []uint64
}
func (s *testSTDSource) Seed(int64) {
s.onInvalid()
}
func (s *testSTDSource) Int63() int64 {
return int64(s.Uint64() & (1<<63 - 1))
}
func (s *testSTDSource) Uint64() uint64 {
if len(s.nums) == 0 {
s.onInvalid()
return 0
}
num := s.nums[0]
s.nums = s.nums[1:]
return num
}
func TestRNG(t *testing.T) {
tests := []struct {
maximum uint64
nums []uint64
expected uint64
}{
{
maximum: math.MaxUint64,
nums: []uint64{
0x01,
},
expected: 0x01,
},
{
maximum: math.MaxUint64,
nums: []uint64{
0x0102030405060708,
},
expected: 0x0102030405060708,
},
{
maximum: math.MaxUint64,
nums: []uint64{
0xF102030405060708,
},
expected: 0xF102030405060708,
},
{
maximum: math.MaxInt64,
nums: []uint64{
0x01,
},
expected: 0x01,
},
{
maximum: math.MaxInt64,
nums: []uint64{
0x0102030405060708,
},
expected: 0x0102030405060708,
},
{
maximum: math.MaxInt64,
nums: []uint64{
0x8102030405060708,
},
expected: 0x0102030405060708,
},
{
maximum: 15,
nums: []uint64{
0x810203040506071a,
},
expected: 0x0a,
},
{
maximum: math.MaxInt64 + 1,
nums: []uint64{
math.MaxInt64 + 1,
},
expected: math.MaxInt64 + 1,
},
{
maximum: math.MaxInt64 + 1,
nums: []uint64{
math.MaxInt64 + 2,
0,
},
expected: 0,
},
{
maximum: math.MaxInt64 + 1,
nums: []uint64{
math.MaxInt64 + 2,
0x0102030405060708,
},
expected: 0x0102030405060708,
},
{
maximum: 2,
nums: []uint64{
math.MaxInt64 - 2,
},
expected: 0x02,
},
{
maximum: 2,
nums: []uint64{
math.MaxInt64 - 1,
0x01,
},
expected: 0x01,
},
}
for i, test := range tests {
t.Run(strconv.Itoa(i), func(t *testing.T) {
require := require.New(t)
source := &testSource{
onInvalid: t.FailNow,
nums: test.nums,
}
r := &rng{rng: source}
val := r.Uint64Inclusive(test.maximum)
require.Equal(test.expected, val)
require.Empty(source.nums)
if test.maximum >= math.MaxInt64 {
return
}
stdSource := &testSTDSource{
onInvalid: t.FailNow,
nums: test.nums,
}
mathRNG := rand.New(stdSource) //#nosec G404
stdVal := mathRNG.Int63n(int64(test.maximum + 1))
require.Equal(test.expected, uint64(stdVal))
require.Empty(source.nums)
})
}
}
func FuzzRNG(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
require := require.New(t)
var (
maximum uint64
sourceNums []uint64
)
fz := fuzzer.NewFuzzer(data)
fz.Fill(&maximum, &sourceNums)
if maximum >= math.MaxInt64 {
t.SkipNow()
}
source := &testSource{
onInvalid: func() { /* Invalid input - continue test */ },
nums: sourceNums,
}
r := &rng{rng: source}
val := r.Uint64Inclusive(maximum)
stdSource := &testSTDSource{
onInvalid: func() { /* Invalid input - continue test */ },
nums: sourceNums,
}
mathRNG := rand.New(stdSource) //#nosec G404
stdVal := mathRNG.Int63n(int64(maximum + 1))
require.Equal(val, uint64(stdVal))
require.Len(stdSource.nums, len(source.nums))
})
}
func BenchmarkSeed32(b *testing.B) {
source := prng.NewMT19937()
b.ResetTimer()
for i := 0; i < b.N; i++ {
source.Seed(0)
}
}
func BenchmarkSeed64(b *testing.B) {
source := prng.NewMT19937_64()
b.ResetTimer()
for i := 0; i < b.N; i++ {
source.Seed(0)
}
}