@@ -143,143 +143,80 @@ func TestPickWeightedRandomPicker(t *testing.T) {
143
143
pod4 := & types.PodMetrics {Pod : & backend.Pod {NamespacedName : k8stypes.NamespacedName {Name : "pod4" }}}
144
144
pod5 := & types.PodMetrics {Pod : & backend.Pod {NamespacedName : k8stypes.NamespacedName {Name : "pod5" }}}
145
145
146
+ // A-Res algorithm uses U^(1/w) transformation which introduces statistical variance
147
+ // beyond simple proportional sampling. Generous tolerance is required to prevent
148
+ // flaky tests in CI environments, especially for multi-tier weights.
146
149
tests := []struct {
147
150
name string
148
151
input []* types.ScoredPod
149
152
maxPods int // maxNumOfEndpoints for this test
150
153
iterations int
151
154
expectedProbabilities map [string ]float64 // pod name -> expected probability
152
155
tolerancePercent float64 // acceptable deviation percentage
153
- expectExactLength int // expected exact length per iteration (0 means skip this check)
154
156
}{
155
- {
156
- name : "All pods requested - basic functionality" ,
157
- input : []* types.ScoredPod {
158
- {Pod : pod1 , Score : 10 },
159
- {Pod : pod2 , Score : 20 },
160
- {Pod : pod3 , Score : 30 },
161
- },
162
- maxPods : 5 , // Request more than available
163
- iterations : 10 ,
164
- expectExactLength : 3 , // All 3 pods returned (maxPods >= totalPods, so use all)
165
- },
166
157
{
167
158
name : "High weight dominance test" ,
168
159
input : []* types.ScoredPod {
169
- {Pod : pod1 , Score : 10 }, // 10/100 = 10%
170
- {Pod : pod2 , Score : 90 }, // 90/100 = 90%
160
+ {Pod : pod1 , Score : 10 }, // Lower weight
161
+ {Pod : pod2 , Score : 90 }, // Higher weight (should dominate)
171
162
},
172
- maxPods : 1 , // special case: maxEndpoint=1, totalPods=2 → topN=2 (uses all)
163
+ maxPods : 1 ,
173
164
iterations : 2000 ,
174
165
expectedProbabilities : map [string ]float64 {
175
166
"pod1" : 0.10 ,
176
167
"pod2" : 0.90 ,
177
168
},
178
- tolerancePercent : 20.0 , // ±20% (more tolerant for statistical variance)
169
+ tolerancePercent : 20.0 ,
179
170
},
180
171
{
181
- name : "Equal weights test with topN filtering " ,
172
+ name : "Equal weights test - A-Res uniform distribution " ,
182
173
input : []* types.ScoredPod {
183
- {Pod : pod1 , Score : 50 }, // Equal scores (will be in top 2 after sorting )
184
- {Pod : pod2 , Score : 50 }, // Equal scores (will be in top 2 after sorting)
185
- {Pod : pod3 , Score : 50 }, // Equal scores (will be filtered out)
174
+ {Pod : pod1 , Score : 100 }, // Equal weights (higher values for better numerical precision )
175
+ {Pod : pod2 , Score : 100 }, // Equal weights should yield uniform distribution
176
+ {Pod : pod3 , Score : 100 }, // Equal weights in A-Res
186
177
},
187
- maxPods : 1 , // ratio = 1/3 = 0.333 < 0.34, so topN = 1+1 = 2
178
+ maxPods : 1 ,
188
179
iterations : 1500 ,
189
180
expectedProbabilities : map [string ]float64 {
190
- "pod1" : 0.333 , // Each pod has equal chance: 2/3 * 1/2 ≈ 0.333
191
- "pod2" : 0.333 , // Each pod has equal chance: 2/3 * 1/2 ≈ 0.333
192
- "pod3" : 0.333 , // Each pod has equal chance: 2/3 * 1/2 ≈ 0.333
193
- },
194
- tolerancePercent : 15.0 , // ±15%
195
- },
196
- {
197
- name : "Progressive weight test" ,
198
- input : []* types.ScoredPod {
199
- {Pod : pod1 , Score : 20 }, // 20/60 = 33.3%
200
- {Pod : pod2 , Score : 40 }, // 40/60 = 66.7%
201
- },
202
- maxPods : 1 , // special case: maxEndpoint=1, totalPods=2 → topN=2 (uses all)
203
- iterations : 1200 ,
204
- expectedProbabilities : map [string ]float64 {
205
- "pod1" : 0.333 ,
206
- "pod2" : 0.667 ,
181
+ "pod1" : 0.333 , // Equal weights should yield uniform distribution
182
+ "pod2" : 0.333 , // A-Res maintains equal probability for equal weights
183
+ "pod3" : 0.333 , // Each pod has theoretically equal chance
207
184
},
208
- tolerancePercent : 20.0 , // ±20% (more tolerant for statistical variance)
185
+ tolerancePercent : 20.0 ,
209
186
},
210
187
{
211
- name : "Zero weight exclusion test" ,
188
+ name : "Zero weight exclusion test - A-Res edge case " ,
212
189
input : []* types.ScoredPod {
213
- {Pod : pod1 , Score : 30 }, // 30/30 = 100%
214
- {Pod : pod2 , Score : 0 }, // 0/30 = 0%
190
+ {Pod : pod1 , Score : 30 }, // Normal weight, should be selected
191
+ {Pod : pod2 , Score : 0 }, // Zero weight, never selected in A-Res
215
192
},
216
193
maxPods : 1 ,
217
194
iterations : 500 ,
218
195
expectedProbabilities : map [string ]float64 {
219
- "pod1" : 1.0 ,
220
- "pod2" : 0.0 ,
196
+ "pod1" : 1.0 , // Only pod with positive weight
197
+ "pod2" : 0.0 , // Zero weight pods are filtered out
221
198
},
222
- tolerancePercent : 5.0 , // ±5%
199
+ tolerancePercent : 5.0 , // ±5% tolerance (should be exact for zero weights)
223
200
},
224
201
{
225
- name : "Top N filtering - only top 2 pods with threshold " ,
202
+ name : "Multi-tier weighted test - A-Res complex distribution " ,
226
203
input : []* types.ScoredPod {
227
- {Pod : pod1 , Score : 100 }, // Highest probability
228
- {Pod : pod2 , Score : 90 }, // Second highest probability
229
- {Pod : pod3 , Score : 50 }, // Should be filtered out
230
- {Pod : pod4 , Score : 30 }, // Should be filtered out
231
- {Pod : pod5 , Score : 10 }, // Should be filtered out
204
+ {Pod : pod1 , Score : 100 }, // Highest weight
205
+ {Pod : pod2 , Score : 90 }, // High weight
206
+ {Pod : pod3 , Score : 50 }, // Medium weight
207
+ {Pod : pod4 , Score : 30 }, // Low weight
208
+ {Pod : pod5 , Score : 20 }, // Lowest weight
232
209
},
233
- maxPods : 1 , // ratio = 1/5 = 0.2 < 0.34, so topN = 1+1 = 2
234
- iterations : 1000 ,
235
- expectedProbabilities : map [string ]float64 {
236
- "pod1" : 0.526 , // 100/(100+90) ≈ 52.6%
237
- "pod2" : 0.474 , // 90/(100+90) ≈ 47.4%
238
- "pod3" : 0.0 , // Should never be selected
239
- "pod4" : 0.0 , // Should never be selected
240
- "pod5" : 0.0 , // Should never be selected
241
- },
242
- tolerancePercent : 15.0 ,
243
- },
244
- {
245
- name : "Boundary test: ratio exactly at threshold (1/3 ≈ 0.33)" ,
246
- input : []* types.ScoredPod {
247
- {Pod : pod1 , Score : 100 }, // Highest probability
248
- {Pod : pod2 , Score : 90 }, // Second highest probability
249
- {Pod : pod3 , Score : 50 }, // Should be included (ratio < 0.34)
250
- },
251
- maxPods : 1 , // ratio = 1/3 = 0.333 < 0.34, so topN = 1+1 = 2
210
+ maxPods : 1 ,
252
211
iterations : 1000 ,
253
212
expectedProbabilities : map [string ]float64 {
254
- "pod1" : 0.526 , // 100/(100+90) ≈ 52.6%
255
- "pod2" : 0.474 , // 90/(100+90) ≈ 47.4%
256
- "pod3" : 0.0 , // Should never be selected due to filtering
213
+ "pod1" : 0.345 , // Highest weight gets highest probability
214
+ "pod2" : 0.310 , // High weight gets high probability
215
+ "pod3" : 0.172 , // Medium weight gets medium probability
216
+ "pod4" : 0.103 , // Low weight gets low probability
217
+ "pod5" : 0.069 , // Lowest weight gets lowest probability
257
218
},
258
- tolerancePercent : 15.0 ,
259
- },
260
- {
261
- name : "No filtering: ratio above threshold (2/3 ≈ 0.67)" ,
262
- input : []* types.ScoredPod {
263
- {Pod : pod1 , Score : 100 }, // Highest probability
264
- {Pod : pod2 , Score : 90 }, // Second highest probability
265
- {Pod : pod3 , Score : 50 }, // Should be included (no filtering)
266
- },
267
- maxPods : 2 , // ratio = 2/3 = 0.667 ≥ 0.34, so no topN filtering
268
- iterations : 10 ,
269
- expectExactLength : 2 , // Should return exactly 2 pods per iteration
270
- },
271
- {
272
- name : "Edge case: maxPods > filtered count, should use all pods" ,
273
- input : []* types.ScoredPod {
274
- {Pod : pod1 , Score : 100 },
275
- {Pod : pod2 , Score : 90 },
276
- {Pod : pod3 , Score : 50 },
277
- {Pod : pod4 , Score : 30 },
278
- {Pod : pod5 , Score : 10 },
279
- },
280
- maxPods : 5 , // maxPods >= totalPods, so use all 5 pods
281
- iterations : 10 ,
282
- expectExactLength : 5 , // All 5 pods should be returned
219
+ tolerancePercent : 25.0 ,
283
220
},
284
221
}
285
222
@@ -288,45 +225,25 @@ func TestPickWeightedRandomPicker(t *testing.T) {
288
225
picker := NewWeightedRandomPicker (test .maxPods )
289
226
selectionCounts := make (map [string ]int )
290
227
291
- // Initialize counters for simple pod names
228
+ // Initialize selection counters for each pod
292
229
for _ , pod := range test .input {
293
230
podName := pod .GetPod ().NamespacedName .Name
294
231
selectionCounts [podName ] = 0
295
232
}
296
233
297
- // Run multiple iterations to gather statistics
298
- var totalLength int
234
+ // Run multiple iterations to gather statistical data
299
235
for i := 0 ; i < test .iterations ; i ++ {
300
- // Create fresh copy of input for each iteration
301
- inputCopy := make ([]* types.ScoredPod , len (test .input ))
302
- for j , pod := range test .input {
303
- inputCopy [j ] = & types.ScoredPod {Pod : pod .Pod , Score : pod .Score }
304
- }
236
+ result := picker .Pick (context .Background (), types .NewCycleState (), test .input )
305
237
306
- result := picker .Pick (context .Background (), types .NewCycleState (), inputCopy )
307
- totalLength += len (result .TargetPods )
308
-
309
- // Count selections for probability distribution (when selecting 1 pod)
310
- if test .maxPods == 1 && len (result .TargetPods ) > 0 {
238
+ // Count selections for probability analysis
239
+ if len (result .TargetPods ) > 0 {
311
240
selectedPodName := result .TargetPods [0 ].GetPod ().NamespacedName .Name
312
241
selectionCounts [selectedPodName ]++
313
242
}
314
243
}
315
244
316
- // Check exact length if specified
317
- if test .expectExactLength > 0 {
318
- expectedTotalLength := test .expectExactLength * test .iterations
319
- if totalLength != expectedTotalLength {
320
- t .Errorf ("Expected total length %d (avg %.1f), got %d (avg %.1f)" ,
321
- expectedTotalLength , float64 (test .expectExactLength ),
322
- totalLength , float64 (totalLength )/ float64 (test .iterations ))
323
- } else {
324
- t .Logf ("Exact length test passed: %d pods selected per iteration ✓" , test .expectExactLength )
325
- }
326
- }
327
-
328
- // Verify probability distribution (only for single pod selection tests)
329
- if test .expectedProbabilities != nil && test .maxPods == 1 {
245
+ // Verify probability distribution
246
+ if test .expectedProbabilities != nil {
330
247
for podName , expectedProb := range test .expectedProbabilities {
331
248
actualCount := selectionCounts [podName ]
332
249
actualProb := float64 (actualCount ) / float64 (test .iterations )
0 commit comments