587 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			587 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! A mock type implementing [`Read`] and [`Write`].
 | |
| //!
 | |
| //! Copied from https://github.com/carllerche/mock-io.
 | |
| //!
 | |
| //! TODO:
 | |
| //! - Either the mock-io crate should be released or this module should be
 | |
| //!   removed from h2.
 | |
| //!
 | |
| //! # Overview
 | |
| //!
 | |
| //! Provides a type that implements [`Read`] + [`Write`] that can be configured
 | |
| //! to handle an arbitrary sequence of read and write operations. This is useful
 | |
| //! for writing unit tests for networking services as using an actual network
 | |
| //! type is fairly non deterministic.
 | |
| //!
 | |
| //! # Usage
 | |
| //!
 | |
| //! Add the following to your `Cargo.toml`
 | |
| //!
 | |
| //! ```toml
 | |
| //! [dependencies]
 | |
| //! mock-io = { git = "https://github.com/carllerche/mock-io" }
 | |
| //! ```
 | |
| //!
 | |
| //! Then use it in your project. For example, a test could be written:
 | |
| //!
 | |
| //! ```
 | |
| //! extern crate mock_io;
 | |
| //!
 | |
| //! use mock_io::{Builder, Mock};
 | |
| //! use std::io::{Read, Write};
 | |
| //!
 | |
| //! # /*
 | |
| //! #[test]
 | |
| //! # */
 | |
| //! fn test_io() {
 | |
| //!     let mut mock = Builder::new()
 | |
| //!         .write(b"ping")
 | |
| //!         .read(b"pong")
 | |
| //!         .build();
 | |
| //!
 | |
| //!    let n = mock.write(b"ping").unwrap();
 | |
| //!    assert_eq!(n, 4);
 | |
| //!
 | |
| //!    let mut buf = vec![];
 | |
| //!    mock.read_to_end(&mut buf).unwrap();
 | |
| //!
 | |
| //!    assert_eq!(buf, b"pong");
 | |
| //! }
 | |
| //! # pub fn main() {
 | |
| //! # test_io();
 | |
| //! # }
 | |
| //! ```
 | |
| //!
 | |
| //! Attempting to write data that the mock isn't expected will result in a
 | |
| //! panic.
 | |
| //!
 | |
| //! # Tokio
 | |
| //!
 | |
| //! `Mock` also supports tokio by implementing `AsyncRead` and `AsyncWrite`.
 | |
| //! When using `Mock` in context of a Tokio task, it will automatically switch
 | |
| //! to "async" behavior (this can also be set explicitly by calling `set_async`
 | |
| //! on `Builder`).
 | |
| //!
 | |
| //! In async mode, calls to read and write are non-blocking and the task using
 | |
| //! the mock is notified when the readiness state changes.
 | |
| //!
 | |
| //! # `io-dump` dump files
 | |
| //!
 | |
| //! `Mock` can also be configured from an `io-dump` file. By doing this, the
 | |
| //! mock value will replay a previously recorded behavior. This is useful for
 | |
| //! collecting a scenario from the real world and replying it as part of a test.
 | |
| //!
 | |
| //! [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html
 | |
| //! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html
 | |
| 
 | |
| #![allow(deprecated)]
 | |
| 
 | |
| use std::{cmp, io};
 | |
| use std::collections::VecDeque;
 | |
| use std::time::{Duration, Instant};
 | |
| 
 | |
| /// An I/O handle that follows a predefined script.
 | |
| ///
 | |
| /// This value is created by `Builder` and implements `Read + `Write`. It
 | |
| /// follows the scenario described by the builder and panics otherwise.
 | |
| #[derive(Debug)]
 | |
| pub struct Mock {
 | |
|     inner: Inner,
 | |
|     tokio: tokio::Inner,
 | |
|     async: Option<bool>,
 | |
| }
 | |
| 
 | |
| #[derive(Debug)]
 | |
| pub struct Handle {
 | |
|     inner: tokio::Handle,
 | |
| }
 | |
| 
 | |
| /// Builds `Mock` instances.
 | |
| #[derive(Debug, Clone, Default)]
 | |
| pub struct Builder {
 | |
|     // Sequence of actions for the Mock to take
 | |
|     actions: VecDeque<Action>,
 | |
| 
 | |
|     // true for Tokio, false for blocking, None to auto detect
 | |
|     async: Option<bool>,
 | |
| }
 | |
| 
 | |
| #[derive(Debug, Clone)]
 | |
| enum Action {
 | |
|     Read(Vec<u8>),
 | |
|     Write(Vec<u8>),
 | |
|     Wait(Duration),
 | |
| }
 | |
| 
 | |
| #[derive(Debug)]
 | |
| struct Inner {
 | |
|     actions: VecDeque<Action>,
 | |
|     waiting: Option<Instant>,
 | |
| }
 | |
| 
 | |
| impl Builder {
 | |
|     /// Return a new, empty `Builder.
 | |
|     pub fn new() -> Self {
 | |
|         Self::default()
 | |
|     }
 | |
| 
 | |
|     /// Sequence a `read` operation.
 | |
|     ///
 | |
|     /// The next operation in the mock's script will be to expect a `read` call
 | |
|     /// and return `buf`.
 | |
|     pub fn read(&mut self, buf: &[u8]) -> &mut Self {
 | |
|         self.actions.push_back(Action::Read(buf.into()));
 | |
|         self
 | |
|     }
 | |
| 
 | |
|     /// Sequence a `write` operation.
 | |
|     ///
 | |
|     /// The next operation in the mock's script will be to expect a `write`
 | |
|     /// call.
 | |
|     pub fn write(&mut self, buf: &[u8]) -> &mut Self {
 | |
|         self.actions.push_back(Action::Write(buf.into()));
 | |
|         self
 | |
|     }
 | |
| 
 | |
|     /// Sequence a wait.
 | |
|     ///
 | |
|     /// The next operation in the mock's script will be to wait without doing so
 | |
|     /// for `duration` amount of time.
 | |
|     pub fn wait(&mut self, duration: Duration) -> &mut Self {
 | |
|         let duration = cmp::max(duration, Duration::from_millis(1));
 | |
|         self.actions.push_back(Action::Wait(duration));
 | |
|         self
 | |
|     }
 | |
| 
 | |
|     /// Build a `Mock` value according to the defined script.
 | |
|     pub fn build(&mut self) -> Mock {
 | |
|         let (mock, _) = self.build_with_handle();
 | |
|         mock
 | |
|     }
 | |
| 
 | |
|     /// Build a `Mock` value paired with a handle
 | |
|     pub fn build_with_handle(&mut self) -> (Mock, Handle) {
 | |
|         let (tokio, handle) = tokio::Inner::new();
 | |
| 
 | |
|         let src = self.clone();
 | |
| 
 | |
|         let mock = Mock {
 | |
|             inner: Inner {
 | |
|                 actions: src.actions,
 | |
|                 waiting: None,
 | |
|             },
 | |
|             tokio: tokio,
 | |
|             async: src.async,
 | |
|         };
 | |
| 
 | |
|         let handle = Handle { inner: handle };
 | |
| 
 | |
|         (mock, handle)
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Handle {
 | |
|     /// Sequence a `read` operation.
 | |
|     ///
 | |
|     /// The next operation in the mock's script will be to expect a `read` call
 | |
|     /// and return `buf`.
 | |
|     pub fn read(&mut self, buf: &[u8]) -> &mut Self {
 | |
|         self.inner.read(buf);
 | |
|         self
 | |
|     }
 | |
| 
 | |
|     /// Sequence a `write` operation.
 | |
|     ///
 | |
|     /// The next operation in the mock's script will be to expect a `write`
 | |
|     /// call.
 | |
|     pub fn write(&mut self, buf: &[u8]) -> &mut Self {
 | |
|         self.inner.write(buf);
 | |
|         self
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Mock {
 | |
|     fn sync_read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
 | |
|         use std::thread;
 | |
| 
 | |
|         loop {
 | |
|             match self.inner.read(dst) {
 | |
|                 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
 | |
|                     if let Some(rem) = self.inner.remaining_wait() {
 | |
|                         thread::sleep(rem);
 | |
|                     } else {
 | |
|                         // We've entered a dead lock scenario. The peer expects
 | |
|                         // a write but we are reading.
 | |
|                         panic!("mock_io::Mock expects write but currently blocked in read");
 | |
|                     }
 | |
|                 }
 | |
|                 ret => return ret,
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn sync_write(&mut self, src: &[u8]) -> io::Result<usize> {
 | |
|         match self.inner.write(src) {
 | |
|             Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
 | |
|                 panic!("mock_io::Mock not currently expecting a write");
 | |
|             }
 | |
|             ret => ret,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Returns `true` if running in a futures-rs task context
 | |
|     fn is_async(&self) -> bool {
 | |
|         self.async.unwrap_or(tokio::is_task_ctx())
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl Inner {
 | |
|     fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
 | |
|         match self.action() {
 | |
|             Some(&mut Action::Read(ref mut data)) =>{
 | |
|                 // Figure out how much to copy
 | |
|                 let n = cmp::min(dst.len(), data.len());
 | |
| 
 | |
|                 // Copy the data into the `dst` slice
 | |
|                 (&mut dst[..n]).copy_from_slice(&data[..n]);
 | |
| 
 | |
|                 // Drain the data from the source
 | |
|                 data.drain(..n);
 | |
| 
 | |
|                 // Return the number of bytes read
 | |
|                 Ok(n)
 | |
|             }
 | |
|             Some(_) => {
 | |
|                 // Either waiting or expecting a write
 | |
|                 Err(io::ErrorKind::WouldBlock.into())
 | |
|             }
 | |
|             None => {
 | |
|                  Ok(0)
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn write(&mut self, mut src: &[u8]) -> io::Result<usize> {
 | |
|         let mut ret = 0;
 | |
| 
 | |
|         if self.actions.is_empty() {
 | |
|             return Err(io::ErrorKind::BrokenPipe.into());
 | |
|         }
 | |
| 
 | |
|         match self.action() {
 | |
|             Some(&mut Action::Wait(..)) => {
 | |
|                 return Err(io::ErrorKind::WouldBlock.into());
 | |
|             }
 | |
|             _ => {}
 | |
|         }
 | |
| 
 | |
|         for i in 0..self.actions.len() {
 | |
|             match self.actions[i] {
 | |
|                 Action::Write(ref mut expect) => {
 | |
|                     let n = cmp::min(src.len(), expect.len());
 | |
| 
 | |
|                     assert_eq!(&src[..n], &expect[..n]);
 | |
| 
 | |
|                     // Drop data that was matched
 | |
|                     expect.drain(..n);
 | |
|                     src = &src[n..];
 | |
| 
 | |
|                     ret += n;
 | |
| 
 | |
|                     if src.is_empty() {
 | |
|                         return Ok(ret);
 | |
|                     }
 | |
|                 }
 | |
|                 Action::Wait(..) => {
 | |
|                     break;
 | |
|                 }
 | |
|                 _ => {}
 | |
|             }
 | |
| 
 | |
|             // TODO: remove write
 | |
|         }
 | |
| 
 | |
|         Ok(ret)
 | |
|     }
 | |
| 
 | |
|     fn remaining_wait(&mut self) -> Option<Duration> {
 | |
|         match self.action() {
 | |
|             Some(&mut Action::Wait(dur)) => Some(dur),
 | |
|             _ => None,
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn action(&mut self) -> Option<&mut Action> {
 | |
|         loop {
 | |
|             if self.actions.is_empty() {
 | |
|                 return None;
 | |
|             }
 | |
| 
 | |
|             match self.actions[0] {
 | |
|                 Action::Read(ref mut data) => {
 | |
|                     if !data.is_empty() {
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|                 Action::Write(ref mut data) => {
 | |
|                     if !data.is_empty() {
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|                 Action::Wait(ref mut dur) => {
 | |
|                     if let Some(until) = self.waiting {
 | |
|                         let now = Instant::now();
 | |
| 
 | |
|                         if now < until {
 | |
|                             break;
 | |
|                         }
 | |
|                     } else {
 | |
|                         self.waiting = Some(Instant::now() + *dur);
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             let _action = self.actions.pop_front();
 | |
|         }
 | |
| 
 | |
|         self.actions.front_mut()
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl io::Read for Mock {
 | |
|     fn read(&mut self, dst: &mut [u8]) -> io::Result<usize> {
 | |
|         if self.is_async() {
 | |
|             tokio::async_read(self, dst)
 | |
|         } else {
 | |
|             self.sync_read(dst)
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl io::Write for Mock {
 | |
|     fn write(&mut self, src: &[u8]) -> io::Result<usize> {
 | |
|         if self.is_async() {
 | |
|             tokio::async_write(self, src)
 | |
|         } else {
 | |
|             self.sync_write(src)
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     fn flush(&mut self) -> io::Result<()> {
 | |
|         Ok(())
 | |
|     }
 | |
| }
 | |
| 
 | |
| // use tokio::*;
 | |
| 
 | |
| mod tokio {
 | |
|     extern crate futures;
 | |
|     extern crate tokio_io;
 | |
|     extern crate tokio_timer;
 | |
| 
 | |
|     use super::*;
 | |
| 
 | |
|     use self::futures::{Future, Stream, Poll, Async};
 | |
|     use self::futures::sync::mpsc;
 | |
|     use self::futures::task::{self, Task};
 | |
|     use self::tokio_io::{AsyncRead, AsyncWrite};
 | |
|     use self::tokio_timer::{Timer, Sleep};
 | |
| 
 | |
|     use std::io;
 | |
| 
 | |
|     impl Builder {
 | |
|         pub fn set_async(&mut self, is_async: bool) -> &mut Self {
 | |
|             self.async = Some(is_async);
 | |
|             self
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     #[derive(Debug)]
 | |
|     pub struct Inner {
 | |
|         timer: Timer,
 | |
|         sleep: Option<Sleep>,
 | |
|         read_wait: Option<Task>,
 | |
|         rx: mpsc::UnboundedReceiver<Action>,
 | |
|     }
 | |
| 
 | |
|     #[derive(Debug)]
 | |
|     pub struct Handle {
 | |
|         tx: mpsc::UnboundedSender<Action>,
 | |
|     }
 | |
| 
 | |
|     // ===== impl Handle =====
 | |
| 
 | |
|     impl Handle {
 | |
|         pub fn read(&mut self, buf: &[u8]) {
 | |
|             mpsc::UnboundedSender::send(&mut self.tx, Action::Read(buf.into())).unwrap();
 | |
|         }
 | |
| 
 | |
|         pub fn write(&mut self, buf: &[u8]) {
 | |
|             mpsc::UnboundedSender::send(&mut self.tx, Action::Write(buf.into())).unwrap();
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // ===== impl Inner =====
 | |
| 
 | |
|     impl Inner {
 | |
|         pub fn new() -> (Inner, Handle) {
 | |
|             // TODO: We probably want a higher resolution timer.
 | |
|             let timer = tokio_timer::wheel()
 | |
|                 .tick_duration(Duration::from_millis(1))
 | |
|                 .max_timeout(Duration::from_secs(3600))
 | |
|                 .build();
 | |
| 
 | |
|             let (tx, rx) = mpsc::unbounded();
 | |
| 
 | |
|             let inner = Inner {
 | |
|                 timer: timer,
 | |
|                 sleep: None,
 | |
|                 read_wait: None,
 | |
|                 rx: rx,
 | |
|             };
 | |
| 
 | |
|             let handle = Handle { tx };
 | |
| 
 | |
|             (inner, handle)
 | |
|         }
 | |
| 
 | |
|         pub(super) fn poll_action(&mut self) -> Poll<Option<Action>, ()> {
 | |
|             self.rx.poll()
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     impl Mock {
 | |
|         fn maybe_wakeup_reader(&mut self) {
 | |
|             match self.inner.action() {
 | |
|                 Some(&mut Action::Read(_)) | None => {
 | |
|                     if let Some(task) = self.tokio.read_wait.take() {
 | |
|                         task.notify();
 | |
|                     }
 | |
|                 }
 | |
|                 _ => {}
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn async_read(me: &mut Mock, dst: &mut [u8]) -> io::Result<usize> {
 | |
|         loop {
 | |
|             if let Some(ref mut sleep) = me.tokio.sleep {
 | |
|                 let res = try!(sleep.poll());
 | |
| 
 | |
|                 if !res.is_ready() {
 | |
|                     return Err(io::ErrorKind::WouldBlock.into());
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // If a sleep is set, it has already fired
 | |
|             me.tokio.sleep = None;
 | |
| 
 | |
|             match me.inner.read(dst) {
 | |
|                 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
 | |
|                     if let Some(rem) = me.inner.remaining_wait() {
 | |
|                         me.tokio.sleep = Some(me.tokio.timer.sleep(rem));
 | |
|                     } else {
 | |
|                         me.tokio.read_wait = Some(task::current());
 | |
|                         return Err(io::ErrorKind::WouldBlock.into());
 | |
|                     }
 | |
|                 }
 | |
|                 Ok(0) => {
 | |
|                     // TODO: Extract
 | |
|                     match me.tokio.poll_action().unwrap() {
 | |
|                         Async::Ready(Some(action)) => {
 | |
|                             me.inner.actions.push_back(action);
 | |
|                             continue;
 | |
|                         }
 | |
|                         Async::Ready(None) => {
 | |
|                             return Ok(0);
 | |
|                         }
 | |
|                         Async::NotReady => {
 | |
|                             return Err(io::ErrorKind::WouldBlock.into());
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 ret => return ret,
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     pub fn async_write(me: &mut Mock, src: &[u8]) -> io::Result<usize> {
 | |
|         loop {
 | |
|             if let Some(ref mut sleep) = me.tokio.sleep {
 | |
|                 let res = try!(sleep.poll());
 | |
| 
 | |
|                 if !res.is_ready() {
 | |
|                     return Err(io::ErrorKind::WouldBlock.into());
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // If a sleep is set, it has already fired
 | |
|             me.tokio.sleep = None;
 | |
| 
 | |
|             match me.inner.write(src) {
 | |
|                 Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
 | |
|                     if let Some(rem) = me.inner.remaining_wait() {
 | |
|                         me.tokio.sleep = Some(me.tokio.timer.sleep(rem));
 | |
|                     } else {
 | |
|                         panic!("unexpected WouldBlock");
 | |
|                     }
 | |
|                 }
 | |
|                 Ok(0) => {
 | |
|                     // TODO: Is this correct?
 | |
|                     if !me.inner.actions.is_empty() {
 | |
|                         return Err(io::ErrorKind::WouldBlock.into());
 | |
|                     }
 | |
| 
 | |
|                     // TODO: Extract
 | |
|                     match me.tokio.poll_action().unwrap() {
 | |
|                         Async::Ready(Some(action)) => {
 | |
|                             me.inner.actions.push_back(action);
 | |
|                             continue;
 | |
|                         }
 | |
|                         Async::Ready(None) => {
 | |
|                             panic!("unexpected write");
 | |
|                         }
 | |
|                         Async::NotReady => {
 | |
|                             return Err(io::ErrorKind::WouldBlock.into());
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
|                 ret => {
 | |
|                     me.maybe_wakeup_reader();
 | |
|                     return ret;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     impl AsyncRead for Mock {
 | |
|     }
 | |
| 
 | |
|     impl AsyncWrite for Mock {
 | |
|         fn shutdown(&mut self) -> Poll<(), io::Error> {
 | |
|             Ok(Async::Ready(()))
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /// Returns `true` if called from the context of a futures-rs Task
 | |
|     pub fn is_task_ctx() -> bool {
 | |
|         use std::panic;
 | |
| 
 | |
|         // Save the existing panic hook
 | |
|         let h = panic::take_hook();
 | |
| 
 | |
|         // Install a new one that does nothing
 | |
|         panic::set_hook(Box::new(|_| {}));
 | |
| 
 | |
|         // Attempt to call the fn
 | |
|         let r = panic::catch_unwind(|| task::current()).is_ok();
 | |
| 
 | |
|         // Re-install the old one
 | |
|         panic::set_hook(h);
 | |
| 
 | |
|         // Return the result
 | |
|         r
 | |
|     }
 | |
| }
 |