blob: c1198dd765fcc5fda5c42796a8bc1ad50950f1f7 [file] [log] [blame]
// Not all functions are used by all tests.
#![allow(dead_code, unused_macros)]
#![cfg(any(feature = "os-poll", feature = "net"))]
use std::net::SocketAddr;
use std::ops::BitOr;
#[cfg(unix)]
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
use std::sync::Once;
use std::time::Duration;
use std::{env, fmt, fs, io};
use log::{error, warn};
use mio::event::Event;
use mio::{Events, Interest, Poll, Token};
pub fn init() {
static INIT: Once = Once::new();
INIT.call_once(|| {
env_logger::try_init().expect("unable to initialise logger");
// Remove all temporary files from previous test runs.
let dir = temp_dir();
let _ = fs::remove_dir_all(&dir);
fs::create_dir_all(&dir).expect("unable to create temporary directory");
})
}
pub fn init_with_poll() -> (Poll, Events) {
init();
let poll = Poll::new().expect("unable to create Poll instance");
let events = Events::with_capacity(16);
(poll, events)
}
pub fn assert_sync<T: Sync>() {}
pub fn assert_send<T: Send>() {}
/// An event that is expected to show up when `Poll` is polled, see
/// `expect_events`.
#[derive(Debug)]
pub struct ExpectEvent {
token: Token,
readiness: Readiness,
}
impl ExpectEvent {
pub fn new<R>(token: Token, readiness: R) -> ExpectEvent
where
R: Into<Readiness>,
{
ExpectEvent {
token,
readiness: readiness.into(),
}
}
fn matches(&self, event: &Event) -> bool {
event.token() == self.token && self.readiness.matches(event)
}
}
#[derive(Debug)]
pub struct Readiness(usize);
const READABLE: usize = 0b00_000_001;
const WRITABLE: usize = 0b00_000_010;
const AIO: usize = 0b00_000_100;
const LIO: usize = 0b00_001_000;
const ERROR: usize = 0b00_010_000;
const READ_CLOSED: usize = 0b00_100_000;
const WRITE_CLOSED: usize = 0b01_000_000;
const PRIORITY: usize = 0b10_000_000;
impl Readiness {
pub const READABLE: Readiness = Readiness(READABLE);
pub const WRITABLE: Readiness = Readiness(WRITABLE);
pub const AIO: Readiness = Readiness(AIO);
pub const LIO: Readiness = Readiness(LIO);
pub const ERROR: Readiness = Readiness(ERROR);
pub const READ_CLOSED: Readiness = Readiness(READ_CLOSED);
pub const WRITE_CLOSED: Readiness = Readiness(WRITE_CLOSED);
pub const PRIORITY: Readiness = Readiness(PRIORITY);
fn matches(&self, event: &Event) -> bool {
// If we expect a readiness then also match on the event.
// In maths terms that is p -> q, which is the same as !p || q.
(!self.is(READABLE) || event.is_readable())
&& (!self.is(WRITABLE) || event.is_writable())
&& (!self.is(AIO) || event.is_aio())
&& (!self.is(LIO) || event.is_lio())
&& (!self.is(ERROR) || event.is_error())
&& (!self.is(READ_CLOSED) || event.is_read_closed())
&& (!self.is(WRITE_CLOSED) || event.is_write_closed())
&& (!self.is(PRIORITY) || event.is_priority())
}
/// Usage: `self.is(READABLE)`.
fn is(&self, value: usize) -> bool {
self.0 & value != 0
}
}
impl BitOr for Readiness {
type Output = Self;
fn bitor(self, other: Self) -> Self {
Readiness(self.0 | other.0)
}
}
impl From<Interest> for Readiness {
fn from(interests: Interest) -> Readiness {
let mut readiness = Readiness(0);
if interests.is_readable() {
readiness.0 |= READABLE;
}
if interests.is_writable() {
readiness.0 |= WRITABLE;
}
if interests.is_aio() {
readiness.0 |= AIO;
}
if interests.is_lio() {
readiness.0 |= LIO;
}
readiness
}
}
pub fn expect_events(poll: &mut Poll, events: &mut Events, mut expected: Vec<ExpectEvent>) {
// In a lot of calls we expect more then one event, but it could be that
// poll returns the first event only in a single call. To be a bit more
// lenient we'll poll a couple of times.
for _ in 0..3 {
poll.poll(events, Some(Duration::from_millis(500)))
.expect("unable to poll");
for event in events.iter() {
let index = expected.iter().position(|expected| expected.matches(event));
if let Some(index) = index {
expected.swap_remove(index);
} else {
// Must accept sporadic events.
warn!("got unexpected event: {:?}", event);
}
}
if expected.is_empty() {
return;
}
}
assert!(
expected.is_empty(),
"the following expected events were not found: {:?}",
expected
);
}
pub fn expect_no_events(poll: &mut Poll, events: &mut Events) {
poll.poll(events, Some(Duration::from_millis(50)))
.expect("unable to poll");
if !events.is_empty() {
for event in events.iter() {
error!("unexpected event: {:?}", event);
}
panic!("received events, but didn't expect any, see above");
}
}
/// Assert that `result` is an error and the formatted error (via
/// `fmt::Display`) equals `expected_msg`.
pub fn assert_error<T, E: fmt::Display>(result: Result<T, E>, expected_msg: &str) {
match result {
Ok(_) => panic!("unexpected OK result"),
Err(err) => assert!(
err.to_string().contains(expected_msg),
"wanted: {}, got: {}",
expected_msg,
err,
),
}
}
/// Assert that the provided result is an `io::Error` with kind `WouldBlock`.
pub fn assert_would_block<T>(result: io::Result<T>) {
match result {
Ok(_) => panic!("unexpected OK result, expected a `WouldBlock` error"),
Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => {}
Err(err) => panic!("unexpected error result: {}", err),
}
}
/// Assert that `NONBLOCK` is set on `socket`.
#[cfg(unix)]
pub fn assert_socket_non_blocking<S>(socket: &S)
where
S: AsRawFd,
{
let flags = unsafe { libc::fcntl(socket.as_raw_fd(), libc::F_GETFL) };
assert!(flags & libc::O_NONBLOCK != 0, "socket not non-blocking");
}
#[cfg(windows)]
pub fn assert_socket_non_blocking<S>(_: &S) {
// No way to get this information...
}
/// Assert that `CLOEXEC` is set on `socket`.
#[cfg(unix)]
pub fn assert_socket_close_on_exec<S>(socket: &S)
where
S: AsRawFd,
{
let flags = unsafe { libc::fcntl(socket.as_raw_fd(), libc::F_GETFD) };
assert!(flags & libc::FD_CLOEXEC != 0, "socket flag CLOEXEC not set");
}
#[cfg(windows)]
pub fn assert_socket_close_on_exec<S>(_: &S) {
// Windows doesn't have this concept.
}
/// Bind to any port on localhost.
pub fn any_local_address() -> SocketAddr {
"127.0.0.1:0".parse().unwrap()
}
/// Bind to any port on localhost, using a IPv6 address.
pub fn any_local_ipv6_address() -> SocketAddr {
"[::1]:0".parse().unwrap()
}
/// Returns a path to a temporary file using `name` as filename.
pub fn temp_file(name: &'static str) -> PathBuf {
let mut path = temp_dir();
path.push(name);
path
}
/// Returns the temporary directory for Mio test files.
fn temp_dir() -> PathBuf {
let mut path = env::temp_dir();
path.push("mio_tests");
path
}
/// A checked {write, send, send_to} macro that ensures the entire buffer is
/// written.
///
/// Usage: `checked_write!(stream.write(&DATA));`
/// Also works for send(_to): `checked_write!(socket.send_to(DATA, address))`.
macro_rules! checked_write {
($socket: ident . $method: ident ( $data: expr $(, $arg: expr)* ) ) => {{
let data = $data;
let n = $socket.$method($data $(, $arg)*)
.expect("unable to write to socket");
assert_eq!(n, data.len(), "short write");
}};
}
/// A checked {read, recv, recv_from, peek, peek_from} macro that ensures the
/// current buffer is read.
///
/// Usage: `expect_read!(stream.read(&mut buf), DATA);` reads into `buf` and
/// compares it to `DATA`.
/// Also works for recv(_from): `expect_read!(socket.recv_from(&mut buf), DATA, address)`.
macro_rules! expect_read {
($socket: ident . $method: ident ( $buf: expr $(, $arg: expr)* ), $expected: expr) => {{
let n = $socket.$method($buf $(, $arg)*)
.expect("unable to read from socket");
let expected = $expected;
assert_eq!(n, expected.len());
assert_eq!(&$buf[..n], expected);
}};
// TODO: change the call sites to check the source address.
// Support for recv_from and peek_from, without checking the address.
($socket: ident . $method: ident ( $buf: expr $(, $arg: expr)* ), $expected: expr, __anywhere) => {{
let (n, _address) = $socket.$method($buf $(, $arg)*)
.expect("unable to read from socket");
let expected = $expected;
assert_eq!(n, expected.len());
assert_eq!(&$buf[..n], expected);
}};
// Support for recv_from and peek_from for `UnixDatagram`s.
($socket: ident . $method: ident ( $buf: expr $(, $arg: expr)* ), $expected: expr, path: $source: expr) => {{
let (n, path) = $socket.$method($buf $(, $arg)*)
.expect("unable to read from socket");
let expected = $expected;
let source = $source;
assert_eq!(n, expected.len());
assert_eq!(&$buf[..n], expected);
assert_eq!(
path.as_pathname().expect("failed to get path name"),
source
);
}};
// Support for recv_from and peek_from for `UdpSocket`s.
($socket: ident . $method: ident ( $buf: expr $(, $arg: expr)* ), $expected: expr, $source: expr) => {{
let (n, address) = $socket.$method($buf $(, $arg)*)
.expect("unable to read from socket");
let expected = $expected;
let source = $source;
assert_eq!(n, expected.len());
assert_eq!(&$buf[..n], expected);
assert_eq!(address, source);
}};
}