tower-test-0.4.0/.cargo_vcs_info.json0000644000000001120000000000000131430ustar { "git": { "sha1": "ca685ae943d5e86f09e093a9baef830c4ef7f933" } } tower-test-0.4.0/CHANGELOG.md000064400000000000000000000007060000000000000135330ustar 00000000000000# 0.4.0 (January 7, 2021) - Updated `tokio-test` dependency to 0.4 - Updated `tokio` dependency to 1.0 # 0.3.0 (December 19, 2019) - Remove `futures-executor` dependency - Update to non-alpha versions - Add `mock::task_fn` util fn # 0.3.0-alpha.2 (September 30, 2019) - Move to `futures-*-preview 0.3.0-alpha.19` - Move to `pin-project 0.4` # 0.3.0-alpha.1 (September 11, 2019) - Move to `std::future` # 0.1.0 (April 26, 2019) - Initial release tower-test-0.4.0/Cargo.toml0000644000000024470000000000000111560ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "tower-test" version = "0.4.0" authors = ["Tower Maintainers "] description = "Utilities for writing client and server `Service` tests.\n" homepage = "https://github.com/tower-rs/tower" documentation = "https://docs.rs/tower-test/0.4.0" readme = "README.md" categories = ["asynchronous", "network-programming"] license = "MIT" repository = "https://github.com/tower-rs/tower" [dependencies.futures-util] version = "0.3" default-features = false [dependencies.pin-project] version = "1" [dependencies.tokio] version = "1.0" features = ["sync"] [dependencies.tokio-test] version = "0.4" [dependencies.tower-layer] version = "0.3" [dependencies.tower-service] version = "0.3" [dev-dependencies.tokio] version = "1.0" features = ["macros"] tower-test-0.4.0/Cargo.toml.orig000064400000000000000000000016720000000000000146140ustar 00000000000000[package] name = "tower-test" # When releasing to crates.io: # - Remove path dependencies # - Update html_root_url. # - Update doc url # - Cargo.toml # - README.md # - Update CHANGELOG.md. # - Create "v0.1.x" git tag. version = "0.4.0" authors = ["Tower Maintainers "] license = "MIT" readme = "README.md" repository = "https://github.com/tower-rs/tower" homepage = "https://github.com/tower-rs/tower" documentation = "https://docs.rs/tower-test/0.4.0" description = """ Utilities for writing client and server `Service` tests. """ categories = ["asynchronous", "network-programming"] edition = "2018" [dependencies] futures-util = { version = "0.3", default-features = false } tokio = { version = "1.0", features = ["sync"] } tokio-test = "0.4" tower-layer = { version = "0.3", path = "../tower-layer" } tower-service = { version = "0.3" } pin-project = "1" [dev-dependencies] tokio = { version = "1.0", features = ["macros"] } tower-test-0.4.0/LICENSE000064400000000000000000000020460000000000000127260ustar 00000000000000Copyright (c) 2019 Tower Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tower-test-0.4.0/README.md000064400000000000000000000005270000000000000132020ustar 00000000000000# Tower Test Utilities for writing client and server `Service` tests. ## License This project is licensed under the [MIT license](LICENSE). ### Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in Tower by you, shall be licensed as MIT, without any additional terms or conditions. tower-test-0.4.0/src/lib.rs000064400000000000000000000004260000000000000136240ustar 00000000000000#![doc(html_root_url = "https://docs.rs/tower-test/0.4.0")] #![warn( missing_debug_implementations, missing_docs, rust_2018_idioms, unreachable_pub )] #![allow(elided_lifetimes_in_paths)] //! Mock `Service` that can be used in tests. mod macros; pub mod mock; tower-test-0.4.0/src/macros.rs000064400000000000000000000023210000000000000143360ustar 00000000000000/// Asserts that the mock handle receives a new request equal to the given /// value. /// /// On success, the [`SendResponse`] handle for the matched request is returned, /// allowing the caller to respond to the request. On failure, the macro panics. /// /// # Examples /// /// ```rust /// use tower_service::Service; /// use tower_test::{mock, assert_request_eq}; /// use tokio_test::assert_ready; /// /// # async fn test() { /// let (mut service, mut handle) = mock::spawn(); /// /// assert_ready!(service.poll_ready()); /// /// let response = service.call("hello"); /// /// assert_request_eq!(handle, "hello").send_response("world"); /// /// assert_eq!(response.await.unwrap(), "world"); /// # } /// ``` /// [`SendResponse`]: crate::mock::SendResponse #[macro_export] macro_rules! assert_request_eq { ($mock_handle:expr, $expect:expr) => { assert_request_eq!($mock_handle, $expect,) }; ($mock_handle:expr, $expect:expr, $($arg:tt)*) => {{ let (actual, send_response) = match $mock_handle.next_request().await { Some(r) => r, None => panic!("expected a request but none was received."), }; assert_eq!(actual, $expect, $($arg)*); send_response }}; } tower-test-0.4.0/src/mock/error.rs000064400000000000000000000007170000000000000151430ustar 00000000000000//! Error types use std::{error, fmt}; pub(crate) type Error = Box; /// Error yielded when a mocked service does not yet accept requests. #[derive(Debug)] pub struct Closed(()); impl Closed { pub(crate) fn new() -> Closed { Closed(()) } } impl fmt::Display for Closed { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { write!(fmt, "service closed") } } impl error::Error for Closed {} tower-test-0.4.0/src/mock/future.rs000064400000000000000000000021040000000000000153140ustar 00000000000000//! Future types use crate::mock::error::{self, Error}; use futures_util::ready; use pin_project::pin_project; use tokio::sync::oneshot; use std::{ future::Future, pin::Pin, task::{Context, Poll}, }; /// Future of the `Mock` response. #[pin_project] #[derive(Debug)] pub struct ResponseFuture { #[pin] rx: Option>, } type Rx = oneshot::Receiver>; impl ResponseFuture { pub(crate) fn new(rx: Rx) -> ResponseFuture { ResponseFuture { rx: Some(rx) } } pub(crate) fn closed() -> ResponseFuture { ResponseFuture { rx: None } } } impl Future for ResponseFuture { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { match self.project().rx.as_pin_mut() { Some(rx) => match ready!(rx.poll(cx)) { Ok(r) => Poll::Ready(r), Err(_) => Poll::Ready(Err(error::Closed::new().into())), }, None => Poll::Ready(Err(error::Closed::new().into())), } } } tower-test-0.4.0/src/mock/mod.rs000064400000000000000000000163150000000000000145720ustar 00000000000000//! Mock `Service` that can be used in tests. pub mod error; pub mod future; pub mod spawn; pub use spawn::Spawn; use crate::mock::{error::Error, future::ResponseFuture}; use core::task::Waker; use tokio::sync::{mpsc, oneshot}; use tower_layer::Layer; use tower_service::Service; use std::{ collections::HashMap, future::Future, sync::{Arc, Mutex}, task::{Context, Poll}, u64, }; /// Spawn a layer onto a mock service. pub fn spawn_layer(layer: L) -> (Spawn, Handle) where L: Layer>, { let (inner, handle) = pair(); let svc = layer.layer(inner); (Spawn::new(svc), handle) } /// Spawn a Service onto a mock task. pub fn spawn() -> (Spawn>, Handle) { let (svc, handle) = pair(); (Spawn::new(svc), handle) } /// Spawn a Service via the provided wrapper closure. pub fn spawn_with(f: F) -> (Spawn, Handle) where F: Fn(Mock) -> S, { let (svc, handle) = pair(); let svc = f(svc); (Spawn::new(svc), handle) } /// A mock service #[derive(Debug)] pub struct Mock { id: u64, tx: Mutex>, state: Arc>, can_send: bool, } /// Handle to the `Mock`. #[derive(Debug)] pub struct Handle { rx: Rx, state: Arc>, } type Request = (T, SendResponse); /// Send a response in reply to a received request. #[derive(Debug)] pub struct SendResponse { tx: oneshot::Sender>, } #[derive(Debug)] struct State { /// Tracks the number of requests that can be sent through rem: u64, /// Tasks that are blocked tasks: HashMap, /// Tracks if the `Handle` dropped is_closed: bool, /// Tracks the ID for the next mock clone next_clone_id: u64, /// Tracks the next error to yield (if any) err_with: Option, } type Tx = mpsc::UnboundedSender>; type Rx = mpsc::UnboundedReceiver>; /// Create a new `Mock` and `Handle` pair. pub fn pair() -> (Mock, Handle) { let (tx, rx) = mpsc::unbounded_channel(); let tx = Mutex::new(tx); let state = Arc::new(Mutex::new(State::new())); let mock = Mock { id: 0, tx, state: state.clone(), can_send: false, }; let handle = Handle { rx, state }; (mock, handle) } impl Service for Mock { type Response = U; type Error = Error; type Future = ResponseFuture; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { let mut state = self.state.lock().unwrap(); if state.is_closed { return Poll::Ready(Err(error::Closed::new().into())); } if let Some(e) = state.err_with.take() { return Poll::Ready(Err(e)); } if self.can_send { return Poll::Ready(Ok(())); } if state.rem > 0 { assert!(!state.tasks.contains_key(&self.id)); // Returning `Ready` means the next call to `call` must succeed. self.can_send = true; Poll::Ready(Ok(())) } else { // Bit weird... but whatevz *state .tasks .entry(self.id) .or_insert_with(|| cx.waker().clone()) = cx.waker().clone(); Poll::Pending } } fn call(&mut self, request: T) -> Self::Future { // Make sure that the service has capacity let mut state = self.state.lock().unwrap(); if state.is_closed { return ResponseFuture::closed(); } if !self.can_send { panic!("service not ready; poll_ready must be called first"); } self.can_send = false; // Decrement the number of remaining requests that can be sent if state.rem > 0 { state.rem -= 1; } let (tx, rx) = oneshot::channel(); let send_response = SendResponse { tx }; match self.tx.lock().unwrap().send((request, send_response)) { Ok(_) => {} Err(_) => { // TODO: Can this be reached return ResponseFuture::closed(); } } ResponseFuture::new(rx) } } impl Clone for Mock { fn clone(&self) -> Self { let id = { let mut state = self.state.lock().unwrap(); let id = state.next_clone_id; state.next_clone_id += 1; id }; let tx = Mutex::new(self.tx.lock().unwrap().clone()); Mock { id, tx, state: self.state.clone(), can_send: false, } } } impl Drop for Mock { fn drop(&mut self) { let mut state = match self.state.lock() { Ok(v) => v, Err(e) => { if ::std::thread::panicking() { return; } panic!("{:?}", e); } }; state.tasks.remove(&self.id); } } // ===== impl Handle ===== impl Handle { /// Asynchronously gets the next request pub fn poll_request(&mut self) -> Poll>> { tokio_test::task::spawn(()).enter(|cx, _| Box::pin(self.rx.recv()).as_mut().poll(cx)) } /// Gets the next request. pub async fn next_request(&mut self) -> Option> { self.rx.recv().await } /// Allow a certain number of requests pub fn allow(&mut self, num: u64) { let mut state = self.state.lock().unwrap(); state.rem = num; if num > 0 { for (_, task) in state.tasks.drain() { task.wake(); } } } /// Make the next poll_ method error with the given error. pub fn send_error>(&mut self, e: E) { let mut state = self.state.lock().unwrap(); state.err_with = Some(e.into()); for (_, task) in state.tasks.drain() { task.wake(); } } } impl Drop for Handle { fn drop(&mut self) { let mut state = match self.state.lock() { Ok(v) => v, Err(e) => { if ::std::thread::panicking() { return; } panic!("{:?}", e); } }; state.is_closed = true; for (_, task) in state.tasks.drain() { task.wake(); } } } // ===== impl SendResponse ===== impl SendResponse { /// Resolve the pending request future for the linked request with the given response. pub fn send_response(self, response: T) { // TODO: Should the result be dropped? let _ = self.tx.send(Ok(response)); } /// Resolve the pending request future for the linked request with the given error. pub fn send_error>(self, err: E) { // TODO: Should the result be dropped? let _ = self.tx.send(Err(err.into())); } } // ===== impl State ===== impl State { fn new() -> State { State { rem: u64::MAX, tasks: HashMap::new(), is_closed: false, next_clone_id: 1, err_with: None, } } } tower-test-0.4.0/src/mock/spawn.rs000064400000000000000000000030330000000000000151340ustar 00000000000000//! Spawn mock services onto a mock task. use std::task::Poll; use tokio_test::task; use tower_service::Service; /// Service spawned on a mock task #[derive(Debug)] pub struct Spawn { inner: T, task: task::Spawn<()>, } impl Spawn { /// Create a new spawn. pub fn new(inner: T) -> Self { Self { inner, task: task::spawn(()), } } /// Check if this service has been woken up. pub fn is_woken(&self) -> bool { self.task.is_woken() } /// Get how many futurs are holding onto the waker. pub fn waker_ref_count(&self) -> usize { self.task.waker_ref_count() } /// Poll this service ready. pub fn poll_ready(&mut self) -> Poll> where T: Service, { let task = &mut self.task; let inner = &mut self.inner; task.enter(|cx, _| inner.poll_ready(cx)) } /// Call the inner Service. pub fn call(&mut self, req: Request) -> T::Future where T: Service, { self.inner.call(req) } /// Get the inner service. pub fn into_inner(self) -> T { self.inner } /// Get a reference to the inner service. pub fn get_ref(&self) -> &T { &self.inner } /// Get a mutable reference to the inner service. pub fn get_mut(&mut self) -> &mut T { &mut self.inner } } impl Clone for Spawn { fn clone(&self) -> Self { Spawn::new(self.inner.clone()) } } tower-test-0.4.0/tests/mock.rs000064400000000000000000000013340000000000000143610ustar 00000000000000use tokio_test::{assert_pending, assert_ready}; use tower_test::{assert_request_eq, mock}; #[tokio::test(flavor = "current_thread")] async fn single_request_ready() { let (mut service, mut handle) = mock::spawn(); assert_pending!(handle.poll_request()); assert_ready!(service.poll_ready()).unwrap(); let response = service.call("hello"); assert_request_eq!(handle, "hello").send_response("world"); assert_eq!(response.await.unwrap(), "world"); } #[tokio::test(flavor = "current_thread")] #[should_panic] async fn backpressure() { let (mut service, mut handle) = mock::spawn::<_, ()>(); handle.allow(0); assert_pending!(service.poll_ready()); service.call("hello").await.unwrap(); }