From d944cb8bfc834bfb9c411db31c0defdd97e43118 Mon Sep 17 00:00:00 2001 From: Kenny Kerr Date: Thu, 6 Feb 2025 11:15:35 -0600 Subject: [PATCH] collection interop testing --- .github/workflows/clippy.yml | 2 + .github/workflows/raw-dylib.yml | 10 +- .github/workflows/test.yml | 10 +- .../tests/winrt/collection_interop/Cargo.toml | 27 +++ .../tests/winrt/collection_interop/build.rs | 61 ++++++ .../winrt/collection_interop/src/bindings.rs | 198 ++++++++++++++++++ .../winrt/collection_interop/src/interop.cpp | 50 +++++ .../tests/winrt/collection_interop/src/lib.rs | 17 ++ .../winrt/collection_interop/src/test.idl | 11 + .../winrt/collection_interop/tests/test.rs | 93 ++++++++ crates/tests/winrt/collections/tests/empty.rs | 49 +++++ 11 files changed, 520 insertions(+), 8 deletions(-) create mode 100644 crates/tests/winrt/collection_interop/Cargo.toml create mode 100644 crates/tests/winrt/collection_interop/build.rs create mode 100644 crates/tests/winrt/collection_interop/src/bindings.rs create mode 100644 crates/tests/winrt/collection_interop/src/interop.cpp create mode 100644 crates/tests/winrt/collection_interop/src/lib.rs create mode 100644 crates/tests/winrt/collection_interop/src/test.idl create mode 100644 crates/tests/winrt/collection_interop/tests/test.rs create mode 100644 crates/tests/winrt/collections/tests/empty.rs diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index e3c40965378..5c32f2bb35b 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -147,6 +147,8 @@ jobs: run: cargo clippy -p test_cfg_generic - name: Clippy test_class_hierarchy run: cargo clippy -p test_class_hierarchy + - name: Clippy test_collection_interop + run: cargo clippy -p test_collection_interop - name: Clippy test_collections run: cargo clippy -p test_collections - name: Clippy test_component diff --git a/.github/workflows/raw-dylib.yml b/.github/workflows/raw-dylib.yml index 6ea6a181670..d0b70a16010 100644 --- a/.github/workflows/raw-dylib.yml +++ b/.github/workflows/raw-dylib.yml @@ -176,6 +176,8 @@ jobs: run: cargo test -p test_cfg_generic --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_class_hierarchy run: cargo test -p test_class_hierarchy --target ${{ matrix.target }} ${{ matrix.etc }} + - name: Test test_collection_interop + run: cargo test -p test_collection_interop --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_collections run: cargo test -p test_collections --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_component @@ -260,10 +262,10 @@ jobs: run: cargo test -p test_readme --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_ref_params run: cargo test -p test_ref_params --target ${{ matrix.target }} ${{ matrix.etc }} - - name: Test test_reference - run: cargo test -p test_reference --target ${{ matrix.target }} ${{ matrix.etc }} - name: Clean run: cargo clean + - name: Test test_reference + run: cargo test -p test_reference --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_reference_client run: cargo test -p test_reference_client --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_reference_float @@ -362,10 +364,10 @@ jobs: run: cargo test -p windows_i686_msvc --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test windows_x86_64_gnu run: cargo test -p windows_x86_64_gnu --target ${{ matrix.target }} ${{ matrix.etc }} - - name: Test windows_x86_64_gnullvm - run: cargo test -p windows_x86_64_gnullvm --target ${{ matrix.target }} ${{ matrix.etc }} - name: Clean run: cargo clean + - name: Test windows_x86_64_gnullvm + run: cargo test -p windows_x86_64_gnullvm --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test windows_x86_64_msvc run: cargo test -p windows_x86_64_msvc --target ${{ matrix.target }} ${{ matrix.etc }} - name: Check diff diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6343ae7890d..883a6792205 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -173,6 +173,8 @@ jobs: run: cargo test -p test_cfg_generic --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_class_hierarchy run: cargo test -p test_class_hierarchy --target ${{ matrix.target }} ${{ matrix.etc }} + - name: Test test_collection_interop + run: cargo test -p test_collection_interop --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_collections run: cargo test -p test_collections --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_component @@ -257,10 +259,10 @@ jobs: run: cargo test -p test_readme --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_ref_params run: cargo test -p test_ref_params --target ${{ matrix.target }} ${{ matrix.etc }} - - name: Test test_reference - run: cargo test -p test_reference --target ${{ matrix.target }} ${{ matrix.etc }} - name: Clean run: cargo clean + - name: Test test_reference + run: cargo test -p test_reference --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_reference_client run: cargo test -p test_reference_client --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_reference_float @@ -359,10 +361,10 @@ jobs: run: cargo test -p windows_i686_msvc --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test windows_x86_64_gnu run: cargo test -p windows_x86_64_gnu --target ${{ matrix.target }} ${{ matrix.etc }} - - name: Test windows_x86_64_gnullvm - run: cargo test -p windows_x86_64_gnullvm --target ${{ matrix.target }} ${{ matrix.etc }} - name: Clean run: cargo clean + - name: Test windows_x86_64_gnullvm + run: cargo test -p windows_x86_64_gnullvm --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test windows_x86_64_msvc run: cargo test -p windows_x86_64_msvc --target ${{ matrix.target }} ${{ matrix.etc }} - name: Check diff diff --git a/crates/tests/winrt/collection_interop/Cargo.toml b/crates/tests/winrt/collection_interop/Cargo.toml new file mode 100644 index 00000000000..f3005f1f94e --- /dev/null +++ b/crates/tests/winrt/collection_interop/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "test_collection_interop" +version = "0.0.0" +edition = "2021" +publish = false + +[lib] +doc = false +doctest = false + +[dependencies.windows-core] +workspace = true + +[dependencies.windows] +workspace = true +features = [ + "Foundation_Collections", +] + +[build-dependencies.windows-bindgen] +workspace = true + +[build-dependencies] +cc = "1.0" + +[build-dependencies.cppwinrt] +workspace = true diff --git a/crates/tests/winrt/collection_interop/build.rs b/crates/tests/winrt/collection_interop/build.rs new file mode 100644 index 00000000000..8a0413b4f9b --- /dev/null +++ b/crates/tests/winrt/collection_interop/build.rs @@ -0,0 +1,61 @@ +fn main() { + if !cfg!(target_env = "msvc") { + return; + } + + println!("cargo:rerun-if-changed=src/test.idl"); + let metadata_dir = format!("{}\\System32\\WinMetadata", env!("windir")); + let mut command = std::process::Command::new("midlrt.exe"); + println!("cargo:rerun-if-changed=src/interop.cpp"); + println!("cargo:rustc-link-lib=onecoreuap"); + + command.args([ + "/winrt", + "/nomidl", + "/h", + "nul", + "/metadata_dir", + &metadata_dir, + "/reference", + &format!("{metadata_dir}\\Windows.Foundation.winmd"), + "/winmd", + "test.winmd", + "src/test.idl", + ]); + + if !command.status().unwrap().success() { + panic!("Failed to run midlrt"); + } + + windows_bindgen::bindgen([ + "--in", + "test.winmd", + &metadata_dir, + "--out", + "src/bindings.rs", + "--filter", + "Test", + "--implement", + "--flat", + "--reference", + "windows,skip-root,Windows", + ]); + + let include = std::env::var("OUT_DIR").unwrap(); + + cppwinrt::cppwinrt([ + "-in", + "test.winmd", + &format!("{}\\System32\\WinMetadata", env!("windir")), + "-out", + &include, + ]); + + cc::Build::new() + .cpp(true) + .std("c++20") + .flag("/EHsc") + .file("src/interop.cpp") + .include(include) + .compile("interop"); +} diff --git a/crates/tests/winrt/collection_interop/src/bindings.rs b/crates/tests/winrt/collection_interop/src/bindings.rs new file mode 100644 index 00000000000..c9a70816da6 --- /dev/null +++ b/crates/tests/winrt/collection_interop/src/bindings.rs @@ -0,0 +1,198 @@ +// Bindings generated by `windows-bindgen` 0.59.0 + +#![allow( + non_snake_case, + non_upper_case_globals, + non_camel_case_types, + dead_code, + clippy::all +)] + +windows_core::imp::define_interface!(ITest, ITest_Vtbl, 0xab9ee103_2921_5ff1_95b3_6b72ea1d289f); +impl windows_core::RuntimeType for ITest { + const SIGNATURE: windows_core::imp::ConstBuffer = + windows_core::imp::ConstBuffer::for_interface::(); +} +windows_core::imp::interface_hierarchy!(ITest, windows_core::IUnknown, windows_core::IInspectable); +impl ITest { + pub fn TestIterable(&self, collection: P0, values: &[i32]) -> windows_core::Result<()> + where + P0: windows_core::Param>, + { + let this = self; + unsafe { + (windows_core::Interface::vtable(this).TestIterable)( + windows_core::Interface::as_raw(this), + collection.param().abi(), + values.len().try_into().unwrap(), + values.as_ptr(), + ) + .ok() + } + } + pub fn GetIterable( + &self, + values: &[i32], + ) -> windows_core::Result> { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).GetIterable)( + windows_core::Interface::as_raw(this), + values.len().try_into().unwrap(), + values.as_ptr(), + &mut result__, + ) + .and_then(|| windows_core::Type::from_abi(result__)) + } + } + pub fn GetMapView( + &self, + values: &[i32], + ) -> windows_core::Result< + windows::Foundation::Collections::IMapView< + i32, + windows::Foundation::Collections::IVectorView, + >, + > { + let this = self; + unsafe { + let mut result__ = core::mem::zeroed(); + (windows_core::Interface::vtable(this).GetMapView)( + windows_core::Interface::as_raw(this), + values.len().try_into().unwrap(), + values.as_ptr(), + &mut result__, + ) + .and_then(|| windows_core::Type::from_abi(result__)) + } + } +} +impl windows_core::RuntimeName for ITest { + const NAME: &'static str = "Test.ITest"; +} +pub trait ITest_Impl: windows_core::IUnknownImpl { + fn TestIterable( + &self, + collection: windows_core::Ref<'_, windows::Foundation::Collections::IIterable>, + values: &[i32], + ) -> windows_core::Result<()>; + fn GetIterable( + &self, + values: &[i32], + ) -> windows_core::Result>; + fn GetMapView( + &self, + values: &[i32], + ) -> windows_core::Result< + windows::Foundation::Collections::IMapView< + i32, + windows::Foundation::Collections::IVectorView, + >, + >; +} +impl ITest_Vtbl { + pub const fn new() -> Self { + unsafe extern "system" fn TestIterable( + this: *mut core::ffi::c_void, + collection: *mut core::ffi::c_void, + values_array_size: u32, + values: *const i32, + ) -> windows_core::HRESULT { + unsafe { + let this: &Identity = + &*((this as *const *const ()).offset(OFFSET) as *const Identity); + ITest_Impl::TestIterable( + this, + core::mem::transmute_copy(&collection), + core::slice::from_raw_parts( + core::mem::transmute_copy(&values), + values_array_size as usize, + ), + ) + .into() + } + } + unsafe extern "system" fn GetIterable( + this: *mut core::ffi::c_void, + values_array_size: u32, + values: *const i32, + result__: *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT { + unsafe { + let this: &Identity = + &*((this as *const *const ()).offset(OFFSET) as *const Identity); + match ITest_Impl::GetIterable( + this, + core::slice::from_raw_parts( + core::mem::transmute_copy(&values), + values_array_size as usize, + ), + ) { + Ok(ok__) => { + result__.write(core::mem::transmute_copy(&ok__)); + core::mem::forget(ok__); + windows_core::HRESULT(0) + } + Err(err) => err.into(), + } + } + } + unsafe extern "system" fn GetMapView( + this: *mut core::ffi::c_void, + values_array_size: u32, + values: *const i32, + result__: *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT { + unsafe { + let this: &Identity = + &*((this as *const *const ()).offset(OFFSET) as *const Identity); + match ITest_Impl::GetMapView( + this, + core::slice::from_raw_parts( + core::mem::transmute_copy(&values), + values_array_size as usize, + ), + ) { + Ok(ok__) => { + result__.write(core::mem::transmute_copy(&ok__)); + core::mem::forget(ok__); + windows_core::HRESULT(0) + } + Err(err) => err.into(), + } + } + } + Self { + base__: windows_core::IInspectable_Vtbl::new::(), + TestIterable: TestIterable::, + GetIterable: GetIterable::, + GetMapView: GetMapView::, + } + } + pub fn matches(iid: &windows_core::GUID) -> bool { + iid == &::IID + } +} +#[repr(C)] +pub struct ITest_Vtbl { + pub base__: windows_core::IInspectable_Vtbl, + pub TestIterable: unsafe extern "system" fn( + *mut core::ffi::c_void, + *mut core::ffi::c_void, + u32, + *const i32, + ) -> windows_core::HRESULT, + pub GetIterable: unsafe extern "system" fn( + *mut core::ffi::c_void, + u32, + *const i32, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, + pub GetMapView: unsafe extern "system" fn( + *mut core::ffi::c_void, + u32, + *const i32, + *mut *mut core::ffi::c_void, + ) -> windows_core::HRESULT, +} diff --git a/crates/tests/winrt/collection_interop/src/interop.cpp b/crates/tests/winrt/collection_interop/src/interop.cpp new file mode 100644 index 00000000000..6bd85dbc3e2 --- /dev/null +++ b/crates/tests/winrt/collection_interop/src/interop.cpp @@ -0,0 +1,50 @@ +#include +#include "winrt/Test.h" +#include "winrt/Windows.Foundation.Collections.h" + +using namespace winrt; +using namespace Windows::Foundation::Collections; +using namespace winrt::Test; + +struct Implementation : implements +{ + void TestIterable(IIterable const& collection, array_view values) { + std::vector buffer(values.size()); + collection.First().GetMany(buffer); + auto span1 = std::span(buffer); + auto span2 = std::span(values); + + // For some reason this doesn't work... assert(span1 == span2); + assert(span1.size() == span2.size()); + assert(std::equal(span1.begin(), span1.end(), span2.begin())); + } + + IIterable GetIterable(array_view values) { + return single_threaded_vector(std::vector(values.begin(), values.end())); + } + + IMapView> GetMapView(array_view values) { + std::map> map; + + for (auto value : values) { + map[value] = single_threaded_vector(std::vector(values.begin(), values.end())).GetView(); + } + + return single_threaded_map(std::move(map)).GetView(); + } +}; + +extern "C" +{ + HRESULT __stdcall make_cpp(void **abi) noexcept + try + { + *abi = detach_abi(make()); + + return S_OK; + } + catch (...) + { + return to_hresult(); + } +} diff --git a/crates/tests/winrt/collection_interop/src/lib.rs b/crates/tests/winrt/collection_interop/src/lib.rs new file mode 100644 index 00000000000..5e885324e15 --- /dev/null +++ b/crates/tests/winrt/collection_interop/src/lib.rs @@ -0,0 +1,17 @@ +#![cfg(target_env = "msvc")] + +mod bindings; +pub use bindings::*; +pub use windows::core::*; + +pub fn make_cpp() -> Result { + extern "system" { + fn make_cpp(test: *mut *mut std::ffi::c_void) -> HRESULT; + } + + unsafe { + let mut test = None; + make_cpp(&mut test as *mut _ as *mut _).ok()?; + Type::from_default(&test) + } +} diff --git a/crates/tests/winrt/collection_interop/src/test.idl b/crates/tests/winrt/collection_interop/src/test.idl new file mode 100644 index 00000000000..344750c8d1e --- /dev/null +++ b/crates/tests/winrt/collection_interop/src/test.idl @@ -0,0 +1,11 @@ +namespace Test +{ + interface ITest + { + void TestIterable(Windows.Foundation.Collections.IIterable collection, Int32[] values); + + Windows.Foundation.Collections.IIterable GetIterable(Int32[] values); + + Windows.Foundation.Collections.IMapView > GetMapView(Int32[] values); + } +} diff --git a/crates/tests/winrt/collection_interop/tests/test.rs b/crates/tests/winrt/collection_interop/tests/test.rs new file mode 100644 index 00000000000..50e80356e05 --- /dev/null +++ b/crates/tests/winrt/collection_interop/tests/test.rs @@ -0,0 +1,93 @@ +#![cfg(target_env = "msvc")] + +use std::collections::BTreeMap; +use test_collection_interop::*; +use windows::Foundation::Collections::*; + +#[implement(ITest)] +#[derive(Default)] +struct Test; + +impl ITest_Impl for Test_Impl { + fn TestIterable(&self, collection: Ref<'_, IIterable>, values: &[i32]) -> Result<()> { + let collection: Vec = collection.ok()?.into_iter().collect(); + assert_eq!(collection, values); + Ok(()) + } + + fn GetIterable(&self, values: &[i32]) -> Result> { + Ok(Vec::from(values).into()) + } + + fn GetMapView(&self, values: &[i32]) -> Result>> { + let mut map = BTreeMap::new(); + + for value in values { + map.insert(*value, Some(IVectorView::from(Vec::from(values)))); + } + + Ok(IMapView::from(map)) + } +} + +fn test_impl(test: &ITest) { + let rust: ITest = Test.into(); + test.TestIterable(&rust.GetIterable(&[1, 2, 3]).unwrap(), &[1, 2, 3]) + .unwrap(); + + let values = test.GetIterable(&[4, 5, 6]).unwrap(); + let values: Vec = values.into_iter().collect(); + assert_eq!(values, [4, 5, 6]); + + let view = test.GetMapView(&[1, 2, 3]).unwrap(); + assert_eq!(view.Size().unwrap(), 3); + + let mut keys = 0; + let mut values = 0; + + for pair in view { + keys += pair.Key().unwrap(); + + for value in pair.Value().unwrap() { + values += value; + } + } + + assert_eq!(keys, 6); + assert_eq!(values, 18); +} + +fn test_empty(test: &ITest) { + let rust: ITest = Test.into(); + test.TestIterable(&rust.GetIterable(&[]).unwrap(), &[]) + .unwrap(); + + let values = test.GetIterable(&[]).unwrap(); + let values: Vec = values.into_iter().collect(); + assert!(values.is_empty()); + + let view = test.GetMapView(&[]).unwrap(); + assert_eq!(view.Size().unwrap(), 0); + + let mut keys = 0; + + for pair in view { + keys += pair.Key().unwrap(); + } + + assert_eq!(keys, 0); +} + +#[test] +fn test_rust() { + let test: ITest = Test.into(); + test_impl(&test); + test_empty(&test); +} + +#[test] +fn test_cpp() { + let test: ITest = make_cpp().unwrap(); + test_impl(&test); + test_empty(&test); +} diff --git a/crates/tests/winrt/collections/tests/empty.rs b/crates/tests/winrt/collections/tests/empty.rs new file mode 100644 index 00000000000..cac5319c3f8 --- /dev/null +++ b/crates/tests/winrt/collections/tests/empty.rs @@ -0,0 +1,49 @@ +use std::collections::BTreeMap; +use windows::Foundation::Collections::*; + +#[test] +fn iterable() { + let empty = IIterable::::from(vec![]); + let mut count = 0; + + for _ in &empty { + count += 1; + } + + assert_eq!(count, 0); + let sum: i32 = empty.into_iter().sum(); + assert_eq!(sum, 0); +} + +#[test] +fn vector_view() { + let empty = IVectorView::::from(vec![]); + let mut count = 0; + + for _ in &empty { + count += 1; + } + + assert_eq!(count, 0); + let sum: i32 = empty.into_iter().sum(); + assert_eq!(sum, 0); +} + +#[test] +fn map_view() { + let empty = IMapView::::from(BTreeMap::from([])); + let mut count = 0; + + for _ in &empty { + count += 1; + } + + assert_eq!(count, 0); + + let sum: i32 = empty + .into_iter() + .map(|pair| pair.Key().unwrap() + pair.Value().unwrap()) + .sum(); + + assert_eq!(sum, 0); +}