mod config;
use cfg_if::cfg_if;
use config::{hw_config, HardwareConfig};
use rstest::rstest;
use serialport::ErrorKind;
use serialport::SerialPort;
cfg_if! {
if #[cfg(unix)] {
use std::os::unix::prelude::*;
use nix::fcntl::FlockArg;
use nix::ioctl_none_bad;
ioctl_none_bad!(tiocexcl, libc::TIOCEXCL);
ioctl_none_bad!(tiocnxcl, libc::TIOCNXCL);
}
}
#[cfg(unix)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum LockMode {
Exclusive,
Shared,
}
#[cfg(unix)]
impl LockMode {
pub fn is_exclusive(&self) -> bool {
match self {
LockMode::Exclusive => true,
LockMode::Shared => false,
}
}
}
#[cfg(unix)]
impl From<LockMode> for FlockArg {
fn from(exclusivity: LockMode) -> FlockArg {
match exclusivity {
LockMode::Exclusive => FlockArg::LockExclusiveNonblock,
LockMode::Shared => FlockArg::LockSharedNonblock,
}
}
}
mod checks {
use super::*;
#[cfg(unix)]
pub use std::fs::File;
#[cfg(unix)]
#[must_use]
pub fn open_file_successful(hw_config: &HardwareConfig) -> File {
File::options()
.read(true)
.write(true)
.open(&hw_config.port_1)
.unwrap()
}
pub fn open_port_fails(hw_config: &HardwareConfig) {
let port = serialport::new(&hw_config.port_1, 115200).open();
assert!(port.is_err());
assert_eq!(port.unwrap_err().kind(), ErrorKind::NoDevice);
}
#[cfg(unix)]
pub fn open_port_with_lock_mode_fails(hw_config: &HardwareConfig, lock_mode: LockMode) {
let port = serialport::new(&hw_config.port_1, 115200)
.exclusive(lock_mode.is_exclusive())
.open();
assert!(port.is_err());
assert_eq!(port.unwrap_err().kind(), ErrorKind::NoDevice);
}
#[must_use]
pub fn open_port_successful(hw_config: &HardwareConfig) -> Box<dyn SerialPort> {
serialport::new(&hw_config.port_1, 115200).open().unwrap()
}
#[cfg(unix)]
#[must_use]
pub fn open_port_with_lock_mode_successful(
hw_config: &HardwareConfig,
lock_mode: LockMode,
) -> Box<dyn SerialPort> {
serialport::new(&hw_config.port_1, 115200)
.exclusive(lock_mode.is_exclusive())
.open()
.unwrap()
}
}
#[rstest]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
fn opening_multiple_times(hw_config: HardwareConfig) {
for _ in 0..3 {
let _ = checks::open_port_successful(&hw_config);
}
}
#[cfg(unix)]
#[rstest]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Exclusive)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Shared)]
fn opening_multiple_times_with_lock_mode(hw_config: HardwareConfig, #[case] lock_mode: LockMode) {
for _ in 0..3 {
let _ = checks::open_port_with_lock_mode_successful(&hw_config, lock_mode);
}
}
mod second_exclusive_open {
use super::*;
#[rstest]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
fn fails_after_open(hw_config: HardwareConfig) {
let _first = checks::open_port_successful(&hw_config);
checks::open_port_fails(&hw_config);
}
#[rstest]
#[cfg(unix)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Exclusive)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Shared)]
fn fails_after_open_with_lock_mode(hw_config: HardwareConfig, #[case] first_mode: LockMode) {
let _first = checks::open_port_with_lock_mode_successful(&hw_config, first_mode);
checks::open_port_with_lock_mode_fails(&hw_config, LockMode::Exclusive);
}
#[rstest]
#[cfg(unix)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Exclusive)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Shared)]
fn fails_after_lock(hw_config: HardwareConfig, #[case] first_mode: LockMode) {
let first = checks::open_file_successful(&hw_config);
match first_mode {
LockMode::Exclusive => first.lock().unwrap(),
LockMode::Shared => first.lock_shared().unwrap(),
}
checks::open_port_with_lock_mode_fails(&hw_config, LockMode::Exclusive);
}
#[rstest]
#[cfg(unix)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(FlockArg::LockExclusiveNonblock)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(FlockArg::LockSharedNonblock)]
fn fails_after_flock(hw_config: HardwareConfig, #[case] first_mode: FlockArg) {
let first = checks::open_file_successful(&hw_config);
let fd = first.as_raw_fd();
nix::fcntl::flock(fd, first_mode).unwrap();
checks::open_port_with_lock_mode_fails(&hw_config, LockMode::Exclusive);
}
#[rstest]
#[cfg(unix)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
fn fails_after_tiocexcl(hw_config: HardwareConfig) {
let first = checks::open_file_successful(&hw_config);
unsafe { tiocexcl(first.as_raw_fd()).unwrap() };
checks::open_port_with_lock_mode_fails(&hw_config, LockMode::Exclusive);
}
#[rstest]
#[cfg(unix)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
fn succeeds_after_tiocnxcl(hw_config: HardwareConfig) {
let first = checks::open_file_successful(&hw_config);
unsafe { tiocnxcl(first.as_raw_fd()).unwrap() };
let _second = checks::open_port_with_lock_mode_successful(&hw_config, LockMode::Exclusive);
}
}
#[cfg(unix)]
mod second_non_exclusive_open {
use super::*;
#[rstest]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Exclusive)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Shared)]
fn after_open(hw_config: HardwareConfig, #[case] first_mode: LockMode) {
let _first = checks::open_port_with_lock_mode_successful(&hw_config, first_mode);
match first_mode {
LockMode::Exclusive => {
checks::open_port_with_lock_mode_fails(&hw_config, LockMode::Shared)
}
LockMode::Shared => {
let _ = checks::open_port_with_lock_mode_successful(&hw_config, LockMode::Shared);
}
}
}
#[rstest]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Exclusive)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Shared)]
fn after_lock(hw_config: HardwareConfig, #[case] first_mode: LockMode) {
let first = checks::open_file_successful(&hw_config);
match first_mode {
LockMode::Exclusive => first.lock().unwrap(),
LockMode::Shared => first.lock_shared().unwrap(),
}
match first_mode {
LockMode::Exclusive => {
checks::open_port_with_lock_mode_fails(&hw_config, LockMode::Shared)
}
LockMode::Shared => {
let _ = checks::open_port_with_lock_mode_successful(&hw_config, LockMode::Shared);
}
}
}
#[rstest]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Exclusive)]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
#[case(LockMode::Shared)]
fn after_flock(hw_config: HardwareConfig, #[case] first_mode: LockMode) {
let first = checks::open_file_successful(&hw_config);
nix::fcntl::flock(first.as_raw_fd(), first_mode.into()).unwrap();
match first_mode {
LockMode::Exclusive => {
checks::open_port_with_lock_mode_fails(&hw_config, LockMode::Shared)
}
LockMode::Shared => {
let _ = checks::open_port_with_lock_mode_successful(&hw_config, LockMode::Shared);
}
}
}
#[rstest]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
fn fails_after_tiocexcl(hw_config: HardwareConfig) {
let first = checks::open_file_successful(&hw_config);
unsafe { tiocexcl(first.as_raw_fd()).unwrap() };
checks::open_port_with_lock_mode_fails(&hw_config, LockMode::Exclusive);
}
#[rstest]
#[cfg_attr(not(feature = "hardware-tests"), ignore)]
fn succeeds_after_tiocnxcl(hw_config: HardwareConfig) {
let first = checks::open_file_successful(&hw_config);
unsafe { tiocnxcl(first.as_raw_fd()).unwrap() };
let _second = checks::open_port_with_lock_mode_successful(&hw_config, LockMode::Exclusive);
}
}