use std::path::Path;
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
use input::{AsRaw as _, Libinput, LibinputInterface};
use crate::libinput_log::{self, LogPriority};
fn log_guard() -> MutexGuard<'static, ()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(()))
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
}
struct Refuse;
impl LibinputInterface for Refuse {
fn open_restricted(&mut self, _path: &Path, _flags: i32) -> Result<std::os::fd::OwnedFd, i32> {
Err(-libc_enoent())
}
fn close_restricted(&mut self, _fd: std::os::fd::OwnedFd) {}
}
const fn libc_enoent() -> i32 {
2
}
#[test]
fn libinput_diagnostics_arrive_through_drmkit_log() {
let _guard = log_guard();
let captured: Arc<Mutex<Vec<(drmkit_log::LogLevel, String)>>> = Arc::default();
let sink = Arc::clone(&captured);
drmkit_log::set_log_sink(move |level, message| {
sink.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push((level, message.to_owned()));
});
let level = drmkit_log::log_level();
drmkit_log::set_log_level(drmkit_log::LogLevel::Debug);
let mut libinput = Libinput::new_from_path(Refuse);
unsafe {
libinput_log::install(libinput.as_raw().cast_mut().cast(), LogPriority::Debug);
}
assert!(
libinput
.path_add_device("/dev/input/there-is-no-such-device")
.is_none(),
"the opener refused it"
);
drmkit_log::set_log_level(level);
drmkit_log::reset_log_sink();
let captured = captured
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
assert!(
captured
.iter()
.any(|(_, message)| message.contains("[libinput]")),
"libinput said nothing through the sink: {captured:?}"
);
assert!(
captured
.iter()
.any(|(_, message)| message.contains("there-is-no-such-device")),
"and it was the message about the device: {captured:?}"
);
assert!(
captured.iter().all(|(_, message)| !message.ends_with('\n')),
"with libinput's newline taken off: {captured:?}"
);
}
#[test]
fn an_overlong_message_is_truncated_rather_than_overrunning() {
let _guard = log_guard();
let captured: Arc<Mutex<Vec<String>>> = Arc::default();
let sink = Arc::clone(&captured);
drmkit_log::set_log_sink(move |_, message| {
sink.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner)
.push(message.to_owned());
});
let level = drmkit_log::log_level();
drmkit_log::set_log_level(drmkit_log::LogLevel::Debug);
let mut libinput = Libinput::new_from_path(Refuse);
unsafe {
libinput_log::install(libinput.as_raw().cast_mut().cast(), LogPriority::Debug);
}
let path = format!("/dev/input/{}", "e".repeat(4096));
libinput.path_add_device(&path);
drmkit_log::set_log_level(level);
drmkit_log::reset_log_sink();
let captured = captured
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let longest = captured.iter().map(String::len).max().unwrap_or(0);
assert!(longest > 0, "libinput said nothing");
assert!(
longest < 1100,
"a 4 KiB path came through whole: {longest} bytes"
);
}
#[test]
fn a_seat_that_does_not_exist_opens_with_nothing_on_it() {
let seat = crate::seat::Seat::open(&crate::seat::SeatOptions {
seat: "there-is-no-such-seat".to_owned(),
..crate::seat::SeatOptions::default()
})
.expect("opens anyway");
assert_eq!(seat.keyboard_count(), 0);
}
#[test]
fn a_seat_error_says_what_failed() {
let message = crate::InputError::Seat("seat0".to_owned()).to_string();
assert_eq!(
message,
"libinput could not enumerate the seat through udev: seat0"
);
assert!(!message.contains("no such seat"), "{message}");
}