package lottery import ( "testing" ) func TestWeightedPicker_EmptyPool(t *testing.T) { p := NewWeightedPicker(42) if _, err := p.Pick(nil); err != ErrEmptyPool { t.Fatalf("expected ErrEmptyPool for nil pool, got %v", err) } if _, err := p.Pick([]Prize{{Weight: 0}, {Weight: 0}}); err != ErrEmptyPool { t.Fatalf("expected ErrEmptyPool when all weights are 0, got %v", err) } } func TestWeightedPicker_SkipsZeroWeight(t *testing.T) { p := NewWeightedPicker(1) pool := []Prize{ {Id: 1, Weight: 0}, // 不参与 {Id: 2, Weight: 100}, // 独占权重 {Id: 3, Weight: 0}, // 不参与 } for i := 0; i < 200; i++ { idx, err := p.Pick(pool) if err != nil { t.Fatalf("Pick err: %v", err) } if idx != 1 { t.Fatalf("expected idx 1 (only positive weight), got %d", idx) } } } func TestWeightedPicker_DistributionCloseToWeights(t *testing.T) { p := NewWeightedPicker(2026) pool := []Prize{ {Id: 10, Weight: 10}, // 10/60 ≈ 16.67% {Id: 20, Weight: 20}, // 20/60 ≈ 33.33% {Id: 30, Weight: 30}, // 30/60 = 50% } const trials = 60000 counts := make(map[int]int) for i := 0; i < trials; i++ { idx, err := p.Pick(pool) if err != nil { t.Fatalf("Pick err: %v", err) } counts[idx]++ } // 允许 ±2% 偏差 expect := []float64{10.0 / 60, 20.0 / 60, 30.0 / 60} for i, e := range expect { got := float64(counts[i]) / float64(trials) if got < e-0.02 || got > e+0.02 { t.Fatalf("prize %d: expected %.4f (±0.02), got %.4f", i, e, got) } } } func TestWeightedPicker_DeterministicWithFixedSeed(t *testing.T) { pool := []Prize{ {Id: 1, Weight: 1}, {Id: 2, Weight: 1}, {Id: 3, Weight: 1}, } a := NewWeightedPicker(7) b := NewWeightedPicker(7) for i := 0; i < 20; i++ { ai, _ := a.Pick(pool) bi, _ := b.Pick(pool) if ai != bi { t.Fatalf("iter %d: seed 7 diverged: a=%d b=%d", i, ai, bi) } } }