#![allow(dead_code)]
use mio::{event::Event, Events, Interest, Poll, Token};
use std::io::{Read, Write};
use std::ops::BitOr;
use std::panic;
use std::sync::Once;
use std::time::Duration;
use serialport::SerialPort;
#[cfg(unix)]
use std::process;
#[cfg(unix)]
use std::thread;
static LOGGING_INIT: Once = Once::new();
#[cfg(unix)]
const DEFAULT_TEST_PORT_NAMES: &str = "USB0;USB1";
#[cfg(windows)]
const DEFAULT_TEST_PORT_NAMES: &str = "COM1;COM2";
#[derive(Debug)]
pub struct Readiness(usize);
const READABLE: usize = 0b0000_0001;
const WRITABLE: usize = 0b0000_0010;
const AIO: usize = 0b0000_0100;
const LIO: usize = 0b0000_1000;
const ERROR: usize = 0b0001_0000;
const READ_CLOSED: usize = 0b0010_0000;
const WRITE_CLOSED: usize = 0b0100_0000;
const PRIORITY: usize = 0b1000_0000;
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 {
(!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())
}
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
}
}
#[must_use]
pub fn init_with_poll() -> (Poll, Events) {
let poll = Poll::new().expect("unable to create poll object");
let events = Events::with_capacity(16);
(poll, 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)
}
}
pub fn expect_events(poll: &mut Poll, events: &mut Events, mut expected: Vec<ExpectEvent>) {
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 {
}
}
if expected.is_empty() {
return;
}
}
assert!(
expected.is_empty(),
"the following expected events were not found: {expected:?}",
);
}
pub fn assert_would_block(result: std::io::Result<usize>) {
match result {
Ok(_) => panic!("unexpected OK result, expected a `WouldBlock` error"),
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {}
Err(e) => panic!("unexpected error result: {e}"),
}
}
pub fn checked_write(port: &mut mio_serial::SerialStream, data: &[u8]) {
let n = port.write(data).expect("unable to write to serial port");
assert_eq!(n, data.len(), "short write");
}
pub fn checked_read(port: &mut mio_serial::SerialStream, data: &mut [u8], expected: &[u8]) {
let n = port.read(data).expect("unable to read from serial port");
assert_eq!(n, expected.len(), "short read");
assert_eq!(&data[..n], expected);
}
pub struct Fixture {
#[cfg(unix)]
process: process::Child,
pub port_a: String,
pub port_b: String,
}
#[cfg(unix)]
impl Drop for Fixture {
fn drop(&mut self) {
log::trace!("stopping socat process (id: {})...", self.process.id());
self.process.kill().ok();
thread::sleep(Duration::from_millis(1000));
log::trace!("removing link: {:?}", self.port_a);
std::fs::remove_file(&self.port_a).ok();
log::trace!("removing link: {:?}", self.port_b);
std::fs::remove_file(&self.port_b).ok();
thread::sleep(Duration::from_millis(1000));
}
}
impl Fixture {
#[cfg(unix)]
pub fn new(port_a: &'static str, port_b: &'static str) -> Self {
use std::sync::atomic::{AtomicUsize, Ordering};
static N: AtomicUsize = AtomicUsize::new(0);
LOGGING_INIT.call_once(env_logger::init);
let n = N.fetch_add(1, Ordering::Relaxed);
let port_a = format!("{port_a}{n}");
let port_b = format!("{port_b}{n}");
let args = [format!("PTY,link={port_a}"), format!("PTY,link={port_b}")];
log::trace!("starting process: socat {} {}", args[0], args[1]);
let process = process::Command::new("socat")
.args(&args)
.spawn()
.expect("unable to spawn socat process");
log::trace!(".... done! (pid: {:?})", process.id());
thread::sleep(Duration::from_millis(1000));
Self {
process,
port_a,
port_b,
}
}
#[cfg(not(unix))]
pub fn new(port_a: &'static str, port_b: &'static str) -> Self {
LOGGING_INIT.call_once(env_logger::init);
Self {
port_a: port_a.to_string(),
port_b: port_b.to_string(),
}
}
}
pub fn setup_virtual_serial_ports() -> Fixture {
let port_names: Vec<&str> = std::option_env!("TEST_PORT_NAMES")
.unwrap_or(DEFAULT_TEST_PORT_NAMES)
.split(';')
.collect();
assert_eq!(port_names.len(), 2);
Fixture::new(port_names[0], port_names[1])
}
pub fn assert_baud_rate<P>(port: &P, expected: u32)
where
P: SerialPort,
{
let actual = port.baud_rate().expect("unable to get baud rate");
assert_eq!(actual, expected, "baud rate not equal");
}