FILE / ScuroNeko/mtg

mtglib/internal/doppel/stats_test.go

Исходный файл и его история в репозитории.
FILE 1c0e8472478861af3daaa2bd42ea90cd2dd4043c
Files
mtg/mtglib/internal/doppel/stats_test.go
T
2026-03-12 19:07:10 +01:00

195 lines
4.3 KiB
Go

package doppel
import (
"math"
"math/rand/v2"
"testing"
"time"
"github.com/stretchr/testify/suite"
)
type StatsTestSuite struct {
suite.Suite
}
func (suite *StatsTestSuite) GenWeibull(k, lambda float64, n int, seed uint64) []time.Duration {
rng := rand.New(rand.NewPCG(seed, 0))
samples := make([]time.Duration, n)
for i := range samples {
u := 1.0 - rng.Float64()
ms := lambda * math.Pow(-math.Log(u), 1.0/k)
d := time.Duration(ms * float64(time.Millisecond))
if d < time.Microsecond {
time.Sleep(time.Microsecond)
d = time.Microsecond
}
samples[i] = d
}
return samples
}
func (suite *StatsTestSuite) TestNewStatsRecoverParameters() {
knownK := 1.5
knownLambda := 100.0
samples := suite.GenWeibull(knownK, knownLambda, 5000, 42)
stats := NewStats(samples)
suite.InDelta(knownK, stats.k, 0.1)
suite.InDelta(knownLambda, stats.lambda, 5.0)
}
func (suite *StatsTestSuite) TestNewStatsExponentialCase() {
// When k=1, Weibull reduces to exponential distribution.
knownK := 1.0
knownLambda := 50.0
samples := suite.GenWeibull(knownK, knownLambda, 5000, 123)
stats := NewStats(samples)
suite.InDelta(knownK, stats.k, 0.1)
suite.InDelta(knownLambda, stats.lambda, 5.0)
}
func (suite *StatsTestSuite) TestNewStatsSmallK() {
// k < 1 produces a heavy-tailed distribution typical for network delays.
// Lambda must be large enough so samples stay above microsecond precision
// after time.Duration round-trip.
knownK := 0.6
knownLambda := 100.0
samples := suite.GenWeibull(knownK, knownLambda, 10000, 99)
stats := NewStats(samples)
suite.InDelta(knownK, stats.k, 0.05)
suite.InDelta(knownLambda, stats.lambda, 5.0)
}
func (suite *StatsTestSuite) TestNewStatsLargeK() {
// k > 1: light tail, concentrated around the mode.
knownK := 5.0
knownLambda := 200.0
samples := suite.GenWeibull(knownK, knownLambda, 5000, 77)
stats := NewStats(samples)
suite.InDelta(knownK, stats.k, 0.3)
suite.InDelta(knownLambda, stats.lambda, 5.0)
}
func (suite *StatsTestSuite) TestDelayNonNegative() {
stats := &Stats{
k: 1.5,
lambda: 100.0,
}
for range 200 {
dur := stats.Delay()
suite.GreaterOrEqual(dur, time.Duration(0))
}
}
func (suite *StatsTestSuite) TestDelayDistributionMean() {
// Weibull mean = λ · Γ(1 + 1/k)
k := 2.0
lambda := 50.0
stats := &Stats{k: k, lambda: lambda}
n := 50000
sum := 0.0
for range n {
dur := stats.Delay()
sum += float64(dur) / float64(time.Millisecond)
}
sampleMean := sum / float64(n)
expectedMean := lambda * math.Gamma(1.0+1.0/k)
suite.InDelta(expectedMean, sampleMean, expectedMean*0.05)
}
func (suite *StatsTestSuite) TestNewStatsRoundTrip() {
// Estimate parameters from data, then verify that Delay samples
// from the fitted distribution have approximately the same mean.
knownK := 1.2
knownLambda := 80.0
samples := suite.GenWeibull(knownK, knownLambda, 5000, 555)
stats := NewStats(samples)
n := 50000
sum := 0.0
for range n {
dur := stats.Delay()
sum += float64(dur) / float64(time.Millisecond)
}
sampleMean := sum / float64(n)
expectedMean := knownLambda * math.Gamma(1.0+1.0/knownK)
suite.InDelta(expectedMean, sampleMean, expectedMean*0.05)
}
func (suite *StatsTestSuite) TestSizeStartPhase() {
stats := &Stats{k: 1.0, lambda: 1.0}
for range TLSCounterAccelAfter {
size := stats.Size()
suite.Equal(TLSRecordSizeStart, size)
}
}
func (suite *StatsTestSuite) TestSizeAccelPhase() {
stats := &Stats{k: 1.0, lambda: 1.0}
for range TLSCounterAccelAfter {
stats.Size()
}
for range TLSCounterMaxAfter - TLSCounterAccelAfter {
size := stats.Size()
suite.Equal(TLSRecordSizeAccel, size)
}
}
func (suite *StatsTestSuite) TestSizeMaxPhase() {
stats := &Stats{k: 1.0, lambda: 1.0}
for range TLSCounterMaxAfter {
stats.Size()
}
for range 20 {
size := stats.Size()
suite.Equal(TLSRecordSizeMax, size)
}
}
func (suite *StatsTestSuite) TestSizeResetsAfterInactivity() {
stats := &Stats{k: 1.0, lambda: 1.0}
// Advance past start phase.
for range TLSCounterMaxAfter {
stats.Size()
}
suite.Equal(TLSRecordSizeMax, stats.Size())
// Simulate inactivity by backdating sizeLastRequested.
stats.sizeLastRequested = time.Now().Add(-TLSRecordSizeResetAfter - time.Millisecond)
suite.Equal(TLSRecordSizeStart, stats.Size())
}
func TestStats(t *testing.T) {
t.Parallel()
suite.Run(t, &StatsTestSuite{})
}