use std::io;
use varta_client::{classify_send_error, BeatError, BeatOutcome, DropReason};
#[cfg(target_os = "linux")]
const ENOBUFS_FOR_THIS_OS: i32 = 105;
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
))]
const ENOBUFS_FOR_THIS_OS: i32 = 55;
#[cfg(any(target_os = "solaris", target_os = "illumos"))]
const ENOBUFS_FOR_THIS_OS: i32 = 111;
#[cfg(not(miri))]
#[test]
fn enobufs_matches_system_header() {
#[cfg(target_os = "linux")]
let header_paths: &[&str] = &[
"/usr/include/asm-generic/errno-base.h",
"/usr/include/asm-generic/errno.h",
];
#[cfg(any(
target_os = "macos",
target_os = "ios",
))]
let header_paths: &[&str] = &[
"/usr/include/sys/errno.h",
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/sys/errno.h",
"/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/sys/errno.h",
];
#[cfg(any(
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
target_os = "solaris",
target_os = "illumos",
))]
let header_paths: &[&str] = &["/usr/include/sys/errno.h"];
#[cfg(not(any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
target_os = "solaris",
target_os = "illumos",
)))]
let header_paths: &[&str] = &[];
let mut seen_enobufs = false;
'search: for path in header_paths {
let content = match std::fs::read_to_string(path) {
Ok(c) => c,
Err(_) => continue,
};
for line in content.lines() {
let trimmed = line.trim();
let Some(rest) = trimmed.strip_prefix("#define") else {
continue;
};
let rest = rest.trim_start();
if !rest.starts_with("ENOBUFS") {
continue;
}
let after = &rest["ENOBUFS".len()..];
if !after.is_empty() && !after.starts_with(|c: char| c.is_whitespace()) {
continue;
}
let value_str = after.split_whitespace().next();
let value = value_str
.unwrap_or("")
.trim_end_matches(|c: char| !c.is_ascii_digit())
.parse::<i32>()
.unwrap_or_else(|_| {
panic!(
"failed to parse ENOBUFS value from line: {line} \
(value_str={value_str:?})"
)
});
assert_eq!(
value, ENOBUFS_FOR_THIS_OS,
"ENOBUFS constant ({ENOBUFS_FOR_THIS_OS}) does not match system header \
value ({value}) — the hardcoded constant in client.rs is stale for this \
kernel / libc version"
);
seen_enobufs = true;
break 'search;
}
}
assert!(
seen_enobufs,
"ENOBUFS definition not found in system errno header; \
check header_paths and the parser"
);
}
#[cfg(any(
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "netbsd",
target_os = "openbsd",
target_os = "dragonfly",
))]
#[test]
fn enobufs_classifies_as_dropped() {
let err = io::Error::from_raw_os_error(ENOBUFS_FOR_THIS_OS);
assert!(
matches!(
classify_send_error(&err),
BeatOutcome::Dropped(DropReason::KernelQueueFull)
),
"ENOBUFS (code {ENOBUFS_FOR_THIS_OS}) should classify as Dropped(KernelQueueFull)"
);
}
#[test]
fn wouldblock_classifies_as_dropped() {
let err = io::Error::from(io::ErrorKind::WouldBlock);
assert!(matches!(
classify_send_error(&err),
BeatOutcome::Dropped(DropReason::KernelQueueFull)
));
}
#[test]
fn connection_refused_classifies_as_dropped() {
let err = io::Error::from(io::ErrorKind::ConnectionRefused);
assert!(matches!(
classify_send_error(&err),
BeatOutcome::Dropped(DropReason::NoObserver)
));
}
#[test]
fn not_found_classifies_as_no_observer() {
let err = io::Error::from(io::ErrorKind::NotFound);
assert!(matches!(
classify_send_error(&err),
BeatOutcome::Dropped(DropReason::NoObserver)
));
}
#[test]
fn connection_reset_classifies_as_peer_gone() {
let err = io::Error::from(io::ErrorKind::ConnectionReset);
assert!(matches!(
classify_send_error(&err),
BeatOutcome::Dropped(DropReason::PeerGone)
));
}
#[test]
fn not_connected_classifies_as_peer_gone() {
let err = io::Error::from(io::ErrorKind::NotConnected);
assert!(matches!(
classify_send_error(&err),
BeatOutcome::Dropped(DropReason::PeerGone)
));
}
#[test]
fn broken_pipe_classifies_as_peer_gone() {
let err = io::Error::from(io::ErrorKind::BrokenPipe);
assert!(matches!(
classify_send_error(&err),
BeatOutcome::Dropped(DropReason::PeerGone)
));
}
#[test]
fn storage_full_classifies_as_storage_full() {
let err = io::Error::from(io::ErrorKind::StorageFull);
assert!(matches!(
classify_send_error(&err),
BeatOutcome::Dropped(DropReason::StorageFull)
));
}
const _: () = {
const fn assert_copy_eq<T: Copy + Eq>() {}
assert_copy_eq::<DropReason>();
assert!(core::mem::size_of::<DropReason>() == 1);
};
#[test]
fn permission_denied_classifies_as_failed() {
let err = io::Error::from_raw_os_error(1);
assert!(
matches!(
classify_send_error(&err),
BeatOutcome::Failed(BeatError { errno: 1, .. })
),
"EPERM should classify as Failed(BeatError {{ errno: 1 }}), not Dropped"
);
}
#[test]
fn beat_error_impl_error() {
const _: fn() = || {
fn req_err<E: std::error::Error>() {}
req_err::<BeatError>();
};
}
#[test]
fn beat_error_to_io_error_roundtrip() {
let be = BeatError {
errno: 1,
kind: io::ErrorKind::PermissionDenied,
};
let io_err = be.to_io_error();
assert_eq!(io_err.raw_os_error(), Some(1));
}
#[test]
fn beat_error_unknown_errno_uses_kind() {
let be = BeatError {
errno: BeatError::UNKNOWN_ERRNO,
kind: io::ErrorKind::WouldBlock,
};
let io_err = be.to_io_error();
assert_eq!(io_err.kind(), io::ErrorKind::WouldBlock);
assert_eq!(io_err.raw_os_error(), None);
}