11import random
2+ from collections import Counter
23from typing import Dict , List , Tuple
34
4- from vllm_router .routers .routing_logic import RoundRobinRouter
5+ import pytest
6+
7+ from vllm_router .routers .routing_logic import RoundRobinRouter , cleanup_routing_logic
8+
9+
10+ @pytest .fixture (autouse = True )
11+ def cleanup_router ():
12+ cleanup_routing_logic ()
13+ yield
14+ cleanup_routing_logic ()
515
616
717class EndpointInfo :
@@ -21,7 +31,7 @@ def __init__(self, headers: Dict[str, str]):
2131
2232class EngineStats :
2333 def __init__ (self ):
24- return
34+ pass
2535
2636
2737def generate_request_args (
@@ -47,6 +57,11 @@ def generate_request(request_type="http") -> Request:
4757 return Request ({"type" : request_type })
4858
4959
60+ def assert_even_distribution (route_counts : Counter ):
61+ counts = route_counts .values ()
62+ assert max (counts ) - min (counts ) <= 1
63+
64+
5065def test_roundrobin_logic (
5166 dynamic_discoveries : int = 10 , max_endpoints : int = 1000 , max_requests : int = 10000
5267):
@@ -55,20 +70,66 @@ def test_roundrobin_logic(
5570 """
5671 router = RoundRobinRouter ()
5772
58- def _fixed_router_check (num_endpoints : int , num_requests : int ) -> bool :
59- # Make num_requests requests to the router and check even output distribution
73+ def assert_router_stays_balanced (num_endpoints : int , num_requests : int ):
6074 endpoints , engine_stats , request_stats = generate_request_args (num_endpoints )
61- output_distribution = {}
62- for request_idx in range (num_requests ):
75+ route_counts = Counter ()
76+ for _ in range (num_requests ):
6377 request = generate_request ()
6478 url = router .route_request (endpoints , engine_stats , request_stats , request )
65- output_distribution [url ] = output_distribution . get ( url , 0 ) + 1
66- request_counts = output_distribution . values ()
67- return max ( request_counts ) - min ( request_counts ) <= 1
79+ route_counts [url ] += 1
80+
81+ assert_even_distribution ( route_counts )
6882
6983 for _ in range (dynamic_discoveries ):
7084 num_endpoints = random .randint (1 , max_endpoints )
7185 num_requests = random .randint (1 , max_requests )
72- # Perform router check
73- res = _fixed_router_check (num_endpoints , num_requests )
74- assert res
86+ assert_router_stays_balanced (num_endpoints , num_requests )
87+
88+
89+ def test_roundrobin_keeps_state_per_endpoint_set ():
90+ router = RoundRobinRouter ()
91+ request = generate_request ()
92+ engine_stats = {}
93+ request_stats = {}
94+ endpoints_a = [EndpointInfo (url = "a0" ), EndpointInfo (url = "a1" )]
95+ endpoints_b = [EndpointInfo (url = "b0" ), EndpointInfo (url = "b1" )]
96+ route_counts_a = Counter ()
97+ route_counts_b = Counter ()
98+
99+ for _ in range (100 ):
100+ url_a = router .route_request (endpoints_a , engine_stats , request_stats , request )
101+ route_counts_a [url_a ] += 1
102+ url_b = router .route_request (endpoints_b , engine_stats , request_stats , request )
103+ route_counts_b [url_b ] += 1
104+
105+ assert_even_distribution (route_counts_a )
106+ assert_even_distribution (route_counts_b )
107+
108+
109+ def test_roundrobin_keeps_state_when_endpoint_order_changes ():
110+ router = RoundRobinRouter ()
111+ request = generate_request ()
112+ engine_stats = {}
113+ request_stats = {}
114+ endpoints_a = [EndpointInfo (url = "a0" ), EndpointInfo (url = "a1" )]
115+ endpoints_a_reordered = [EndpointInfo (url = "a1" ), EndpointInfo (url = "a0" )]
116+
117+ assert (
118+ router .route_request (endpoints_a , engine_stats , request_stats , request ) == "a0"
119+ )
120+ assert (
121+ router .route_request (
122+ endpoints_a_reordered ,
123+ engine_stats ,
124+ request_stats ,
125+ request ,
126+ )
127+ == "a1"
128+ )
129+
130+
131+ def test_roundrobin_rejects_empty_endpoint_list ():
132+ router = RoundRobinRouter ()
133+
134+ with pytest .raises (ValueError , match = "at least one endpoint" ):
135+ router .route_request ([], {}, {}, generate_request ())
0 commit comments