const BEFORE_COM : Duration = Duration::from_secs(if std::option_env!("TESTFAST").is_some() { 1 } else { 20 });
const WITH_COM : Duration = Duration::from_secs(if std::option_env!("TESTFAST").is_some() { 1 } else { 20 });
use xinput::*;
use mmrbi::*;
use winapi::shared::winerror::*;
use winapi::um::combaseapi::*;
use winapi::um::objbase::*;
use std::ptr::null_mut;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::Relaxed;
use std::thread::sleep;
use std::time::Duration;
macro_rules! fuzz {
( $expr:expr $(, $error:expr)* $(,)? ) => {
fuzz(stringify!($expr), file!(), line!(), &[$(($error).into()),*], $expr)
}
}
#[test] fn torment() {
let should_init_com = Arc::new(AtomicBool::new(false));
let continue_running = Arc::new(AtomicBool::new(true));
let threads = (0 .. 8).map(|thread_idx|{
let should_init_com = should_init_com.clone();
let continue_running = continue_running.clone();
std::thread::spawn(move || {
let mut initialized_com = false;
while !initialized_com || continue_running.load(Relaxed) {
if !initialized_com && should_init_com.load(Relaxed) {
initialized_com = true;
initialize_com_for_the_first_time_on_this_thread(thread_idx & 1 != 0);
}
for i in 0 ..= 100 {
let _ = xinput::enable(i>>2 & 1 != 0);
}
fuzz!(
#[allow(deprecated)] |u| xinput::get_audio_device_ids(u),
error::BAD_ARGUMENTS,
error::DEVICE_NOT_CONNECTED, error::INVALID_FUNCTION,
);
fuzz!(
#[allow(deprecated)] |u| xinput::get_dsound_audio_device_guids(u),
error::BAD_ARGUMENTS,
error::DEVICE_NOT_CONNECTED,
error::INVALID_FUNCTION,
);
for bt in [
xinput::BatteryDevType::Gamepad,
xinput::BatteryDevType::Headset,
xinput::BatteryDevType::from_unchecked(2),
xinput::BatteryDevType::from_unchecked(42),
xinput::BatteryDevType::from_unchecked(255),
].iter().copied() {
fuzz!(
|u| xinput::get_battery_information(u, bt),
error::BAD_ARGUMENTS,
error::DEVICE_NOT_CONNECTED,
error::INVALID_FUNCTION,
);
}
for flag in [
xinput::Flag::None,
xinput::Flag::Gamepad,
xinput::Flag::from_unchecked(2),
xinput::Flag::from_unchecked(4),
xinput::Flag::from_unchecked(8),
xinput::Flag::from_unchecked(16),
xinput::Flag::from_unchecked(32),
xinput::Flag::from_unchecked(64),
xinput::Flag::from_unchecked(128),
xinput::Flag::from_unchecked(!0),
].iter().copied() {
fuzz!(
|u| xinput::get_capabilities(u, flag),
error::BAD_ARGUMENTS,
error::DEVICE_NOT_CONNECTED,
);
}
fuzz!(
|u| xinput::get_keystroke(u, ()),
error::BAD_ARGUMENTS,
error::DEVICE_NOT_CONNECTED,
error::INVALID_FUNCTION,
);
fuzz!(
|u| xinput::get_state(u),
error::BAD_ARGUMENTS,
error::DEVICE_NOT_CONNECTED,
);
#[cfg(feature = "undocumented")] fuzz!(
#[allow(deprecated)] |u| xinput::get_state_ex(u),
error::BAD_ARGUMENTS,
error::DEVICE_NOT_CONNECTED,
);
for enable in [false, true].iter().copied() {
let _ = xinput::enable(enable);
for vibration in [
xinput::Vibration::default(),
xinput::Vibration { left_motor_speed: 1, right_motor_speed: 1 },
].iter().copied() {
fuzz!(
|u| xinput::set_state(u, vibration),
error::BAD_ARGUMENTS,
error::DEVICE_NOT_CONNECTED,
);
}
}
}
})
});
status!("Testing", "without COM initialized ({:?})", BEFORE_COM);
sleep(BEFORE_COM);
status!("Testing", "with COM initialized ({:?})", WITH_COM);
should_init_com.store(true, Relaxed);
sleep(WITH_COM);
status!("Testing", "teardown");
continue_running.store(false, Relaxed);
for thread in threads { thread.join().unwrap() }
}
fn initialize_com_for_the_first_time_on_this_thread(mta: bool) {
match unsafe { CoInitializeEx(null_mut(), if mta { COINIT_MULTITHREADED } else { COINIT_APARTMENTTHREADED }) } {
S_OK => {},
S_FALSE => fatal!("CoInitializeEx(nullptr, ...) returned S_FALSE: COM was already initialized on this thread! Tests were trying to ensure xinput is sound to use pre-COM..."),
err => fatal!("CoInitializeEx(nullptr, ...) returned 0x{:08X} (unexpected {})", err, if SUCCEEDED(err) { "success code" } else { "error" }),
}
}
fn fuzz<T>(title: &str, file: &str, line: u32, codes: &[xinput::error::Kind], f: impl Fn(u32) -> Result<T, Error>) {
for u in [0, 1, 2, 3, xuser::INDEX_ANY, 4, 42, 67, 254] {
for _ in 0 ..= 100 {
if let Err(err) = f(u) {
let kind = err.kind();
if !codes.contains(&kind) { fatal!(at: file, line: line as _, "when fuzzing {}: unexpected error {:?}", title, kind); }
}
}
}
for u in 0 ..= 255 {
if let Err(err) = f(u) {
let kind = err.kind();
if !codes.contains(&kind) { fatal!(at: file, line: line as _, "when fuzzing {}: unexpected error {:?}", title, kind); }
}
}
}