From 7017bad4bb530724f3fe7398ec491f6ce19c7071 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 1 Jan 2019 22:59:52 -0800 Subject: [PATCH] add test server --- Cargo.toml | 3 +- actix-test-server/Cargo.toml | 57 ++++++++++++++++ actix-test-server/src/lib.rs | 123 +++++++++++++++++++++++++++++++++++ 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 actix-test-server/Cargo.toml create mode 100644 actix-test-server/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 40e4831f8..f71868dcc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,10 @@ edition = "2018" members = [ "actix-codec", "actix-connector", + "actix-rt", "actix-service", "actix-server", - "actix-rt", + "actix-test-server", "actix-utils", ] diff --git a/actix-test-server/Cargo.toml b/actix-test-server/Cargo.toml new file mode 100644 index 000000000..aae7baed3 --- /dev/null +++ b/actix-test-server/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "actix-test-server" +version = "0.1.0" +authors = ["Nikolay Kim "] +description = "Actix test server" +keywords = ["network", "framework", "async", "futures"] +homepage = "https://actix.rs" +repository = "https://github.com/actix/actix-net.git" +documentation = "https://docs.rs/actix-test-server/" +categories = ["network-programming", "asynchronous"] +license = "MIT/Apache-2.0" +exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] +edition = "2018" +workspace = "../" + +[package.metadata.docs.rs] +features = ["ssl", "tls", "rust-tls"] + +[lib] +name = "actix_test_server" +path = "src/lib.rs" + +[features] +default = [] + +# tls +tls = ["native-tls", "actix-server/tls"] + +# openssl +ssl = ["openssl", "actix-server/ssl"] + +# rustls +rust-tls = ["rustls", "tokio-rustls", "webpki", "webpki-roots"] + +[dependencies] +actix-rt = "0.1.0" +actix-server = "0.1.0" + +log = "0.4" + +# io +net2 = "0.2" +futures = "0.1" +tokio-tcp = "0.1" +tokio-reactor = "0.1" + +# native-tls +native-tls = { version="0.2", optional = true } + +# openssl +openssl = { version="0.10", optional = true } + +#rustls +rustls = { version = "^0.14", optional = true } +tokio-rustls = { version = "^0.8", optional = true } +webpki = { version = "0.18", optional = true } +webpki-roots = { version = "0.15", optional = true } diff --git a/actix-test-server/src/lib.rs b/actix-test-server/src/lib.rs new file mode 100644 index 000000000..9dd51eacc --- /dev/null +++ b/actix-test-server/src/lib.rs @@ -0,0 +1,123 @@ +//! Various helpers for Actix applications to use during testing. +use std::sync::mpsc; +use std::{net, thread}; + +use actix_rt::{Runtime, System}; +use actix_server::{Server, StreamServiceFactory}; + +use futures::Future; +use net2::TcpBuilder; +use tokio_reactor::Handle; +use tokio_tcp::TcpStream; + +/// The `TestServer` type. +/// +/// `TestServer` is very simple test server that simplify process of writing +/// integration tests cases for actix applications. +/// +/// # Examples +/// +/// ```rust +/// # extern crate actix_test_server; +/// # use actix_web::*; +/// # +/// # fn my_handler(req: &HttpRequest) -> HttpResponse { +/// # HttpResponse::Ok().into() +/// # } +/// # +/// # fn main() { +/// use actix_test_server::TestServer; +/// +/// let mut srv = TestServer::new(|app| app.handler(my_handler)); +/// +/// let req = srv.get().finish().unwrap(); +/// let response = srv.execute(req.send()).unwrap(); +/// assert!(response.status().is_success()); +/// # } +/// ``` +pub struct TestServer; + +/// Test server runstime +pub struct TestServerRuntime { + addr: net::SocketAddr, + rt: Runtime, +} + +impl TestServer { + /// Start new test server with application factory + pub fn with(factory: F) -> TestServerRuntime { + let (tx, rx) = mpsc::channel(); + + // run server in separate thread + thread::spawn(move || { + let sys = System::new("actix-test-server"); + let tcp = net::TcpListener::bind("127.0.0.1:0").unwrap(); + let local_addr = tcp.local_addr().unwrap(); + + Server::build() + .listen("test", tcp, factory) + .workers(1) + .disable_signals() + .start(); + + tx.send((System::current(), local_addr)).unwrap(); + sys.run(); + }); + + let (system, addr) = rx.recv().unwrap(); + System::set_current(system); + + let rt = Runtime::new().unwrap(); + + TestServerRuntime { addr, rt } + } + + /// Get firat available unused local address + pub fn unused_addr() -> net::SocketAddr { + let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); + let socket = TcpBuilder::new_v4().unwrap(); + socket.bind(&addr).unwrap(); + socket.reuse_address(true).unwrap(); + let tcp = socket.to_tcp_listener().unwrap(); + tcp.local_addr().unwrap() + } +} + +impl TestServerRuntime { + /// Execute future on current runtime + pub fn block_on(&mut self, fut: F) -> Result + where + F: Future, + { + self.rt.block_on(fut) + } + + /// Spawn future to the current runtime + pub fn spawn(&mut self, fut: F) + where + F: Future + 'static, + { + self.rt.spawn(fut); + } + + /// Get test server address + pub fn addr(&self) -> net::SocketAddr { + self.addr + } + + /// Stop http server + fn stop(&mut self) { + System::current().stop(); + } + + /// Connect to server, return tokio TcpStream + pub fn connect(&self) -> std::io::Result { + TcpStream::from_std(net::TcpStream::connect(self.addr)?, &Handle::default()) + } +} + +impl Drop for TestServerRuntime { + fn drop(&mut self) { + self.stop() + } +}