actix_server/
test_server.rs

1use std::{io, net, sync::mpsc, thread};
2
3use actix_rt::{net::TcpStream, System};
4
5use crate::{Server, ServerBuilder, ServerHandle, ServerServiceFactory};
6
7/// A testing server.
8///
9/// `TestServer` is very simple test server that simplify process of writing integration tests for
10/// network applications.
11///
12/// # Examples
13/// ```
14/// use actix_service::fn_service;
15/// use actix_server::TestServer;
16///
17/// #[actix_rt::main]
18/// async fn main() {
19///     let srv = TestServer::start(|| fn_service(
20///         |sock| async move {
21///             println!("New connection: {:?}", sock);
22///             Ok::<_, ()>(())
23///         }
24///     ));
25///
26///     println!("SOCKET: {:?}", srv.connect());
27/// }
28/// ```
29pub struct TestServer;
30
31/// Test server handle.
32pub struct TestServerHandle {
33    addr: net::SocketAddr,
34    host: String,
35    port: u16,
36    server_handle: ServerHandle,
37    thread_handle: Option<thread::JoinHandle<io::Result<()>>>,
38}
39
40impl TestServer {
41    /// Start new `TestServer` using application factory and default server config.
42    pub fn start(factory: impl ServerServiceFactory<TcpStream>) -> TestServerHandle {
43        Self::start_with_builder(Server::build(), factory)
44    }
45
46    /// Start new `TestServer` using application factory and server builder.
47    pub fn start_with_builder(
48        server_builder: ServerBuilder,
49        factory: impl ServerServiceFactory<TcpStream>,
50    ) -> TestServerHandle {
51        let (tx, rx) = mpsc::channel();
52
53        // run server in separate thread
54        let thread_handle = thread::spawn(move || {
55            let lst = net::TcpListener::bind("127.0.0.1:0").unwrap();
56            let local_addr = lst.local_addr().unwrap();
57
58            System::new().block_on(async {
59                let server = server_builder
60                    .listen("test", lst, factory)
61                    .unwrap()
62                    .workers(1)
63                    .disable_signals()
64                    .run();
65
66                tx.send((server.handle(), local_addr)).unwrap();
67                server.await
68            })
69        });
70
71        let (server_handle, addr) = rx.recv().unwrap();
72
73        let host = format!("{}", addr.ip());
74        let port = addr.port();
75
76        TestServerHandle {
77            addr,
78            host,
79            port,
80            server_handle,
81            thread_handle: Some(thread_handle),
82        }
83    }
84
85    /// Get first available unused local address.
86    pub fn unused_addr() -> net::SocketAddr {
87        use socket2::{Domain, Protocol, Socket, Type};
88
89        let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap();
90        let domain = Domain::for_address(addr);
91        let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP)).unwrap();
92
93        socket.set_reuse_address(true).unwrap();
94        socket.set_nonblocking(true).unwrap();
95        socket.bind(&addr.into()).unwrap();
96        socket.listen(1024).unwrap();
97
98        net::TcpListener::from(socket).local_addr().unwrap()
99    }
100}
101
102impl TestServerHandle {
103    /// Test server host.
104    pub fn host(&self) -> &str {
105        &self.host
106    }
107
108    /// Test server port.
109    pub fn port(&self) -> u16 {
110        self.port
111    }
112
113    /// Get test server address.
114    pub fn addr(&self) -> net::SocketAddr {
115        self.addr
116    }
117
118    /// Stop server.
119    fn stop(&mut self) {
120        drop(self.server_handle.stop(false));
121        self.thread_handle.take().unwrap().join().unwrap().unwrap();
122    }
123
124    /// Connect to server, returning a Tokio `TcpStream`.
125    pub fn connect(&self) -> io::Result<TcpStream> {
126        let stream = net::TcpStream::connect(self.addr)?;
127        stream.set_nonblocking(true)?;
128        TcpStream::from_std(stream)
129    }
130}
131
132impl Drop for TestServerHandle {
133    fn drop(&mut self) {
134        self.stop()
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use actix_service::fn_service;
141
142    use super::*;
143
144    #[tokio::test]
145    async fn connect_in_tokio_runtime() {
146        let srv = TestServer::start(|| fn_service(|_sock| async move { Ok::<_, ()>(()) }));
147        assert!(srv.connect().is_ok());
148    }
149
150    #[actix_rt::test]
151    async fn connect_in_actix_runtime() {
152        let srv = TestServer::start(|| fn_service(|_sock| async move { Ok::<_, ()>(()) }));
153        assert!(srv.connect().is_ok());
154    }
155}