11//! Mock server
22use std:: {
33 cell:: OnceCell ,
4- net:: { SocketAddr , TcpStream } ,
4+ net:: { IpAddr , Ipv4Addr , SocketAddr , TcpStream } ,
55 sync:: { Arc , RwLock , RwLockReadGuard , RwLockWriteGuard } ,
66 time:: Duration ,
77} ;
@@ -32,6 +32,7 @@ pub struct MockServer {
3232 addr : OnceCell < SocketAddr > ,
3333 base_url : OnceCell < Url > ,
3434 state : Arc < MockServerState > ,
35+ config : MockServerConfig ,
3536}
3637
3738impl MockServer {
@@ -43,6 +44,7 @@ impl MockServer {
4344 addr : OnceCell :: new ( ) ,
4445 base_url : OnceCell :: new ( ) ,
4546 state : Arc :: new ( MockServerState :: default ( ) ) ,
47+ config : MockServerConfig :: default ( ) ,
4648 }
4749 }
4850
@@ -58,15 +60,38 @@ impl MockServer {
5860 self
5961 }
6062
63+ /// Sets the server configuration.
64+ pub fn with_config ( mut self , config : MockServerConfig ) -> Self {
65+ self . config = config;
66+ self
67+ }
68+
6169 pub async fn start ( & self ) -> Result < ( ) , Error > {
6270 if self . addr ( ) . is_some ( ) {
6371 return Err ( Error :: ServerError ( "already running" . into ( ) ) ) ;
6472 }
65- let port = find_available_port ( ) . unwrap ( ) ;
66- let addr = SocketAddr :: from ( ( [ 0 , 0 , 0 , 0 ] , port) ) ;
73+
74+ let mut counter = 0 ;
75+ let mut rng = rand:: rng ( ) ;
76+
77+ let listener = loop {
78+ let port: u16 =
79+ rng. random_range ( self . config . port_range_start ..self . config . port_range_end ) ;
80+ let addr = SocketAddr :: from ( ( self . config . listen_addr , port) ) ;
81+ if let Ok ( listener) = TcpListener :: bind ( & addr) . await {
82+ break listener;
83+ }
84+
85+ if counter == self . config . bind_max_retries {
86+ return Err ( Error :: ServerError ( "server failed to bind to port" . into ( ) ) ) ;
87+ }
88+ counter += 1 ;
89+ } ;
90+
91+ let addr = listener. local_addr ( ) ?;
92+ info ! ( "started {} [{}] server on {addr}" , self . name( ) , & self . kind) ;
6793 let base_url = Url :: parse ( & format ! ( "http://{}" , & addr) ) . unwrap ( ) ;
68- info ! ( "starting {} [{}] server on {addr}" , self . name( ) , & self . kind) ;
69- let listener = TcpListener :: bind ( & addr) . await ?;
94+
7095 match self . kind {
7196 ServerKind :: Http => {
7297 let service = HttpMockService :: new ( self . state . clone ( ) ) ;
@@ -78,10 +103,15 @@ impl MockServer {
78103 }
79104 } ;
80105 // Wait for server to become ready
81- for _ in 0 ..30 {
82- if TcpStream :: connect_timeout ( & addr, Duration :: from_millis ( 10 ) ) . is_ok ( ) {
106+ let mut counter = 0 ;
107+ loop {
108+ if TcpStream :: connect_timeout ( & addr, self . config . ready_connect_timeout ) . is_ok ( ) {
83109 break ;
84110 }
111+ if counter == self . config . ready_connect_max_retries {
112+ return Err ( Error :: ServerError ( "server failed to become ready" . into ( ) ) ) ;
113+ }
114+ counter += 1 ;
85115 tokio:: time:: sleep ( Duration :: from_millis ( 10 ) ) . await ;
86116 }
87117 info ! ( "{} server ready" , self . name( ) ) ;
@@ -227,16 +257,31 @@ where
227257 Ok ( ( ) )
228258}
229259
230- fn find_available_port ( ) -> Option < u16 > {
231- let mut rng = rand:: rng ( ) ;
232- loop {
233- let port: u16 = rng. random_range ( 40000 ..60000 ) ;
234- if port_is_available ( port) {
235- return Some ( port) ;
236- }
260+ #[ derive( Debug ) ]
261+ pub struct MockServerConfig {
262+ pub listen_addr : IpAddr ,
263+ pub port_range_start : u16 ,
264+ pub port_range_end : u16 ,
265+ pub bind_max_retries : usize ,
266+ pub ready_connect_max_retries : usize ,
267+ pub ready_connect_timeout : Duration ,
268+ }
269+
270+ impl MockServerConfig {
271+ pub fn new ( ) -> Self {
272+ Self :: default ( )
237273 }
238274}
239275
240- fn port_is_available ( port : u16 ) -> bool {
241- std:: net:: TcpListener :: bind ( ( "0.0.0.0" , port) ) . is_ok ( )
276+ impl Default for MockServerConfig {
277+ fn default ( ) -> Self {
278+ Self {
279+ listen_addr : IpAddr :: V4 ( Ipv4Addr :: new ( 0 , 0 , 0 , 0 ) ) ,
280+ port_range_start : 10000 ,
281+ port_range_end : 30000 ,
282+ bind_max_retries : 10 ,
283+ ready_connect_max_retries : 30 ,
284+ ready_connect_timeout : Duration :: from_millis ( 10 ) ,
285+ }
286+ }
242287}
0 commit comments