Tests for nodes/listeners.rs (#541)

* Add unit tests for core::nodes::NodeStream

* Move DummyMuxer to core/tests

* Address grumbles

* Impl Debug for SubstreamRef<P>

* Add test for poll()

* Don't need to open a substream

* pretty printer test

* More tests for NodeStream poll()

* ListenerStream unit tests: transport() and listeners()

* Tests for nodes/listeners.rs

* Add a few tests to help illustrate the "drowning" behaviour of busy listeners

* Address grumbles

* Remove non-project specific stuff

* Address grumbles

* Prefer freestanding function
This commit is contained in:
David
2018-10-10 16:27:07 +02:00
committed by Pierre Krieger
parent dec7cd13b2
commit 0c7f313146
5 changed files with 295 additions and 10 deletions

View File

@ -7,7 +7,7 @@ Right now everything including the crate organization is very much Work in Progr
## Documentation
This repository includes a facade crate named `libp2p`, which reexports the rest of the repository.
This repository includes a façade crate named `libp2p`, which reexports the rest of the repository.
For documentation, you are encouraged to clone this repository or add `libp2p` as a dependency in
your Cargo.toml and run `cargo doc`.

View File

@ -45,21 +45,21 @@ pub trait NodeHandler<TSubstream> {
/// The handler is responsible for upgrading the substream to whatever protocol it wants.
fn inject_substream(&mut self, substream: TSubstream, endpoint: NodeHandlerEndpoint<Self::OutboundOpenInfo>);
/// Indicates the handler that the inbound part of the muxer has been closed, and that
/// Indicates to the handler that the inbound part of the muxer has been closed, and that
/// therefore no more inbound substream will be produced.
fn inject_inbound_closed(&mut self);
/// Indicates the handler that an outbound substream failed to open because the outbound
/// Indicates to the handler that an outbound substream failed to open because the outbound
/// part of the muxer has been closed.
fn inject_outbound_closed(&mut self, user_data: Self::OutboundOpenInfo);
/// Indicates the handler that the multiaddr future has resolved.
/// Indicates to the handler that the multiaddr future has resolved.
fn inject_multiaddr(&mut self, multiaddr: Result<Multiaddr, IoError>);
/// Injects an event coming from the outside in the handler.
/// Injects an event coming from the outside into the handler.
fn inject_event(&mut self, event: Self::InEvent);
/// Indicates the node that it should shut down. After that, it is expected that `poll()`
/// Indicates that the node that it should shut down. After that, it is expected that `poll()`
/// returns `Ready(None)` as soon as possible.
///
/// This method allows an implementation to perform a graceful shutdown of the substreams, and
@ -78,7 +78,7 @@ pub enum NodeHandlerEndpoint<TOutboundOpenInfo> {
Listener,
}
/// Event produces by a handler.
/// Event produced by a handler.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum NodeHandlerEvent<TOutboundOpenInfo, TCustom> {
/// Require a new outbound substream to be opened with the remote.
@ -88,7 +88,7 @@ pub enum NodeHandlerEvent<TOutboundOpenInfo, TCustom> {
Custom(TCustom),
}
/// Event produces by a handler.
/// Event produced by a handler.
impl<TOutboundOpenInfo, TCustom> NodeHandlerEvent<TOutboundOpenInfo, TCustom> {
/// If this is `OutboundSubstreamRequest`, maps the content to something else.
#[inline]
@ -173,7 +173,7 @@ where
self.node.is_none()
}
/// Indicates the handled node that it should shut down. After calling this method, the
/// Indicates to the handled node that it should shut down. After calling this method, the
/// `Stream` will end in the not-so-distant future.
///
/// After this method returns, `is_shutting_down()` should return true.

View File

@ -37,6 +37,7 @@ where
}
/// A single active listener.
#[derive(Debug)]
struct Listener<TTrans>
where
TTrans: Transport,
@ -161,7 +162,7 @@ where
}
}
// We register the current task to be waken up if a new listener is added.
// We register the current task to be woken up if a new listener is added.
Async::NotReady
}
}
@ -220,9 +221,42 @@ where
#[cfg(test)]
mod tests {
extern crate libp2p_tcp_transport;
use super::*;
use transport;
use tokio::runtime::current_thread::Runtime;
use std::io;
use futures::{future::{self}, stream};
use tests::dummy_transport::{DummyTransport, ListenerState};
fn set_listener_state(ls: &mut ListenersStream<DummyTransport>, idx: usize, state: ListenerState) {
let l = &mut ls.listeners[idx];
l.listener =
match state {
ListenerState::Error => {
let stream = stream::poll_fn(|| future::err(io::Error::new(io::ErrorKind::Other, "oh noes")).poll() );
Box::new(stream)
}
ListenerState::Ok(async) => {
match async {
Async::NotReady => {
let stream = stream::poll_fn(|| Ok(Async::NotReady));
Box::new(stream)
}
Async::Ready(Some(n)) => {
let addr = l.address.clone();
let stream = stream::iter_ok(n..)
.map(move |stream| future::ok( (stream, future::ok(addr.clone())) ));
Box::new(stream)
}
Async::Ready(None) => {
let stream = stream::empty();
Box::new(stream)
}
}
}
};
}
#[test]
fn incoming_event() {
@ -251,4 +285,161 @@ mod tests {
let mut runtime = Runtime::new().unwrap();
runtime.block_on(future).unwrap();
}
#[test]
fn listener_stream_returns_transport() {
let t = DummyTransport::new();
let ls = ListenersStream::new(t);
assert_eq!(ls.transport(), &t);
}
#[test]
fn listener_stream_can_iterate_over_listeners() {
let t = DummyTransport::new();
let addr1 = "/ip4/127.0.0.1/tcp/1234".parse::<Multiaddr>().expect("bad multiaddr");
let addr2 = "/ip4/127.0.0.1/tcp/4321".parse::<Multiaddr>().expect("bad multiaddr");
let expected_addrs = vec![addr1.to_string(), addr2.to_string()];
let mut ls = ListenersStream::new(t);
ls.listen_on(addr1).expect("listen_on failed");
ls.listen_on(addr2).expect("listen_on failed");
let listener_addrs = ls.listeners().map(|ma| ma.to_string() ).collect::<Vec<String>>();
assert_eq!(listener_addrs, expected_addrs);
}
#[test]
fn listener_stream_poll_without_listeners_is_not_ready() {
let t = DummyTransport::new();
let mut ls = ListenersStream::new(t);
assert_matches!(ls.poll(), Async::NotReady);
}
#[test]
fn listener_stream_poll_with_listeners_that_arent_ready_is_not_ready() {
let t = DummyTransport::new();
let addr = "/ip4/127.0.0.1/tcp/1234".parse::<Multiaddr>().expect("bad multiaddr");
let mut ls = ListenersStream::new(t);
ls.listen_on(addr).expect("listen_on failed");
set_listener_state(&mut ls, 0, ListenerState::Ok(Async::NotReady));
assert_matches!(ls.poll(), Async::NotReady);
assert_eq!(ls.listeners.len(), 1); // listener is still there
}
#[test]
fn listener_stream_poll_with_ready_listeners_is_ready() {
let mut t = DummyTransport::new();
t.set_initial_listener_state(ListenerState::Ok(Async::Ready(Some(1))));
let addr1 = "/ip4/127.0.0.1/tcp/1234".parse::<Multiaddr>().expect("bad multiaddr");
let addr2 = "/ip4/127.0.0.2/tcp/4321".parse::<Multiaddr>().expect("bad multiaddr");
let mut ls = ListenersStream::new(t);
ls.listen_on(addr1).expect("listen_on failed");
ls.listen_on(addr2).expect("listen_on failed");
assert_matches!(ls.poll(), Async::Ready(Some(listeners_event)) => {
assert_matches!(listeners_event, ListenersEvent::Incoming{mut upgrade, listen_addr} => {
assert_eq!(listen_addr.to_string(), "/ip4/127.0.0.2/tcp/4321");
assert_matches!(upgrade.poll().unwrap(), Async::Ready(tup) => {
assert_matches!(tup, (1, _))
});
})
});
// TODO: When several listeners are continuously Async::Ready
// admittetdly a corner case the last one is processed first and then
// put back *last* on the pile. This means that at the next poll() it
// will get polled again and if it always has data to yield, it will
// effectively block all other listeners from being "heard". One way
// around this is to switch to using a `VecDeque` to keep the listeners
// collection, and instead of pushing the processed item to the end of
// the list, stick it on top so that it'll be processed *last* instead
// during the next poll. This might also get us a performance win as
// even in the normal case, the most recently polled listener is more
// unlikely to have anything to yield than the others so we might avoid
// a few unneeded poll calls.
// Make the second listener return NotReady so we get the first listener next poll()
set_listener_state(&mut ls, 1, ListenerState::Ok(Async::NotReady));
assert_matches!(ls.poll(), Async::Ready(Some(listeners_event)) => {
assert_matches!(listeners_event, ListenersEvent::Incoming{mut upgrade, listen_addr} => {
assert_eq!(listen_addr.to_string(), "/ip4/127.0.0.1/tcp/1234");
assert_matches!(upgrade.poll().unwrap(), Async::Ready(tup) => {
assert_matches!(tup, (1, _))
});
})
});
assert_eq!(ls.listeners.len(), 2);
}
#[test]
fn listener_stream_poll_with_closed_listener_emits_closed_event() {
let t = DummyTransport::new();
let addr = "/ip4/127.0.0.1/tcp/1234".parse::<Multiaddr>().expect("bad multiaddr");
let mut ls = ListenersStream::new(t);
ls.listen_on(addr).expect("listen_on failed");
set_listener_state(&mut ls, 0, ListenerState::Ok(Async::Ready(None)));
assert_matches!(ls.poll(), Async::Ready(Some(listeners_event)) => {
assert_matches!(listeners_event, ListenersEvent::Closed{..})
});
assert_eq!(ls.listeners.len(), 0); // it's gone
}
#[test]
fn listener_stream_poll_with_erroring_listener_emits_closed_event() {
let mut t = DummyTransport::new();
t.set_initial_listener_state(ListenerState::Ok(Async::Ready(Some(1))));
let addr = "/ip4/127.0.0.1/tcp/1234".parse::<Multiaddr>().expect("bad multiaddr");
let mut ls = ListenersStream::new(t);
ls.listen_on(addr).expect("listen_on failed");
set_listener_state(&mut ls, 0, ListenerState::Error); // simulate an error on the socket
assert_matches!(ls.poll(), Async::Ready(Some(listeners_event)) => {
assert_matches!(listeners_event, ListenersEvent::Closed{..})
});
assert_eq!(ls.listeners.len(), 0); // it's gone
}
#[test]
fn listener_stream_poll_chatty_listeners_may_drown_others() {
let mut t = DummyTransport::new();
t.set_initial_listener_state(ListenerState::Ok(Async::Ready(Some(1))));
let mut ls = ListenersStream::new(t);
for n in 0..4 {
let addr = format!("/ip4/127.0.0.{}/tcp/123{}", n, n).parse::<Multiaddr>().expect("bad multiaddr");
ls.listen_on(addr).expect("listen_on failed");
}
// polling processes listeners in reverse order
// Only the last listener ever gets processed
for _n in 0..10 {
assert_matches!(ls.poll(), Async::Ready(Some(ListenersEvent::Incoming{listen_addr, ..})) => {
assert_eq!(listen_addr.to_string(), "/ip4/127.0.0.3/tcp/1233")
})
}
// Make last listener NotReady so now only the third listener is processed
set_listener_state(&mut ls, 3, ListenerState::Ok(Async::NotReady));
for _n in 0..10 {
assert_matches!(ls.poll(), Async::Ready(Some(ListenersEvent::Incoming{listen_addr, ..})) => {
assert_eq!(listen_addr.to_string(), "/ip4/127.0.0.2/tcp/1232")
})
}
}
#[test]
fn listener_stream_poll_processes_listeners_as_expected_if_they_are_not_yielding_continuously() {
let mut t = DummyTransport::new();
t.set_initial_listener_state(ListenerState::Ok(Async::Ready(Some(1))));
let mut ls = ListenersStream::new(t);
for n in 0..4 {
let addr = format!("/ip4/127.0.0.{}/tcp/123{}", n, n).parse::<Multiaddr>().expect("bad multiaddr");
ls.listen_on(addr).expect("listen_on failed");
}
// If the listeners do not yield items continuously (the normal case) we
// process them in the expected, reverse, order.
for n in (0..4).rev() {
assert_matches!(ls.poll(), Async::Ready(Some(ListenersEvent::Incoming{listen_addr, ..})) => {
assert_eq!(listen_addr.to_string(), format!("/ip4/127.0.0.{}/tcp/123{}", n, n));
});
// kick the last listener (current) to NotReady state
set_listener_state(&mut ls, 3, ListenerState::Ok(Async::NotReady));
}
}
}

View File

@ -0,0 +1,92 @@
// Copyright 2018 Parity Technologies (UK) Ltd.
//
// 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.
//! `DummyTransport` is a `Transport` used in tests. It implements a bare-bones
//! version of the trait along with a way to setup the transport listeners with
//! an initial state to facilitate testing.
use futures::prelude::*;
use futures::{future::{self, FutureResult}, stream};
use {Multiaddr, Transport};
use std::io;
#[derive(Debug, PartialEq, Clone, Copy)]
pub(crate) enum ListenerState {
/// The `usize` indexes items produced by the listener
Ok(Async<Option<usize>>),
Error
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub(crate) struct DummyTransport {
listener_state: ListenerState,
}
impl DummyTransport {
pub(crate) fn new() -> Self { DummyTransport{ listener_state: ListenerState::Ok(Async::NotReady) }}
pub(crate) fn set_initial_listener_state(&mut self, state: ListenerState) {
self.listener_state = state;
}
}
impl Transport for DummyTransport {
type Output = usize;
type Listener = Box<Stream<Item=Self::ListenerUpgrade, Error=io::Error> + Send>;
type ListenerUpgrade = FutureResult<(Self::Output, Self::MultiaddrFuture), io::Error>;
type MultiaddrFuture = FutureResult<Multiaddr, io::Error>;
type Dial = Box<Future<Item=(Self::Output, Self::MultiaddrFuture), Error=io::Error> + Send>;
fn listen_on(self, addr: Multiaddr) -> Result<(Self::Listener, Multiaddr), (Self, Multiaddr)>
where
Self: Sized
{
let addr2 = addr.clone();
match self.listener_state {
ListenerState::Ok(async) => {
let tupelize = move |stream| future::ok( (stream, future::ok(addr.clone())) );
Ok(match async {
Async::NotReady => {
let stream = stream::poll_fn(|| Ok(Async::NotReady)).map(tupelize);
(Box::new(stream), addr2)
},
Async::Ready(Some(n)) => {
let stream = stream::iter_ok(n..).map(tupelize);
(Box::new(stream), addr2)
},
Async::Ready(None) => {
let stream = stream::empty();
(Box::new(stream), addr2)
},
})
}
ListenerState::Error => Err( (self, addr2) )
}
}
fn dial(self, _addr: Multiaddr) -> Result<Self::Dial, (Self, Multiaddr)>
where
Self: Sized
{
unimplemented!();
}
fn nat_traversal(&self, _server: &Multiaddr, _observed: &Multiaddr) -> Option<Multiaddr> {
unimplemented!();
}
}

View File

@ -20,3 +20,5 @@
#[cfg(test)]
pub(crate) mod dummy_muxer;
#[cfg(test)]
pub(crate) mod dummy_transport;