1#![allow(clippy::missing_panics_doc)]
3use std::{fmt, io, marker::PhantomData, net, rc::Rc, thread, time};
4
5use ntex_io::{Io, IoConfig};
6use ntex_net::tcp_connect;
7use ntex_rt::System;
8use ntex_service::{IntoService, Service, cfg::SharedCfg};
9use socket2::{Domain, SockAddr, Socket, Type};
10use uuid::Uuid;
11
12use super::{NoConfig, Server, ServerAppConfig, ServerBuilder};
13
14pub struct TestServerBuilder<Cfg, F, Sf, I> {
16 id: Uuid,
17 cfg: Cfg,
18 factory: F,
19 config: SharedCfg,
20 client_config: SharedCfg,
21 _t: PhantomData<(Sf, I)>,
22}
23
24impl<Cfg, F, Sf, I> fmt::Debug for TestServerBuilder<Cfg, F, Sf, 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, I> TestServerBuilder<NoConfig, F, S, I>
35where
36 F: AsyncFn() -> I + Send + Clone + 'static,
37 I: IntoService<S, (), Io> + 'static,
38 S: Service<(), Io> + 'static,
39{
40 #[must_use]
41 pub fn new(factory: F) -> Self {
43 Self {
44 factory,
45 id: Uuid::now_v7(),
46 cfg: NoConfig,
47 config: SharedCfg::new("TEST-SERVER").into(),
48 client_config: SharedCfg::new("TEST-CLIENT").into(),
49 _t: PhantomData,
50 }
51 }
52}
53
54impl<Cfg, F, S, I> TestServerBuilder<Cfg, F, S, I>
55where
56 F: AsyncFn() -> I + Send + Clone + 'static,
57 I: IntoService<S, Cfg::State, Io> + 'static,
58 S: Service<Cfg::State, Io> + 'static,
59 Cfg: ServerAppConfig + 'static,
60{
61 #[must_use]
62 pub fn with(cfg: Cfg, factory: F) -> Self {
64 Self {
65 cfg,
66 factory,
67 id: Uuid::now_v7(),
68 config: SharedCfg::new("TEST-SERVER").into(),
69 client_config: SharedCfg::new("TEST-CLIENT").into(),
70 _t: PhantomData,
71 }
72 }
73
74 #[must_use]
75 pub fn config<T: Into<SharedCfg>>(mut self, cfg: T) -> Self {
77 self.config = cfg.into();
78 self
79 }
80
81 #[must_use]
82 pub fn client_config<T: Into<SharedCfg>>(mut self, cfg: T) -> Self {
84 self.client_config = cfg.into();
85 self
86 }
87
88 pub fn start(self) -> TestServer {
90 log::debug!("Starting test server {:?}", self.id);
91 let cfg = self.cfg;
92 let config = self.config;
93 let factory = self.factory;
94 let sys_cfg = System::current().config();
95 let name = System::current().name().to_string();
96
97 let (tx, rx) = oneshot::channel();
98 thread::spawn(move || {
100 let sys = System::with_config(&name, sys_cfg);
101 let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
102 let local_addr = tcp.local_addr().unwrap();
103
104 sys.run(move || {
105 let server = ServerBuilder::new(cfg)
106 .listen("test", tcp, config, async move |_| factory().await)?
107 .workers(1)
108 .disable_signals()
109 .enable_affinity()
110 .run();
111
112 ntex_rt::spawn(async move {
113 tx.send((System::current(), local_addr, server))
114 .expect("Failed to send Server to TestServer");
115 });
116
117 Ok(())
118 })
119 });
120 let (system, addr, server) = rx.recv().unwrap();
121 thread::sleep(time::Duration::from_millis(25));
122
123 TestServer {
124 addr,
125 inner: Rc::new(TestServerInner {
126 server,
127 system,
128 id: self.id,
129 cfg: self.client_config,
130 }),
131 }
132 }
133}
134
135pub fn test_server<F, S>(factory: F) -> TestServer
165where
166 F: AsyncFn() -> S + Send + Clone + 'static,
167 S: Service<(), Io> + 'static,
168{
169 TestServerBuilder::new(factory).start()
170}
171
172pub fn build_test_server<Cfg, F>(cfg: Cfg, factory: F) -> TestServer
174where
175 Cfg: ServerAppConfig,
176 F: AsyncFnOnce(ServerBuilder<Cfg>) -> ServerBuilder<Cfg> + Send + 'static,
177{
178 let sys = System::current().config();
179 let name = System::current().name().to_string();
180
181 let id = Uuid::now_v7();
182 log::debug!("Starting {name:?} server {id:?}");
183
184 let (tx, rx) = oneshot::channel();
185
186 thread::spawn(move || {
188 let sys = System::with_config(&name, sys);
189
190 sys.block_on(async move {
191 let server = factory(ServerBuilder::new(cfg))
192 .await
193 .workers(1)
194 .disable_signals()
195 .run();
196 tx.send((System::current(), server.clone()))
197 .expect("Failed to send Server to TestServer");
198 let _ = server.await;
199 });
200 });
201 let (system, server) = rx.recv().unwrap();
202 thread::sleep(time::Duration::from_millis(25));
203
204 TestServer {
205 addr: "127.0.0.1:0".parse().unwrap(),
206 inner: Rc::new(TestServerInner {
207 id,
208 system,
209 server,
210 cfg: SharedCfg::new("TEST-CLIENT").add(IoConfig::new()).into(),
211 }),
212 }
213}
214
215#[derive(Clone, Debug)]
216pub struct TestServer {
218 addr: net::SocketAddr,
219 inner: Rc<TestServerInner>,
220}
221
222#[derive(Debug)]
223struct TestServerInner {
224 id: Uuid,
225 system: System,
226 server: Server,
227 cfg: SharedCfg,
228}
229
230impl TestServer {
231 pub fn addr(&self) -> net::SocketAddr {
233 self.addr
234 }
235
236 #[must_use]
237 pub fn set_addr(mut self, addr: net::SocketAddr) -> Self {
238 self.addr = addr;
239 self
240 }
241
242 pub fn config(&self) -> SharedCfg {
244 self.inner.cfg.clone()
245 }
246
247 pub async fn connect(&self) -> io::Result<Io> {
249 tcp_connect(self.addr, self.inner.cfg.clone()).await
250 }
251
252 pub fn stop(&self) {
254 drop(self.inner.server.stop(true));
255 }
256
257 pub fn unused_addr() -> net::SocketAddr {
259 let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
260 let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
261 socket.set_reuse_address(true).unwrap();
262 socket.bind(&SockAddr::from(addr)).unwrap();
263 let tcp = net::TcpListener::from(socket);
264 tcp.local_addr().unwrap()
265 }
266
267 pub fn server(&self) -> Server {
269 self.inner.server.clone()
270 }
271}
272
273impl Drop for TestServerInner {
274 fn drop(&mut self) {
275 log::debug!("Stopping test server (dropped) {:?}", self.id);
276 drop(self.server.stop(false));
277 thread::sleep(time::Duration::from_millis(75));
278 self.system.stop();
279 thread::sleep(time::Duration::from_millis(25));
280 }
281}