use std::fs::File;
use std::os::windows::io::{AsRawHandle, OwnedHandle};
use windows_sys::Win32::Foundation::{CloseHandle, ERROR_INVALID_HANDLE, FALSE, HANDLE};
use wtf_string::Wtf16String;
use super::CloseRequest;
use crate::handle::tests::{Fixture, handle_allocation};
use crate::watch::{NotifyFilter, WatchDirectory};
use crate::{CapturedHandle, prepare};
fn was_still_open(handle: HANDLE) -> bool {
unsafe { CloseHandle(handle) != FALSE }
}
fn captured_duplicate(fixture: &Fixture) -> OwnedHandle {
let file = fixture.open_file();
CapturedHandle::capture(std::os::windows::io::AsHandle::as_handle(&file))
.expect("capture the file handle")
.into_owned_handle()
}
#[test]
fn an_ordinary_handle_closes() {
let _allocating = handle_allocation()
.write()
.expect("the lock is not poisoned");
let fixture = Fixture::new("close-ordinary");
let owned = captured_duplicate(&fixture);
let raw = owned.as_raw_handle().cast();
CloseRequest::for_handle(owned)
.perform()
.expect("close the handle");
assert!(
!was_still_open(raw),
"the handle must be closed exactly once, by the request"
);
}
#[test]
fn a_dropped_request_still_closes_its_handle() {
let _allocating = handle_allocation()
.write()
.expect("the lock is not poisoned");
let fixture = Fixture::new("close-dropped");
let owned = captured_duplicate(&fixture);
let raw = owned.as_raw_handle().cast();
drop(CloseRequest::for_handle(owned));
assert!(
!was_still_open(raw),
"dropping the request must close the handle, not leak it"
);
}
#[test]
fn performing_the_request_does_not_also_close_on_drop() {
let _allocating = handle_allocation()
.write()
.expect("the lock is not poisoned");
let fixture = Fixture::new("close-once");
let owned = captured_duplicate(&fixture);
let raw = owned.as_raw_handle().cast();
CloseRequest::for_handle(owned)
.perform()
.expect("close the handle");
assert!(!was_still_open(raw));
}
#[test]
fn taking_the_handle_stops_owned_handle_from_closing_it_first() {
let _allocating = handle_allocation()
.write()
.expect("the lock is not poisoned");
let fixture = Fixture::new("close-ownership");
let owned = captured_duplicate(&fixture);
let raw: HANDLE = owned.as_raw_handle().cast();
let request = CloseRequest::for_handle(owned);
assert_eq!(request.handle(), raw, "the request holds the same handle");
request
.perform()
.expect("the handle was still open to close");
}
#[test]
fn a_change_notification_closes_with_its_own_routine() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("close-notification");
let text = fixture
.directory()
.to_str()
.expect("the fixture path is valid UTF-8");
let notification =
WatchDirectory::new(prepare(&Wtf16String::from(text)).expect("prepare the path"))
.with_filter(NotifyFilter::FILE_NAME)
.perform()
.expect("start a watch");
CloseRequest::for_change_notification(notification)
.perform()
.expect("close the notification with the correct routine");
}
#[test]
fn a_dropped_notification_request_still_closes() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("close-notification-dropped");
let text = fixture
.directory()
.to_str()
.expect("the fixture path is valid UTF-8");
let notification =
WatchDirectory::new(prepare(&Wtf16String::from(text)).expect("prepare the path"))
.with_filter(NotifyFilter::FILE_NAME)
.perform()
.expect("start a watch");
drop(CloseRequest::for_change_notification(notification));
}
#[test]
fn the_two_routines_are_distinguishable() {
let _allocating = handle_allocation()
.read()
.expect("the lock is not poisoned");
let fixture = Fixture::new("close-routines");
let text = fixture
.directory()
.to_str()
.expect("the fixture path is valid UTF-8");
let notification =
WatchDirectory::new(prepare(&Wtf16String::from(text)).expect("prepare the path"))
.with_filter(NotifyFilter::FILE_NAME)
.perform()
.expect("start a watch");
let ordinary = format!(
"{:?}",
CloseRequest::for_handle(captured_duplicate(&fixture))
);
let custom = format!("{:?}", CloseRequest::for_change_notification(notification));
assert!(ordinary.contains("CloseHandle"), "unexpected: {ordinary}");
assert!(
custom.contains("FindCloseChangeNotification"),
"unexpected: {custom}"
);
}
#[test]
fn a_caller_supplied_routine_is_carried() {
let _allocating = handle_allocation()
.write()
.expect("the lock is not poisoned");
let fixture = Fixture::new("close-custom");
let owned = captured_duplicate(&fixture);
let raw: HANDLE = owned.as_raw_handle().cast();
std::mem::forget(owned);
let request = unsafe { CloseRequest::from_raw(raw, CloseHandle) };
request.perform().expect("close through the custom routine");
assert!(!was_still_open(raw));
}
#[test]
fn closing_an_already_closed_handle_reports_the_raw_code() {
let _allocating = handle_allocation()
.write()
.expect("the lock is not poisoned");
let fixture = Fixture::new("close-stale");
let owned = captured_duplicate(&fixture);
let raw: HANDLE = owned.as_raw_handle().cast();
drop(owned);
let request = unsafe { CloseRequest::from_raw(raw, CloseHandle) };
let error = request.perform().expect_err("a stale handle cannot close");
assert_eq!(
error.code(),
ERROR_INVALID_HANDLE,
"the code is passed through, not reclassified"
);
}
#[test]
fn a_close_performs_the_same_way_on_another_thread() {
const fn assert_send<T: Send>() {}
const fn assert_sync<T: Sync>() {}
assert_send::<CloseRequest>();
assert_sync::<CloseRequest>();
let _allocating = handle_allocation()
.write()
.expect("the lock is not poisoned");
let fixture = Fixture::new("close-thread");
let owned = captured_duplicate(&fixture);
let raw: HANDLE = owned.as_raw_handle().cast();
let request = CloseRequest::for_handle(owned);
std::thread::spawn(move || request.perform())
.join()
.expect("the worker did not panic")
.expect("close on a worker thread");
assert!(!was_still_open(raw));
}
#[test]
fn a_file_handle_round_trips_through_the_request() {
let _allocating = handle_allocation()
.write()
.expect("the lock is not poisoned");
let fixture = Fixture::new("close-roundtrip");
let file = File::open(fixture.file()).expect("open the fixture file");
let raw: HANDLE = file.as_raw_handle().cast();
CloseRequest::for_handle(file.into())
.perform()
.expect("close a std File's handle");
assert!(!was_still_open(raw));
}