Skip to content

Commit

Permalink
Add HSTRING compatibility testing (#1749)
Browse files Browse the repository at this point in the history
  • Loading branch information
kennykerr authored May 10, 2022
1 parent f20f1fe commit fe19c78
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 24 deletions.
7 changes: 7 additions & 0 deletions crates/libs/windows/src/core/heap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ pub fn heap_alloc(bytes: usize) -> Result<RawPtr> {
if ptr.is_null() {
Err(E_OUTOFMEMORY.into())
} else {
// HeapAlloc is not guaranteed to return zero memory but usually does. This just ensures that
// it predictably returns non-zero memory for testing purposes. This is similar to what MSVC's
// debug allocator does for the same reason.
#[cfg(debug_assertions)]
unsafe {
std::ptr::write_bytes(ptr, 0xCC, bytes);
}
Ok(ptr)
}
}
Expand Down
15 changes: 13 additions & 2 deletions crates/tests/core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,16 @@ version = "0.0.0"
authors = ["Microsoft"]
edition = "2018"

[dependencies]
windows = { path = "../../libs/windows" }
[dependencies.windows]
path = "../../libs/windows"
features = [
"alloc",
"Win32_Foundation",
"Win32_System_WinRT",
]

[dependencies.windows-sys]
path = "../../libs/sys"
features = [
"Win32_System_WinRT",
]
103 changes: 82 additions & 21 deletions crates/tests/core/tests/hstrings.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
use std::convert::TryFrom;
use windows::core::*;
type StringType = HSTRING;

#[test]
fn hstring_works() {
let empty = StringType::new();
let empty = HSTRING::new();
assert!(empty.is_empty());
assert!(empty.is_empty());

let mut hello = StringType::from("Hello");
let mut hello = HSTRING::from("Hello");
assert!(!hello.is_empty());
assert!(hello.len() == 5);

Expand All @@ -24,32 +23,32 @@ fn hstring_works() {
assert!(!hello2.is_empty());
assert!(hello2.len() == 5);

assert!(StringType::from("Hello") == StringType::from("Hello"));
assert!(StringType::from("Hello") != StringType::from("World"));
assert!(HSTRING::from("Hello") == HSTRING::from("Hello"));
assert!(HSTRING::from("Hello") != HSTRING::from("World"));

assert!(StringType::from("Hello") == "Hello");
assert!(StringType::from("Hello") != "Hello ");
assert!(StringType::from("Hello") != "Hell");
assert!(StringType::from("Hello") != "World");
assert!(HSTRING::from("Hello") == "Hello");
assert!(HSTRING::from("Hello") != "Hello ");
assert!(HSTRING::from("Hello") != "Hell");
assert!(HSTRING::from("Hello") != "World");

assert!(StringType::from("Hello").to_string() == String::from("Hello"));
assert!(HSTRING::from("Hello").to_string() == String::from("Hello"));
}

#[test]
fn display_format() {
let value = StringType::from("Hello world");
let value = HSTRING::from("Hello world");
assert!(format!("{}", value) == "Hello world");
}

#[test]
fn debug_format() {
let value = StringType::from("Hello world");
let value = HSTRING::from("Hello world");
assert!(format!("{:?}", value) == "Hello world");
}

#[test]
fn from_empty_string() {
let h = StringType::from("");
let h = HSTRING::from("");
assert!(format!("{}", h).is_empty());
}

Expand All @@ -58,14 +57,14 @@ fn from_os_string_string() {
let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063];
use std::os::windows::prelude::OsStringExt;
let o = std::ffi::OsString::from_wide(wide_data);
let h = StringType::from(o);
let d = StringType::from_wide(wide_data);
let h = HSTRING::from(o);
let d = HSTRING::from_wide(wide_data);
assert_eq!(h, d);
}

#[test]
fn hstring_to_string() {
let h = StringType::from("test");
let h = HSTRING::from("test");
let s = String::try_from(h).unwrap();
assert!(s == "test");
}
Expand All @@ -74,7 +73,7 @@ fn hstring_to_string() {
fn hstring_to_string_err() {
// 𝄞mu<invalid>ic
let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063];
let h = StringType::from_wide(wide_data);
let h = HSTRING::from_wide(wide_data);
let err = String::try_from(h);
assert!(err.is_err());
}
Expand All @@ -83,7 +82,7 @@ fn hstring_to_string_err() {
fn hstring_to_string_lossy() {
// 𝄞mu<invalid>ic
let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063];
let h = StringType::from_wide(wide_data);
let h = HSTRING::from_wide(wide_data);
let s = h.to_string_lossy();
assert_eq!(s, "𝄞mu�ic");
}
Expand All @@ -92,15 +91,15 @@ fn hstring_to_string_lossy() {
fn hstring_to_os_string() {
// 𝄞mu<invalid>ic
let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063];
let h = StringType::from_wide(wide_data);
let h = HSTRING::from_wide(wide_data);
let s = h.to_os_string();
use std::os::windows::prelude::OsStringExt;
assert_eq!(s, std::ffi::OsString::from_wide(wide_data));
}

#[test]
fn hstring_equality_combinations() {
let h = StringType::from("test");
let h = HSTRING::from("test");
let s = String::from("test");
let ss: &str = "test";

Expand Down Expand Up @@ -128,7 +127,7 @@ fn hstring_equality_combinations() {
#[test]
fn hstring_osstring_equality_combinations() {
let wide_data = &[0xD834, 0xDD1E, 0x006d, 0x0075, 0xD800, 0x0069, 0x0063];
let h = StringType::from_wide(wide_data);
let h = HSTRING::from_wide(wide_data);
use std::os::windows::prelude::OsStringExt;
let s = std::ffi::OsString::from_wide(wide_data);
let ss = s.as_os_str();
Expand All @@ -153,3 +152,65 @@ fn hstring_osstring_equality_combinations() {
assert_eq!(ss, h);
assert_eq!(ss, &h);
}

#[test]
fn hstring_compat() -> Result<()> {
unsafe {
use windows::Win32::System::WinRT::*;
let hey = HSTRING::from("Hey");
let world = HSTRING::from("World");
assert_eq!(WindowsCompareStringOrdinal(&hey, &world)?, -1);
assert_eq!(WindowsCompareStringOrdinal("Hey", "World")?, -1);

assert_eq!(WindowsConcatString(&hey, &world)?, "HeyWorld");
assert_eq!(WindowsConcatString("Hey", "World")?, "HeyWorld");

assert_eq!(WindowsCreateString(hey.as_wide())?, "Hey");

assert_eq!(WindowsDuplicateString(&hey)?, "Hey");
assert_eq!(WindowsDuplicateString("Hey")?, "Hey");

assert_eq!(WindowsGetStringLen(&hey), 3);
assert_eq!(WindowsGetStringLen("Hey"), 3);
assert_eq!(WindowsGetStringLen(&world), 5);
assert_eq!(WindowsGetStringLen("World"), 5);

assert_eq!(WindowsIsStringEmpty(HSTRING::new()), true);
assert_eq!(WindowsIsStringEmpty(""), true);
assert_eq!(WindowsIsStringEmpty(&world), false);
assert_eq!(WindowsIsStringEmpty("World"), false);

let mut len = 0;
let buffer = WindowsGetStringRawBuffer(&world, &mut len);
assert_eq!(len, 5);
// Adding +1 to the length of the slice to validate that it is null terminated.
assert_eq!(std::slice::from_raw_parts(buffer.0, 6), [87, 111, 114, 108, 100, 0]);

// We need to drop to windows-sys to call the raw WindowsDeleteString function to avoid double-freeing the HSTRING,
// but this test is important as it ensures that the allocators match.
let hresult = windows_sys::Win32::System::WinRT::WindowsDeleteString(std::mem::transmute_copy(&*std::mem::ManuallyDrop::new(hey)));
assert_eq!(hresult, 0);

// An HSTRING reference a.k.a. "fast pass" string is a kind of stack-based HSTRING used by C++ callers
// to avoid the heap allocation in some cases. It's not used in Rust since it assumes a wide character
// string literal, which is inconvenient to create in Rust. Here we again use windows-sys to make one
// and thereby excercise the windows::core::HSTRING support for HSTRING reference duplication.
let mut header: windows_sys::Win32::System::WinRT::HSTRING_HEADER = std::mem::zeroed();
let mut stack_hstring: windows_sys::core::HSTRING = std::mem::zeroed();
let hresult = windows_sys::Win32::System::WinRT::WindowsCreateStringReference([87, 111, 114, 108, 100, 0].as_ptr(), 5, &mut header, &mut stack_hstring);
assert_eq!(hresult, 0);
assert_eq!(header.length, 5);
let stack_hstring: std::mem::ManuallyDrop<HSTRING> = std::mem::transmute(stack_hstring);
let duplicate: HSTRING = (*stack_hstring).clone();
assert_eq!(&duplicate, &*stack_hstring);
assert_eq!(duplicate, "World");

let mut len = 0;
let buffer = WindowsGetStringRawBuffer(&duplicate, &mut len);
assert_eq!(len, 5);
// Adding +1 to the length of the slice to validate that it is null terminated.
assert_eq!(std::slice::from_raw_parts(buffer.0, 6), [87, 111, 114, 108, 100, 0]);

Ok(())
}
}
2 changes: 1 addition & 1 deletion crates/tests/nightly_component/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ impl ::windows::core::RuntimeName for IClass {
}
impl IClass_Vtbl {
pub const fn new<Identity: ::windows::core::IUnknownImpl<Impl = Impl>, Impl: IClass_Impl, const OFFSET: isize>() -> IClass_Vtbl {
unsafe extern "system" fn Property<Identity: ::windows::core::IUnknownImpl, Impl: IClass_Impl, const OFFSET: isize>(this: *mut ::core::ffi::c_void, result__: *mut i32) -> ::windows::core::HRESULT {
unsafe extern "system" fn Property<Identity: ::windows::core::IUnknownImpl<Impl = Impl>, Impl: IClass_Impl, const OFFSET: isize>(this: *mut ::core::ffi::c_void, result__: *mut i32) -> ::windows::core::HRESULT {
let this = (this as *const *const ()).offset(OFFSET) as *const Identity;
let this = (*this).get_impl();
match this.Property() {
Expand Down

0 comments on commit fe19c78

Please sign in to comment.