1#![allow(clippy::missing_panics_doc)]
3use std::{fmt, io, marker::PhantomData, net, thread, time};
4
5use ntex_io::{Io, IoConfig};
6use ntex_net::tcp_connect;
7use ntex_rt::System;
8use ntex_service::{IntoService, Service, cfg::SharedCfg, state::State};
9use socket2::{Domain, SockAddr, Socket, Type};
10use uuid::Uuid;
11
12use super::{Server, ServerBuilder, state::StateFactory};
13
14pub struct TestServerBuilder<F, Sf, St, I> {
16 id: Uuid,
17 factory: F,
18 config: SharedCfg,
19 client_config: SharedCfg,
20 state: Option<Box<dyn StateFactory<St> + Send>>,
21 _t: PhantomData<(Sf, St, I)>,
22}
23
24impl<F, Sf, St, I> fmt::Debug for TestServerBuilder<F, Sf, St, I> {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.debug_struct("TestServerBuilder")
27 .field("id", &self.id)
28 .field("config", &self.config)
29 .field("client_config", &self.client_config)
30 .finish()
31 }
32}
33
34impl<F, S, St, I> TestServerBuilder<F, S, St, I>
35where
36 F: AsyncFn() -> I + Send + Clone + 'static,
37 I: IntoService<S, St, Io> + 'static,
38 S: Service<St, Io> + 'static,
39 St: State<St, Io> + Clone + Default + 'static,
40{
41 #[must_use]
42 pub fn new(factory: F) -> Self {
44 Self {
45 factory,
46 id: Uuid::now_v7(),
47 config: SharedCfg::new("TEST-SERVER").into(),
48 client_config: SharedCfg::new("TEST-CLIENT").into(),
49 state: None,
50 _t: PhantomData,
51 }
52 }
53
54 #[must_use]
55 pub fn config<T: Into<SharedCfg>>(mut self, cfg: T) -> Self {
57 self.config = cfg.into();
58 self
59 }
60
61 #[must_use]
62 pub fn client_config<T: Into<SharedCfg>>(mut self, cfg: T) -> Self {
64 self.client_config = cfg.into();
65 self
66 }
67
68 pub fn start(self) -> TestServer {
70 log::debug!("Starting test server {:?}", self.id);
71 let config = self.config;
72 let _state = self.state;
73 let factory = self.factory;
74 let cfg = System::current().config();
75 let name = System::current().name().to_string();
76
77 let (tx, rx) = oneshot::channel();
78 thread::spawn(move || {
80 let sys = System::with_config(&name, cfg);
81 let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
82 let local_addr = tcp.local_addr().unwrap();
83
84 sys.run(move || {
85 let server = ServerBuilder::<St>::new(async || Ok(St::default()))
86 .listen("test", tcp, config, async move |_| factory().await)?
87 .workers(1)
88 .disable_signals()
89 .enable_affinity()
90 .run();
91
92 ntex_rt::spawn(async move {
93 tx.send((System::current(), local_addr, server))
94 .expect("Failed to send Server to TestServer");
95 });
96
97 Ok(())
98 })
99 });
100 let (system, addr, server) = rx.recv().unwrap();
101 thread::sleep(time::Duration::from_millis(25));
102
103 TestServer {
104 addr,
105 server,
106 system,
107 id: self.id,
108 cfg: self.client_config,
109 }
110 }
111}
112
113pub fn test_server<F, S>(factory: F) -> TestServer
143where
144 F: AsyncFn() -> S + Send + Clone + 'static,
145 S: Service<(), Io> + 'static,
146{
147 TestServerBuilder::new(factory).start()
148}
149
150pub fn build_test_server<F>(factory: F) -> TestServer
152where
153 F: AsyncFnOnce(ServerBuilder) -> ServerBuilder + Send + 'static,
154{
155 let cfg = System::current().config();
156 let name = System::current().name().to_string();
157
158 let id = Uuid::now_v7();
159 log::debug!("Starting {name:?} server {id:?}");
160
161 let (tx, rx) = oneshot::channel();
162
163 thread::spawn(move || {
165 let sys = System::with_config(&name, cfg);
166
167 sys.block_on(async move {
168 let server = factory(super::build())
169 .await
170 .workers(1)
171 .disable_signals()
172 .run();
173 tx.send((System::current(), server.clone()))
174 .expect("Failed to send Server to TestServer");
175 let _ = server.await;
176 });
177 });
178 let (system, server) = rx.recv().unwrap();
179 thread::sleep(time::Duration::from_millis(25));
180
181 TestServer {
182 id,
183 system,
184 server,
185 addr: "127.0.0.1:0".parse().unwrap(),
186 cfg: SharedCfg::new("TEST-CLIENT").add(IoConfig::new()).into(),
187 }
188}
189
190#[derive(Debug)]
191pub struct TestServer {
193 id: Uuid,
194 addr: net::SocketAddr,
195 system: System,
196 server: Server,
197 cfg: SharedCfg,
198}
199
200impl TestServer {
201 pub fn addr(&self) -> net::SocketAddr {
203 self.addr
204 }
205
206 #[must_use]
207 pub fn set_addr(mut self, addr: net::SocketAddr) -> Self {
208 self.addr = addr;
209 self
210 }
211
212 pub fn config(&self) -> SharedCfg {
214 self.cfg.clone()
215 }
216
217 pub async fn connect(&self) -> io::Result<Io> {
219 tcp_connect(self.addr, self.cfg.clone()).await
220 }
221
222 pub fn stop(&self) {
224 drop(self.server.stop(true));
225 }
226
227 pub fn unused_addr() -> net::SocketAddr {
229 let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
230 let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
231 socket.set_reuse_address(true).unwrap();
232 socket.bind(&SockAddr::from(addr)).unwrap();
233 let tcp = net::TcpListener::from(socket);
234 tcp.local_addr().unwrap()
235 }
236
237 pub fn server(&self) -> Server {
239 self.server.clone()
240 }
241}
242
243impl Drop for TestServer {
244 fn drop(&mut self) {
245 log::debug!("Stopping test server {:?}", self.id);
246 drop(self.server.stop(false));
247 thread::sleep(time::Duration::from_millis(75));
248 self.system.stop();
249 thread::sleep(time::Duration::from_millis(25));
250 }
251}