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> {
20 id: Uuid,
21 cfg: Cfg,
22 factory: F,
23 config: SharedCfg,
24 client_config: SharedCfg,
25 _t: PhantomData<(Sf, I)>,
26}
27
28impl<Cfg, F, Sf, I> fmt::Debug for TestServerBuilder<Cfg, F, Sf, I> {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 f.debug_struct("TestServerBuilder")
31 .field("id", &self.id)
32 .field("config", &self.config)
33 .field("client_config", &self.client_config)
34 .finish()
35 }
36}
37
38impl<F, S, I> TestServerBuilder<NoConfig, F, S, I>
39where
40 F: AsyncFn(&()) -> I + Send + Clone + 'static,
41 I: IntoService<S, (), Io> + 'static,
42 S: Service<(), Io> + 'static,
43{
44 #[must_use]
45 pub fn new(factory: F) -> Self {
49 Self {
50 factory,
51 id: Uuid::now_v7(),
52 cfg: NoConfig,
53 config: SharedCfg::new("TEST-SERVER").into(),
54 client_config: SharedCfg::new("TEST-CLIENT").into(),
55 _t: PhantomData,
56 }
57 }
58}
59
60impl<Cfg, F, S, I> TestServerBuilder<Cfg, F, S, I>
61where
62 F: AsyncFn(&Cfg::State) -> I + Send + Clone + 'static,
63 I: IntoService<S, Cfg::State, Io> + 'static,
64 S: Service<Cfg::State, Io> + 'static,
65 Cfg: ServerAppConfig + 'static,
66{
67 #[must_use]
68 pub fn with(cfg: Cfg, factory: F) -> Self {
73 Self {
74 cfg,
75 factory,
76 id: Uuid::now_v7(),
77 config: SharedCfg::new("TEST-SERVER").into(),
78 client_config: SharedCfg::new("TEST-CLIENT").into(),
79 _t: PhantomData,
80 }
81 }
82
83 #[must_use]
84 pub fn config<T: Into<SharedCfg>>(mut self, cfg: T) -> Self {
86 self.config = cfg.into();
87 self
88 }
89
90 #[must_use]
91 pub fn client_config<T: Into<SharedCfg>>(mut self, cfg: T) -> Self {
93 self.client_config = cfg.into();
94 self
95 }
96
97 pub fn start(self) -> TestServer {
99 log::debug!("Starting test server {:?}", self.id);
100 let cfg = self.cfg;
101 let config = self.config;
102 let factory = self.factory;
103 let sys_cfg = System::current().config();
104 let name = System::current().name().to_string();
105
106 let (tx, rx) = oneshot::channel();
107 thread::spawn(move || {
109 let sys = System::with_config(&name, sys_cfg);
110 let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap();
111 let local_addr = tcp.local_addr().unwrap();
112
113 sys.run(move || {
114 let server = ServerBuilder::new(cfg)
115 .listen("test", tcp, config, factory)?
116 .workers(1)
117 .disable_signals()
118 .run();
119
120 ntex_rt::spawn_detached(async move {
121 tx.send((System::current(), local_addr, server))
122 .expect("Failed to send Server to TestServer");
123 });
124
125 Ok(())
126 })
127 });
128 let (system, addr, server) = rx.recv().unwrap();
129 thread::sleep(time::Duration::from_millis(25));
130
131 TestServer {
132 addr,
133 inner: Rc::new(TestServerInner {
134 server,
135 system,
136 id: self.id,
137 cfg: self.client_config,
138 }),
139 }
140 }
141}
142
143pub fn test_server<F, S>(factory: F) -> TestServer
181where
182 F: AsyncFn(&()) -> S + Send + Clone + 'static,
183 S: Service<(), Io> + 'static,
184{
185 TestServerBuilder::new(factory).start()
186}
187
188pub fn build_test_server<Cfg, F>(cfg: Cfg, factory: F) -> TestServer
194where
195 Cfg: ServerAppConfig,
196 F: AsyncFnOnce(ServerBuilder<Cfg>) -> ServerBuilder<Cfg> + Send + 'static,
197{
198 let sys = System::current().config();
199 let name = System::current().name().to_string();
200
201 let id = Uuid::now_v7();
202 log::debug!("Starting {name:?} server {id:?}");
203
204 let (tx, rx) = oneshot::channel();
205
206 thread::spawn(move || {
208 let sys = System::with_config(&name, sys);
209
210 sys.block_on(async move {
211 let server = factory(ServerBuilder::new(cfg))
212 .await
213 .workers(1)
214 .disable_signals()
215 .run();
216 tx.send((System::current(), server.clone()))
217 .expect("Failed to send Server to TestServer");
218 let _ = server.await;
219 });
220 });
221 let (system, server) = rx.recv().unwrap();
222 thread::sleep(time::Duration::from_millis(25));
223
224 TestServer {
225 addr: "127.0.0.1:0".parse().unwrap(),
226 inner: Rc::new(TestServerInner {
227 id,
228 system,
229 server,
230 cfg: SharedCfg::new("TEST-CLIENT").add(IoConfig::new()).into(),
231 }),
232 }
233}
234
235#[derive(Clone, Debug)]
236pub struct TestServer {
241 addr: net::SocketAddr,
242 inner: Rc<TestServerInner>,
243}
244
245#[derive(Debug)]
246struct TestServerInner {
247 id: Uuid,
248 system: System,
249 server: Server,
250 cfg: SharedCfg,
251}
252
253impl TestServer {
254 pub fn addr(&self) -> net::SocketAddr {
256 self.addr
257 }
258
259 #[must_use]
260 pub fn set_addr(mut self, addr: net::SocketAddr) -> Self {
262 self.addr = addr;
263 self
264 }
265
266 pub fn config(&self) -> SharedCfg {
268 self.inner.cfg.clone()
269 }
270
271 pub async fn connect(&self) -> io::Result<Io> {
273 tcp_connect(self.addr, self.inner.cfg.clone()).await
274 }
275
276 pub fn stop(&self) {
280 drop(self.inner.server.stop(true));
281 }
282
283 pub fn unused_addr() -> net::SocketAddr {
288 let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
289 let socket = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
290 socket.set_reuse_address(true).unwrap();
291 socket.bind(&SockAddr::from(addr)).unwrap();
292 let tcp = net::TcpListener::from(socket);
293 tcp.local_addr().unwrap()
294 }
295
296 pub fn server(&self) -> Server {
298 self.inner.server.clone()
299 }
300}
301
302impl Drop for TestServerInner {
303 fn drop(&mut self) {
304 log::debug!("Stopping test server (dropped) {:?}", self.id);
305 drop(self.server.stop(false));
306 thread::sleep(time::Duration::from_millis(75));
307 self.system.stop();
308 thread::sleep(time::Duration::from_millis(25));
309 }
310}