@@ -260,6 +260,269 @@ func TestRoundRobin(t *testing.T) {
260260 })
261261}
262262
263+ func TestLeastConnectionsPolicy (t * testing.T ) {
264+ t .Run ("empty trackers" , func (t * testing.T ) {
265+ cb , pt := leastConnectionsPolicy (context .Background (), []* podTracker {})
266+ defer cb ()
267+ if pt != nil {
268+ t .Fatal ("Expected nil tracker for empty input" )
269+ }
270+ })
271+
272+ t .Run ("single tracker" , func (t * testing.T ) {
273+ podTrackers := makeTrackers (1 , 1 )
274+ cb , pt := leastConnectionsPolicy (context .Background (), podTrackers )
275+ defer cb ()
276+ if pt == nil {
277+ t .Fatal ("Expected non-nil tracker" )
278+ }
279+ if got , want := pt .dest , podTrackers [0 ].dest ; got != want {
280+ t .Errorf ("pt.dest = %s, want: %s" , got , want )
281+ }
282+ })
283+
284+ t .Run ("multiple trackers with different loads" , func (t * testing.T ) {
285+ podTrackers := makeTrackers (3 , 2 )
286+ // Simulate different loads
287+ podTrackers [0 ].weight .Store (5 )
288+ podTrackers [1 ].weight .Store (2 )
289+ podTrackers [2 ].weight .Store (8 )
290+
291+ cb , pt := leastConnectionsPolicy (context .Background (), podTrackers )
292+ defer cb ()
293+ if pt == nil {
294+ t .Fatal ("Expected non-nil tracker" )
295+ }
296+ // Should pick the one with lowest weight (index 1)
297+ if got , want := pt .dest , podTrackers [1 ].dest ; got != want {
298+ t .Errorf ("pt.dest = %s, want: %s (should pick lowest load)" , got , want )
299+ }
300+ })
301+
302+ t .Run ("nil trackers in list" , func (t * testing.T ) {
303+ podTrackers := []* podTracker {
304+ nil ,
305+ {
306+ dest : "tracker-1" ,
307+ b : queue .NewBreaker (queue.BreakerParams {
308+ QueueDepth : 1 ,
309+ MaxConcurrency : 1 ,
310+ InitialCapacity : 1 ,
311+ }),
312+ },
313+ nil ,
314+ }
315+ cb , pt := leastConnectionsPolicy (context .Background (), podTrackers )
316+ defer cb ()
317+ if pt == nil {
318+ t .Fatal ("Expected non-nil tracker" )
319+ }
320+ if got , want := pt .dest , "tracker-1" ; got != want {
321+ t .Errorf ("pt.dest = %s, want: %s" , got , want )
322+ }
323+ })
324+
325+ t .Run ("all nil trackers" , func (t * testing.T ) {
326+ podTrackers := []* podTracker {nil , nil , nil }
327+ cb , pt := leastConnectionsPolicy (context .Background (), podTrackers )
328+ defer cb ()
329+ if pt != nil {
330+ t .Fatal ("Expected nil tracker when all trackers are nil" )
331+ }
332+ })
333+
334+ t .Run ("negative weight handling" , func (t * testing.T ) {
335+ podTrackers := makeTrackers (2 , 1 )
336+ podTrackers [0 ].weight .Store (- 5 )
337+ podTrackers [1 ].weight .Store (3 )
338+
339+ cb , pt := leastConnectionsPolicy (context .Background (), podTrackers )
340+ defer cb ()
341+ if pt == nil {
342+ t .Fatal ("Expected non-nil tracker" )
343+ }
344+ // Negative weight should be treated as 0, so should pick first tracker
345+ if got , want := pt .dest , podTrackers [0 ].dest ; got != want {
346+ t .Errorf ("pt.dest = %s, want: %s (negative weight should be treated as 0)" , got , want )
347+ }
348+ })
349+ }
350+
351+ func TestRandomLBPolicyWithNilTrackers (t * testing.T ) {
352+ t .Run ("empty trackers" , func (t * testing.T ) {
353+ cb , pt := randomLBPolicy (context .Background (), []* podTracker {})
354+ defer cb ()
355+ if pt != nil {
356+ t .Fatal ("Expected nil tracker for empty input" )
357+ }
358+ })
359+
360+ t .Run ("all nil trackers" , func (t * testing.T ) {
361+ podTrackers := []* podTracker {nil , nil , nil }
362+ cb , pt := randomLBPolicy (context .Background (), podTrackers )
363+ defer cb ()
364+ if pt != nil {
365+ t .Fatal ("Expected nil tracker when all trackers are nil" )
366+ }
367+ })
368+
369+ t .Run ("mixed nil and valid trackers" , func (t * testing.T ) {
370+ podTrackers := makeTrackers (3 , 0 )
371+ // Set middle one to nil
372+ podTrackers [1 ] = nil
373+
374+ // Run multiple times to ensure we don't get nil
375+ for i := 0 ; i < 10 ; i ++ {
376+ cb , pt := randomLBPolicy (context .Background (), podTrackers )
377+ defer cb ()
378+ if pt == nil {
379+ t .Fatal ("Should not return nil when valid trackers exist" )
380+ }
381+ if pt .dest != podTrackers [0 ].dest && pt .dest != podTrackers [2 ].dest {
382+ t .Fatal ("Should return one of the valid trackers" )
383+ }
384+ }
385+ })
386+ }
387+
388+ func TestRandomChoice2PolicyWithNilTrackers (t * testing.T ) {
389+ t .Run ("single nil tracker" , func (t * testing.T ) {
390+ podTrackers := []* podTracker {nil }
391+ cb , pt := randomChoice2Policy (context .Background (), podTrackers )
392+ defer cb ()
393+ if pt != nil {
394+ t .Fatal ("Expected nil tracker when single tracker is nil" )
395+ }
396+ })
397+
398+ t .Run ("all nil trackers" , func (t * testing.T ) {
399+ podTrackers := []* podTracker {nil , nil , nil }
400+ cb , pt := randomChoice2Policy (context .Background (), podTrackers )
401+ defer cb ()
402+ if pt != nil {
403+ t .Fatal ("Expected nil tracker when all trackers are nil" )
404+ }
405+ })
406+
407+ t .Run ("mixed nil and valid trackers" , func (t * testing.T ) {
408+ podTrackers := makeTrackers (4 , 0 )
409+ // Set some to nil
410+ podTrackers [1 ] = nil
411+ podTrackers [3 ] = nil
412+
413+ // Run multiple times to check behavior
414+ foundNonNil := false
415+ for i := 0 ; i < 20 ; i ++ {
416+ cb , pt := randomChoice2Policy (context .Background (), podTrackers )
417+ defer cb ()
418+ if pt != nil {
419+ foundNonNil = true
420+ if pt .dest != podTrackers [0 ].dest && pt .dest != podTrackers [2 ].dest {
421+ t .Fatal ("Should return one of the valid trackers" )
422+ }
423+ }
424+ }
425+ if ! foundNonNil {
426+ t .Fatal ("Should find at least one non-nil tracker in multiple attempts" )
427+ }
428+ })
429+
430+ t .Run ("mostly nil trackers" , func (t * testing.T ) {
431+ // Create a large array with mostly nils
432+ podTrackers := make ([]* podTracker , 10 )
433+ // Create a proper tracker with initialized fields
434+ validTracker := & podTracker {
435+ dest : "valid-tracker" ,
436+ }
437+ // Initialize the weight field properly
438+ validTracker .weight .Store (0 )
439+ podTrackers [0 ] = validTracker
440+
441+ // Run multiple times - should eventually find the valid tracker
442+ foundValid := false
443+ for i := 0 ; i < 100 ; i ++ {
444+ cb , pt := randomChoice2Policy (context .Background (), podTrackers )
445+ if cb != nil {
446+ defer cb ()
447+ }
448+ if pt != nil && pt .dest == "valid-tracker" {
449+ foundValid = true
450+ break
451+ }
452+ }
453+ if ! foundValid {
454+ t .Fatal ("Should eventually find the valid tracker" )
455+ }
456+ })
457+ }
458+
459+ func TestFirstAvailableWithNilTrackers (t * testing.T ) {
460+ t .Run ("nil trackers in list" , func (t * testing.T ) {
461+ podTrackers := []* podTracker {
462+ nil ,
463+ {
464+ dest : "tracker-1" ,
465+ b : queue .NewBreaker (queue.BreakerParams {
466+ QueueDepth : 1 ,
467+ MaxConcurrency : 1 ,
468+ InitialCapacity : 1 ,
469+ }),
470+ },
471+ nil ,
472+ }
473+ cb , pt := firstAvailableLBPolicy (context .Background (), podTrackers )
474+ defer cb ()
475+ if pt == nil {
476+ t .Fatal ("Expected non-nil tracker" )
477+ }
478+ if got , want := pt .dest , "tracker-1" ; got != want {
479+ t .Errorf ("pt.dest = %s, want: %s" , got , want )
480+ }
481+ })
482+
483+ t .Run ("all nil trackers" , func (t * testing.T ) {
484+ podTrackers := []* podTracker {nil , nil , nil }
485+ cb , pt := firstAvailableLBPolicy (context .Background (), podTrackers )
486+ defer cb ()
487+ if pt != nil {
488+ t .Fatal ("Expected nil tracker when all trackers are nil" )
489+ }
490+ })
491+ }
492+
493+ func TestRoundRobinWithNilTrackers (t * testing.T ) {
494+ t .Run ("nil trackers in list" , func (t * testing.T ) {
495+ rrp := newRoundRobinPolicy ()
496+ podTrackers := makeTrackers (3 , 1 )
497+ // Set middle tracker to nil
498+ podTrackers [1 ] = nil
499+
500+ cb , pt := rrp (context .Background (), podTrackers )
501+ t .Cleanup (cb )
502+ if got , want := pt , podTrackers [0 ]; got != want {
503+ t .Fatalf ("Tracker = %v, want: %v" , got , want )
504+ }
505+
506+ // Should skip nil tracker and go to next valid one
507+ cb , pt = rrp (context .Background (), podTrackers )
508+ t .Cleanup (cb )
509+ if got , want := pt , podTrackers [2 ]; got != want {
510+ t .Fatalf ("Tracker = %v, want: %v (should skip nil tracker)" , got , want )
511+ }
512+ })
513+
514+ t .Run ("all nil trackers" , func (t * testing.T ) {
515+ rrp := newRoundRobinPolicy ()
516+ podTrackers := []* podTracker {nil , nil , nil }
517+
518+ cb , pt := rrp (context .Background (), podTrackers )
519+ defer cb ()
520+ if pt != nil {
521+ t .Fatal ("Expected nil tracker when all trackers are nil" )
522+ }
523+ })
524+ }
525+
263526func BenchmarkPolicy (b * testing.B ) {
264527 for _ , test := range []struct {
265528 name string
@@ -276,6 +539,9 @@ func BenchmarkPolicy(b *testing.B) {
276539 }, {
277540 name : "round-robin" ,
278541 policy : newRoundRobinPolicy (),
542+ }, {
543+ name : "least-connections" ,
544+ policy : leastConnectionsPolicy ,
279545 }} {
280546 for _ , n := range []int {1 , 2 , 3 , 10 , 100 } {
281547 b .Run (fmt .Sprintf ("%s-%d-trackers-sequential" , test .name , n ), func (b * testing.B ) {
0 commit comments