Files
team-api/relaykit/dispatch/hrw_test.go
T

98 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dispatch
import (
"fmt"
"math"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func scoredSet(weights map[int64]float64) []ScoredChannel {
out := make([]ScoredChannel, 0, len(weights))
for id, w := range weights {
out = append(out, ScoredChannel{Channel: Channel{ID: id}, Weight: w})
}
return out
}
func TestPickHRW_StickyStability(t *testing.T) {
cands := scoredSet(map[int64]float64{1: 5, 2: 3, 3: 2})
first := PickHRW(cands, "session-abc")
require.NotNil(t, first)
for range 10_000 {
got := PickHRW(cands, "session-abc")
require.Equal(t, first.ID, got.ID, "相同 sessionKey + 相同候选必须永远同一结果")
}
}
func TestPickHRW_CandidateOrderIndependent(t *testing.T) {
a := []ScoredChannel{
{Channel: Channel{ID: 1}, Weight: 5},
{Channel: Channel{ID: 2}, Weight: 3},
{Channel: Channel{ID: 3}, Weight: 2},
}
b := []ScoredChannel{a[2], a[0], a[1]} // 打乱顺序
for i := range 1000 {
key := fmt.Sprintf("sess-%d", i)
pa, pb := PickHRW(a, key), PickHRW(b, key)
require.Equal(t, pa.ID, pb.ID, "候选顺序不得影响选择结果")
}
}
func TestPickHRW_WeightProportionalDistribution(t *testing.T) {
// 权重 5:3:2 → 期望占比 50%/30%/20%,10 万采样误差 < 2 个百分点
cands := scoredSet(map[int64]float64{1: 5, 2: 3, 3: 2})
const n = 100_000
counts := map[int64]int{}
for i := range n {
p := PickHRW(cands, fmt.Sprintf("session-%d", i))
counts[p.ID]++
}
expect := map[int64]float64{1: 0.5, 2: 0.3, 3: 0.2}
for id, want := range expect {
got := float64(counts[id]) / n
assert.InDelta(t, want, got, 0.02, "渠道 %d 占比 got=%v want=%v", id, got, want)
}
}
func TestPickHRW_NonPositiveWeightExcluded(t *testing.T) {
cands := scoredSet(map[int64]float64{1: 0, 2: -1, 3: 2})
for i := range 1000 {
p := PickHRW(cands, fmt.Sprintf("s-%d", i))
require.Equal(t, int64(3), p.ID)
}
assert.Nil(t, PickHRW(scoredSet(map[int64]float64{1: 0}), "s"), "无正权重候选返回 nil")
assert.Nil(t, PickHRW(nil, "s"))
}
func TestPickHRW_Monotonicity(t *testing.T) {
// 增加一个渠道只迁移有限比例会话:不含新渠道时选中 A 的会话,
// 加入新渠道后要么仍选 A,要么选新渠道,绝不迁到其它旧渠道。
oldSet := scoredSet(map[int64]float64{1: 5, 2: 3, 3: 2})
newSet := scoredSet(map[int64]float64{1: 5, 2: 3, 3: 2, 4: 3})
moved := 0
const n = 20_000
for i := range n {
key := fmt.Sprintf("mono-%d", i)
before, after := PickHRW(oldSet, key), PickHRW(newSet, key)
if before.ID != after.ID {
require.Equal(t, int64(4), after.ID, "迁移只能流向新渠道")
moved++
}
}
// 新渠道权重占比 3/13 ≈ 23%,迁移比例应接近
assert.InDelta(t, 3.0/13.0, float64(moved)/n, 0.02)
}
func TestUniformHash_Range(t *testing.T) {
for i := range 10_000 {
u := uniformHash(fmt.Sprintf("k-%d", i), int64(i))
require.Greater(t, u, 0.0)
require.Less(t, u, 1.0)
require.False(t, math.IsInf(-math.Log(u), 1), "不得出现 log(0)")
}
}