From 577f91206cbfbb69c1fb13802db6b484446299f6 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Tue, 30 Jan 2018 15:13:33 -0800 Subject: [PATCH] added support for websocket testing --- Cargo.toml | 3 ++- guide/src/qs_8.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++ src/test.rs | 27 ++++++++++++++++++++----- src/ws/client.rs | 12 ++++++++--- src/ws/mod.rs | 2 +- tests/test_ws.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 134 insertions(+), 10 deletions(-) create mode 100644 tests/test_ws.rs diff --git a/Cargo.toml b/Cargo.toml index 1540fb938..93f0e1d39 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -78,7 +78,8 @@ tokio-openssl = { version="0.2", optional = true } [dependencies.actix] #version = "^0.4.6" -git = "https://github.com/actix/actix.git" +#git = "https://github.com/actix/actix.git" +path = "../actix" [dev-dependencies] env_logger = "0.5" diff --git a/guide/src/qs_8.md b/guide/src/qs_8.md index 53713a205..0f062edce 100644 --- a/guide/src/qs_8.md +++ b/guide/src/qs_8.md @@ -95,3 +95,54 @@ fn main() { assert!(reqwest::get(&url).unwrap().status().is_success()); // <- make request } ``` + +## WebSocket server tests + +It is possible to register *handler* with `TestApp::handler()` method that +initiate web socket connection. *TestServer* provides `ws()` which connects to +websocket server and returns ws reader and writer objects. *TestServer* also +provides `execute()` method which runs future object to completion and returns +result of the future computation. + +Here is simple example, that shows how to test server websocket handler. + +```rust +# extern crate actix; +# extern crate actix_web; +# extern crate futures; +# extern crate http; +# extern crate bytes; + +use actix_web::*; +use futures::Stream; +# use actix::prelude::*; + +struct Ws; // <- WebSocket actor + +impl Actor for Ws { + type Context = ws::WebsocketContext; +} + +impl Handler for Ws { + type Result = (); + + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { + match msg { + ws::Message::Text(text) => ctx.text(&text), + _ => (), + } + } +} + +fn main() { + let mut srv = test::TestServer::new( // <- start our server with ws handler + |app| app.handler(|req| ws::start(req, Ws))); + + let (reader, mut writer) = srv.ws().unwrap(); // <- connect to ws server + + writer.text("text"); // <- send message to server + + let (item, reader) = srv.execute(reader.into_future()).unwrap(); // <- wait for one message + assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); +} +``` diff --git a/src/test.rs b/src/test.rs index fffc6d023..39f8d605d 100644 --- a/src/test.rs +++ b/src/test.rs @@ -6,7 +6,7 @@ use std::sync::mpsc; use std::str::FromStr; use std::collections::HashMap; -use actix::{Arbiter, SyncAddress, System, msgs}; +use actix::{Arbiter, SyncAddress, System, SystemRunner, msgs}; use cookie::Cookie; use http::{Uri, Method, Version, HeaderMap, HttpTryFrom}; use http::header::{HeaderName, HeaderValue}; @@ -25,6 +25,7 @@ use payload::Payload; use httprequest::HttpRequest; use httpresponse::HttpResponse; use server::{HttpServer, HttpHandler, IntoHttpHandler, ServerSettings}; +use ws::{WsClient, WsClientError, WsClientReader, WsClientWriter}; /// The `TestServer` type. /// @@ -54,7 +55,8 @@ use server::{HttpServer, HttpHandler, IntoHttpHandler, ServerSettings}; pub struct TestServer { addr: net::SocketAddr, thread: Option>, - sys: SyncAddress, + system: SystemRunner, + server_sys: SyncAddress, } impl TestServer { @@ -95,7 +97,8 @@ impl TestServer { TestServer { addr: addr, thread: Some(join), - sys: sys, + system: System::new("actix-test"), + server_sys: sys, } } @@ -131,7 +134,8 @@ impl TestServer { TestServer { addr: addr, thread: Some(join), - sys: sys, + system: System::new("actix-test"), + server_sys: sys, } } @@ -162,10 +166,23 @@ impl TestServer { /// Stop http server fn stop(&mut self) { if let Some(handle) = self.thread.take() { - self.sys.send(msgs::SystemExit(0)); + self.server_sys.send(msgs::SystemExit(0)); let _ = handle.join(); } } + + /// Execute future on current core + pub fn execute(&mut self, fut: F) -> Result + where F: Future + { + self.system.run_until_complete(fut) + } + + /// Connect to websocket server + pub fn ws(&mut self) -> Result<(WsClientReader, WsClientWriter), WsClientError> { + let url = self.url("/"); + self.system.run_until_complete(WsClient::new(url).connect().unwrap()) + } } impl Drop for TestServer { diff --git a/src/ws/client.rs b/src/ws/client.rs index 500d13a3a..9d49cffb3 100644 --- a/src/ws/client.rs +++ b/src/ws/client.rs @@ -1,6 +1,6 @@ //! Http client request #![allow(unused_imports, dead_code)] -use std::{io, str}; +use std::{fmt, io, str}; use std::rc::Rc; use std::time::Duration; use std::cell::UnsafeCell; @@ -299,8 +299,8 @@ impl Future for WsHandshake { let match_key = if let Some(key) = resp.headers().get( HeaderName::try_from("SEC-WEBSOCKET-ACCEPT").unwrap()) { - // ... field is constructed by concatenating /key/ ... - // ... with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) + // field is constructed by concatenating /key/ + // with the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" (RFC 6455) const WS_GUID: &[u8] = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; let mut sha1 = Sha1::new(); sha1.update(self.key.as_ref()); @@ -336,6 +336,12 @@ pub struct WsClientReader { inner: Rc> } +impl fmt::Debug for WsClientReader { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "WsClientReader()") + } +} + impl WsClientReader { #[inline] fn as_mut(&mut self) -> &mut Inner { diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 3f6c895a9..caecefc63 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -74,7 +74,7 @@ const SEC_WEBSOCKET_VERSION: &str = "SEC-WEBSOCKET-VERSION"; /// `WebSocket` Message -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum Message { Text(String), Binary(Binary), diff --git a/tests/test_ws.rs b/tests/test_ws.rs new file mode 100644 index 000000000..83a7caf03 --- /dev/null +++ b/tests/test_ws.rs @@ -0,0 +1,49 @@ +extern crate actix; +extern crate actix_web; +extern crate futures; +extern crate http; +extern crate bytes; + +use bytes::Bytes; +use futures::Stream; + +use actix_web::*; +use actix::prelude::*; + +struct Ws; + +impl Actor for Ws { + type Context = ws::WebsocketContext; +} + +impl Handler for Ws { + type Result = (); + + fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) { + match msg { + ws::Message::Ping(msg) => ctx.pong(&msg), + ws::Message::Text(text) => ctx.text(&text), + ws::Message::Binary(bin) => ctx.binary(bin), + _ => (), + } + } +} + +#[test] +fn test_simple() { + let mut srv = test::TestServer::new( + |app| app.handler(|req| ws::start(req, Ws))); + let (reader, mut writer) = srv.ws().unwrap(); + + writer.text("text"); + let (item, reader) = srv.execute(reader.into_future()).unwrap(); + assert_eq!(item, Some(ws::Message::Text("text".to_owned()))); + + writer.binary(b"text".as_ref()); + let (item, reader) = srv.execute(reader.into_future()).unwrap(); + assert_eq!(item, Some(ws::Message::Binary(Bytes::from_static(b"text").into()))); + + writer.ping("ping"); + let (item, _) = srv.execute(reader.into_future()).unwrap(); + assert_eq!(item, Some(ws::Message::Pong("ping".to_owned()))); +}