diff --git a/internal/accounts/weighted_test.go b/internal/accounts/weighted_test.go index 34a5067..6040e08 100644 --- a/internal/accounts/weighted_test.go +++ b/internal/accounts/weighted_test.go @@ -42,15 +42,39 @@ func TestWeightedDistributesAndRespectsWeight(t *testing.T) { } } +// TestWeightedZeroWeightTreatedAsOne 用确定性断言(非抽样分布)验证 Weight<=0 记 1。 +// +// 此前版本靠 400 个 "N"(i%10, i%26 联动生成)key 抽样统计两账户 +// 是否都出现过 —— 但 fnv32a 对这批同构 key 恰好全部落在偶数哈希(见 check.go 复现), +// 导致抽样必然只命中一侧、测试恒红,与实现无关(已用随机 key / 完整 10x26 网格核实 +// 实现对普通 key 分布均匀,证据见修复报告)。改为: +// 1. 单候选 weight=0 必须可选中(不因权重被当成 0 而排除)。 +// 2. 两候选 weight=0+0 → 各按文档规则记 1、总权重 2,target=fnv32a(key)%2, +// 按候选顺序累加桶(a1 占 [0,1)、a2 占 [1,2))。用两个已知奇偶性的字面量 key +// (提前用同一 fnv32a 规则手算)分别断言恰好落在 a1 / a2,不依赖分布采样。 func TestWeightedZeroWeightTreatedAsOne(t *testing.T) { w := accounts.NewWeighted() - cs := []config.AccountConfig{{AccountID: "a1", Weight: 0}, {AccountID: "a2", Weight: 0}} - counts := map[string]int{} - for i := 0; i < 400; i++ { - got, _ := w.Pick("fake|global", cs, accounts.PickHint{OutTradeNo: "N" + string(rune('0'+i%10)) + string(rune('a'+i%26))}) - counts[got.AccountID]++ + + single := []config.AccountConfig{{AccountID: "solo", Weight: 0}} + got, err := w.Pick("fake|global", single, accounts.PickHint{OutTradeNo: "ANY-KEY"}) + if err != nil || got.AccountID != "solo" { + t.Fatalf("单候选 weight=0 应可选中, got %v err %v", got, err) } - if counts["a1"] == 0 || counts["a2"] == 0 { - t.Fatalf("weight=0 应当 1 处理、两账户都可选, got %+v", counts) + + cs := []config.AccountConfig{{AccountID: "a1", Weight: 0}, {AccountID: "a2", Weight: 0}} + // fnv32a("PAY-0002") = 3179067314,偶数 → target=0,落 a1 的 [0,1) 桶。 + // fnv32a("PAY-0001") = 3162289695,奇数 → target=1,落 a2 的 [1,2) 桶。 + cases := []struct { + key string + want string + }{ + {"PAY-0002", "a1"}, + {"PAY-0001", "a2"}, + } + for _, tc := range cases { + got, err := w.Pick("fake|global", cs, accounts.PickHint{OutTradeNo: tc.key}) + if err != nil || got.AccountID != tc.want { + t.Fatalf("key=%s: weight=0 应按 1 处理走桶, want %s got %v err %v", tc.key, tc.want, got, err) + } } }