use std::os::windows::io::{AsHandle, AsRawHandle, FromRawHandle, OwnedHandle};
use windows_sys::Win32::Foundation::{
CompareObjectHandles, ERROR_ACCESS_DENIED, FALSE, GetHandleInformation, HANDLE_FLAG_INHERIT,
WAIT_FAILED, WAIT_OBJECT_0, WAIT_TIMEOUT,
};
use windows_sys::Win32::System::Threading::{
CreateMutexW, EVENT_MODIFY_STATE, ResetEvent, SYNCHRONIZATION_SYNCHRONIZE, SetEvent,
WaitForSingleObject,
};
use super::{Access, duplicate_handle_with_access};
use crate::{Event, ResetMode};
fn manual_event(signalled: bool) -> Event {
Event::new(ResetMode::Manual, signalled).expect("create an event")
}
fn duplicate(handle: impl AsHandle, access: Access) -> OwnedHandle {
duplicate_handle_with_access(handle, access).expect("duplicate")
}
fn poll(handle: &impl AsRawHandle) -> u32 {
unsafe { WaitForSingleObject(handle.as_raw_handle(), 0) }
}
fn set_error(handle: &impl AsRawHandle) -> Option<i32> {
if unsafe { SetEvent(handle.as_raw_handle()) } == FALSE {
return std::io::Error::last_os_error().raw_os_error();
}
None
}
fn reset_error(handle: &impl AsRawHandle) -> Option<i32> {
if unsafe { ResetEvent(handle.as_raw_handle()) } == FALSE {
return std::io::Error::last_os_error().raw_os_error();
}
None
}
fn same_object(a: &impl AsRawHandle, b: &impl AsRawHandle) -> bool {
unsafe { CompareObjectHandles(a.as_raw_handle(), b.as_raw_handle()) != FALSE }
}
const DENIED: Option<i32> = Some(ERROR_ACCESS_DENIED as i32);
#[test]
fn a_duplicate_is_the_same_object_and_a_new_event_is_not() {
let event = manual_event(false);
let other = manual_event(false);
let copy = duplicate(&event, Access::SYNCHRONIZE);
assert!(same_object(&event, ©));
assert!(!same_object(&other, ©));
assert_ne!(copy.as_raw_handle(), event.as_raw_handle());
}
#[test]
fn a_synchronize_only_duplicate_can_wait_and_sees_the_signal() {
let event = manual_event(false);
let copy = duplicate(&event, Access::SYNCHRONIZE);
assert_eq!(poll(©), WAIT_TIMEOUT);
event.set().expect("set the original");
assert_eq!(poll(©), WAIT_OBJECT_0);
}
#[test]
fn a_synchronize_only_duplicate_can_neither_set_nor_reset() {
let event = manual_event(false);
let copy = duplicate(&event, Access::SYNCHRONIZE);
assert_eq!(set_error(©), DENIED);
assert_eq!(
poll(&event),
WAIT_TIMEOUT,
"the refused set changed nothing"
);
event.set().expect("set the original");
assert_eq!(reset_error(©), DENIED);
assert_eq!(
poll(&event),
WAIT_OBJECT_0,
"the refused reset changed nothing"
);
}
#[test]
fn a_duplicate_with_modify_state_changes_the_original() {
let event = manual_event(false);
let copy = duplicate(&event, Access::EVENT_MODIFY_STATE | Access::SYNCHRONIZE);
assert_eq!(set_error(©), None);
assert_eq!(poll(&event), WAIT_OBJECT_0, "set through the duplicate");
assert_eq!(reset_error(©), None);
assert_eq!(poll(&event), WAIT_TIMEOUT, "reset through the duplicate");
}
#[test]
fn a_duplicate_with_modify_state_alone_can_change_the_event_and_not_wait_on_it() {
let event = manual_event(true);
let copy = duplicate(&event, Access::EVENT_MODIFY_STATE);
assert_eq!(set_error(©), None);
assert_eq!(reset_error(©), None);
assert_eq!(poll(&event), WAIT_TIMEOUT, "the duplicate reset it");
assert_eq!(poll(©), WAIT_FAILED, "no SYNCHRONIZE, no wait");
assert_eq!(
std::io::Error::last_os_error().raw_os_error(),
DENIED,
"the wait is refused for access"
);
}
#[test]
fn a_duplicate_may_hold_rights_its_source_does_not() {
let event = manual_event(false);
let waiter = duplicate(&event, Access::SYNCHRONIZE);
assert_eq!(set_error(&waiter), DENIED, "the source cannot set");
let widened = duplicate(&waiter, Access::EVENT_MODIFY_STATE | Access::SYNCHRONIZE);
assert!(same_object(&widened, &event));
assert_eq!(set_error(&widened), None, "the duplicate can");
assert_eq!(poll(&event), WAIT_OBJECT_0);
}
#[test]
fn narrowing_and_asking_for_what_the_source_holds_both_work() {
let event = manual_event(false);
let both = duplicate(&event, Access::EVENT_MODIFY_STATE | Access::SYNCHRONIZE);
let narrowed = duplicate(&both, Access::SYNCHRONIZE);
let same = duplicate(&narrowed, Access::SYNCHRONIZE);
let nothing = duplicate(&same, Access::from_bits(0));
for copy in [&both, &narrowed, &same, ¬hing] {
assert!(same_object(copy, &event));
}
assert_eq!(set_error(&narrowed), DENIED);
assert_eq!(set_error(&same), DENIED);
}
#[test]
fn a_mask_windows_refuses_is_reported_and_makes_no_handle() {
let event = manual_event(false);
let refused = duplicate_handle_with_access(&event, Access::from_bits(u32::MAX))
.expect_err("every bit set, reserved ones included");
assert_eq!(refused.raw_os_error(), DENIED);
event.set().expect("the source is untouched by the refusal");
assert_eq!(poll(&event), WAIT_OBJECT_0);
}
#[test]
fn a_duplicate_with_no_rights_can_do_nothing() {
let event = manual_event(true);
let nothing = duplicate(&event, Access::from_bits(0));
assert_eq!(poll(¬hing), WAIT_FAILED);
assert_eq!(set_error(¬hing), DENIED);
assert_eq!(poll(&event), WAIT_OBJECT_0, "the original is untouched");
}
#[test]
fn a_duplicate_outlives_its_source() {
let event = manual_event(false);
let keeper = event.try_clone().expect("clone");
let copy = duplicate(&event, Access::SYNCHRONIZE);
drop(event);
keeper.set().expect("set through the kept clone");
assert_eq!(poll(©), WAIT_OBJECT_0);
drop(keeper);
assert_eq!(poll(©), WAIT_OBJECT_0);
}
#[test]
fn closing_a_duplicate_leaves_its_source_working() {
let event = manual_event(false);
drop(duplicate(&event, Access::SYNCHRONIZE));
event.set().expect("set after the duplicate closed");
assert_eq!(poll(&event), WAIT_OBJECT_0);
}
#[test]
fn a_duplicate_is_not_inheritable() {
let event = manual_event(false);
let copy = duplicate(&event, Access::SYNCHRONIZE);
let mut flags = 0u32;
let ok = unsafe { GetHandleInformation(copy.as_raw_handle(), &mut flags) };
assert_ne!(ok, FALSE);
assert_eq!(flags & HANDLE_FLAG_INHERIT, 0);
}
#[test]
fn any_kind_of_object_can_be_duplicated_not_only_an_event() {
let raw = unsafe { CreateMutexW(std::ptr::null(), FALSE, std::ptr::null()) };
assert!(!raw.is_null());
let mutex = unsafe { OwnedHandle::from_raw_handle(raw) };
let copy = duplicate(&mutex, Access::SYNCHRONIZE);
assert!(same_object(&mutex, ©));
assert_eq!(poll(©), WAIT_OBJECT_0, "an unowned mutex is signalled");
}
#[test]
fn the_source_may_be_borrowed_owned_or_an_event() {
let event = manual_event(false);
let owned: OwnedHandle = event.try_clone().expect("clone").into();
let from_event = duplicate(&event, Access::SYNCHRONIZE);
let from_owned = duplicate(&owned, Access::SYNCHRONIZE);
let from_borrowed = duplicate(owned.as_handle(), Access::SYNCHRONIZE);
for copy in [&from_event, &from_owned, &from_borrowed] {
assert!(same_object(copy, &event));
}
}
#[test]
fn the_named_rights_are_the_win32_ones() {
assert_eq!(Access::SYNCHRONIZE.bits(), SYNCHRONIZATION_SYNCHRONIZE);
assert_eq!(Access::EVENT_MODIFY_STATE.bits(), EVENT_MODIFY_STATE);
assert_eq!(
Access::SYNCHRONIZE.bits() & Access::EVENT_MODIFY_STATE.bits(),
0,
"the two rights are distinct bits"
);
}
#[test]
fn access_masks_combine_and_round_trip() {
let both = Access::EVENT_MODIFY_STATE | Access::SYNCHRONIZE;
assert_eq!(
both.bits(),
EVENT_MODIFY_STATE | SYNCHRONIZATION_SYNCHRONIZE
);
assert_eq!(Access::from_bits(both.bits()), both);
assert_eq!(Access::from_bits(0).bits(), 0);
assert_eq!(both | both, both, "combining is a union");
assert_ne!(both, Access::SYNCHRONIZE);
}
#[test]
fn access_is_copy_send_and_sync() {
fn assert_traits<T: Copy + Send + Sync + std::fmt::Debug + Eq + std::hash::Hash>() {}
assert_traits::<Access>();
}