diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml index 0769ab8c4f..87c6f849a0 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy.yml @@ -133,6 +133,8 @@ jobs: run: cargo clippy -p test_arch_feature - name: Clippy test_array run: cargo clippy -p test_array + - name: Clippy test_async + run: cargo clippy -p test_async - name: Clippy test_bcrypt run: cargo clippy -p test_bcrypt - name: Clippy test_calling_convention @@ -173,10 +175,6 @@ jobs: run: cargo clippy -p test_event - name: Clippy test_extensions run: cargo clippy -p test_extensions - - name: Clippy test_futures - run: cargo clippy -p test_futures - - name: Clippy test_futures_impl - run: cargo clippy -p test_futures_impl - name: Clippy test_handles run: cargo clippy -p test_handles - name: Clippy test_helpers diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a501e0eb5a..f020e46af8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -159,6 +159,8 @@ jobs: run: cargo test -p test_arch_feature --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_array run: cargo test -p test_array --target ${{ matrix.target }} ${{ matrix.etc }} + - name: Test test_async + run: cargo test -p test_async --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_bcrypt run: cargo test -p test_bcrypt --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_calling_convention @@ -199,10 +201,6 @@ jobs: run: cargo test -p test_event --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_extensions run: cargo test -p test_extensions --target ${{ matrix.target }} ${{ matrix.etc }} - - name: Test test_futures - run: cargo test -p test_futures --target ${{ matrix.target }} ${{ matrix.etc }} - - name: Test test_futures_impl - run: cargo test -p test_futures_impl --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_handles run: cargo test -p test_handles --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_helpers @@ -255,10 +253,10 @@ jobs: run: cargo test -p test_result --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_return_handle run: cargo test -p test_return_handle --target ${{ matrix.target }} ${{ matrix.etc }} - - name: Clean - run: cargo clean - name: Test test_return_struct run: cargo test -p test_return_struct --target ${{ matrix.target }} ${{ matrix.etc }} + - name: Clean + run: cargo clean - name: Test test_riddle run: cargo test -p test_riddle --target ${{ matrix.target }} ${{ matrix.etc }} - name: Test test_standalone diff --git a/crates/libs/windows/src/extensions/Foundation.rs b/crates/libs/windows/src/extensions/Foundation.rs index ae22d9a550..5199c88690 100644 --- a/crates/libs/windows/src/extensions/Foundation.rs +++ b/crates/libs/windows/src/extensions/Foundation.rs @@ -1,6 +1,8 @@ pub mod Async; #[cfg(feature = "implement")] pub mod AsyncReady; +#[cfg(feature = "implement")] +pub mod AsyncSpawn; #[cfg(feature = "Foundation_Collections")] pub mod Collections; #[cfg(feature = "Foundation_Numerics")] diff --git a/crates/libs/windows/src/extensions/Foundation/Async.rs b/crates/libs/windows/src/extensions/Foundation/Async.rs index 47c69c1d6d..91b2ca3b6e 100644 --- a/crates/libs/windows/src/extensions/Foundation/Async.rs +++ b/crates/libs/windows/src/extensions/Foundation/Async.rs @@ -1,6 +1,5 @@ -use crate::core::{imp::Waiter, Interface, Result, RuntimeType}; -use crate::Foundation::{AsyncActionCompletedHandler, AsyncActionWithProgressCompletedHandler, AsyncOperationCompletedHandler, AsyncOperationWithProgressCompletedHandler}; -use crate::Foundation::{AsyncStatus, IAsyncAction, IAsyncActionWithProgress, IAsyncInfo, IAsyncOperation, IAsyncOperationWithProgress}; +use crate::core::{imp::Waiter, *}; +use crate::Foundation::*; use std::future::{Future, IntoFuture}; use std::pin::Pin; use std::sync::{Arc, Mutex}; @@ -17,10 +16,10 @@ use std::task::{Context, Poll, Waker}; // implementation below can be reused for all of them. pub trait Async: Interface { // The type of value produced on completion. - type Output; + type Output: Clone; // The type of the delegate use for completion notification. - type CompletedHandler; + type CompletedHandler: Clone; // Sets the handler or callback to invoke when execution completes. This handler can only be set once. fn set_completed(&self, handler: F) -> Result<()>; @@ -237,6 +236,7 @@ impl IntoFuture for IAsyncOperationWithProgress< // impl IAsyncAction { + /// Waits for the `IAsyncAction` to finish. pub fn get(&self) -> Result<()> { if self.Status()? == AsyncStatus::Started { let (_waiter, signaler) = Waiter::new()?; @@ -253,6 +253,7 @@ impl IAsyncAction { } impl IAsyncOperation { + /// Waits for the `IAsyncOperation` to finish. pub fn get(&self) -> Result { if self.Status()? == AsyncStatus::Started { let (_waiter, signaler) = Waiter::new()?; @@ -269,6 +270,7 @@ impl IAsyncOperation { } impl IAsyncActionWithProgress

{ + /// Waits for the `IAsyncActionWithProgress

` to finish. pub fn get(&self) -> Result<()> { if self.Status()? == AsyncStatus::Started { let (_waiter, signaler) = Waiter::new()?; @@ -285,6 +287,7 @@ impl IAsyncActionWithProgress

{ } impl IAsyncOperationWithProgress { + /// Waits for the `IAsyncOperationWithProgress` to finish. pub fn get(&self) -> Result { if self.Status()? == AsyncStatus::Started { let (_waiter, signaler) = Waiter::new()?; diff --git a/crates/libs/windows/src/extensions/Foundation/AsyncReady.rs b/crates/libs/windows/src/extensions/Foundation/AsyncReady.rs index a45e9a0092..1d26326cbc 100644 --- a/crates/libs/windows/src/extensions/Foundation/AsyncReady.rs +++ b/crates/libs/windows/src/extensions/Foundation/AsyncReady.rs @@ -23,7 +23,7 @@ impl ReadyState { // The "Ready" implementations don't need to store the handler since the handler is invoked immediately // but still need to confirm that `SetCompleted` is called at most once. fn invoke_completed(&self, sender: &T, handler: Option<&T::CompletedHandler>) -> Result<()> { - if self.set_completed.swap(true, Ordering::SeqCst) == false { + if !self.set_completed.swap(true, Ordering::SeqCst) { if let Some(handler) = handler { sender.invoke_completed(handler, self.status()); } @@ -195,24 +195,28 @@ impl IAsyncOperationWithProgress_Impl for } impl IAsyncAction { + /// Creates an `IAsyncAction` that is immediately ready with a value. pub fn ready(result: Result<()>) -> Self { ReadyAction(ReadyState::new(result)).into() } } impl IAsyncOperation { + /// Creates an `IAsyncOperation` that is immediately ready with a value. pub fn ready(result: Result) -> Self { ReadyOperation(ReadyState::new(result)).into() } } impl IAsyncActionWithProgress

{ + /// Creates an `IAsyncActionWithProgress

` that is immediately ready with a value. pub fn ready(result: Result<()>) -> Self { ReadyActionWithProgress(ReadyState::new(result)).into() } } impl IAsyncOperationWithProgress { + /// Creates an `IAsyncOperationWithProgress` that is immediately ready with a value. pub fn ready(result: Result) -> Self { ReadyOperationWithProgress(ReadyState::new(result)).into() } diff --git a/crates/libs/windows/src/extensions/Foundation/AsyncSpawn.rs b/crates/libs/windows/src/extensions/Foundation/AsyncSpawn.rs new file mode 100644 index 0000000000..ca69073100 --- /dev/null +++ b/crates/libs/windows/src/extensions/Foundation/AsyncSpawn.rs @@ -0,0 +1,328 @@ +use super::Async::Async; +use crate::{core::*, Foundation::*}; +use core::ffi::c_void; +use std::sync::Mutex; + +struct State { + result: Option>, + completed: Option, + completed_assigned: bool, +} + +impl State { + fn status(&self) -> AsyncStatus { + match &self.result { + None => AsyncStatus::Started, + Some(Ok(_)) => AsyncStatus::Completed, + Some(Err(_)) => AsyncStatus::Error, + } + } + + fn error_code(&self) -> HRESULT { + match &self.result { + Some(Err(error)) => error.code(), + _ => HRESULT(0), + } + } + + fn get_results(&self) -> Result { + match &self.result { + Some(result) => result.clone(), + None => Err(Error::from_hresult(HRESULT(0x8000000Eu32 as i32))), // E_ILLEGAL_METHOD_CALL + } + } +} + +struct SyncState(Mutex>); + +impl SyncState { + fn new() -> Self { + Self(Mutex::new(State { result: None, completed: None, completed_assigned: false })) + } + + fn status(&self) -> AsyncStatus { + self.0.lock().unwrap().status() + } + + fn error_code(&self) -> HRESULT { + self.0.lock().unwrap().error_code() + } + + fn get_results(&self) -> Result { + self.0.lock().unwrap().get_results() + } + + fn set_completed(&self, sender: &T, handler: Option<&T::CompletedHandler>) -> Result<()> { + let mut guard = self.0.lock().unwrap(); + + if guard.completed_assigned { + Err(Error::from_hresult(HRESULT(0x80000018u32 as i32))) // E_ILLEGAL_DELEGATE_ASSIGNMENT + } else { + guard.completed_assigned = true; + let status = guard.status(); + + if status == AsyncStatus::Started { + guard.completed = handler.cloned(); + } else { + drop(guard); + sender.invoke_completed(handler.unwrap(), status); + } + + Ok(()) + } + } + + fn spawn(&self, sender: &T, f: F) + where + F: FnOnce() -> Result + Send + 'static, + { + let result = f(); + let mut guard = self.0.lock().unwrap(); + debug_assert!(guard.result.is_none()); + guard.result = Some(result); + let status = guard.status(); + let completed = guard.completed.take(); + + drop(guard); + + if let Some(completed) = completed { + sender.invoke_completed(&completed, status); + } + } +} + +unsafe impl Send for SyncState {} + +#[implement(IAsyncAction, IAsyncInfo)] +struct Action(SyncState); + +#[implement(IAsyncOperation, IAsyncInfo)] +struct Operation(SyncState>) +where + T: RuntimeType + 'static; + +#[implement(IAsyncActionWithProgress

, IAsyncInfo)] +struct ActionWithProgress

(SyncState>) +where + P: RuntimeType + 'static; + +#[implement(IAsyncOperationWithProgress, IAsyncInfo)] +struct OperationWithProgress(SyncState>) +where + T: RuntimeType + 'static, + P: RuntimeType + 'static; + +impl IAsyncInfo_Impl for Action_Impl { + fn Id(&self) -> Result { + Ok(1) + } + fn Status(&self) -> Result { + Ok(self.0.status()) + } + fn ErrorCode(&self) -> Result { + Ok(self.0.error_code()) + } + fn Cancel(&self) -> Result<()> { + Ok(()) + } + fn Close(&self) -> Result<()> { + Ok(()) + } +} + +impl IAsyncInfo_Impl for Operation_Impl { + fn Id(&self) -> Result { + Ok(1) + } + fn Status(&self) -> Result { + Ok(self.0.status()) + } + fn ErrorCode(&self) -> Result { + Ok(self.0.error_code()) + } + fn Cancel(&self) -> Result<()> { + Ok(()) + } + fn Close(&self) -> Result<()> { + Ok(()) + } +} + +impl IAsyncInfo_Impl for ActionWithProgress_Impl

{ + fn Id(&self) -> Result { + Ok(1) + } + fn Status(&self) -> Result { + Ok(self.0.status()) + } + fn ErrorCode(&self) -> Result { + Ok(self.0.error_code()) + } + fn Cancel(&self) -> Result<()> { + Ok(()) + } + fn Close(&self) -> Result<()> { + Ok(()) + } +} + +impl IAsyncInfo_Impl for OperationWithProgress_Impl { + fn Id(&self) -> Result { + Ok(1) + } + fn Status(&self) -> Result { + Ok(self.0.status()) + } + fn ErrorCode(&self) -> Result { + Ok(self.0.error_code()) + } + fn Cancel(&self) -> Result<()> { + Ok(()) + } + fn Close(&self) -> Result<()> { + Ok(()) + } +} + +impl IAsyncAction_Impl for Action_Impl { + fn SetCompleted(&self, handler: Option<&AsyncActionCompletedHandler>) -> Result<()> { + self.0.set_completed(&self.as_interface(), handler) + } + fn Completed(&self) -> Result { + Err(Error::empty()) + } + fn GetResults(&self) -> Result<()> { + self.0.get_results() + } +} + +impl IAsyncOperation_Impl for Operation_Impl { + fn SetCompleted(&self, handler: Option<&AsyncOperationCompletedHandler>) -> Result<()> { + self.0.set_completed(&self.as_interface(), handler) + } + fn Completed(&self) -> Result> { + Err(Error::empty()) + } + fn GetResults(&self) -> Result { + self.0.get_results() + } +} + +impl IAsyncActionWithProgress_Impl

for ActionWithProgress_Impl

{ + fn SetCompleted(&self, handler: Option<&AsyncActionWithProgressCompletedHandler

>) -> Result<()> { + self.0.set_completed(&self.as_interface(), handler) + } + fn Completed(&self) -> Result> { + Err(Error::empty()) + } + fn GetResults(&self) -> Result<()> { + self.0.get_results() + } + fn SetProgress(&self, _: Option<&AsyncActionProgressHandler

>) -> Result<()> { + Ok(()) + } + fn Progress(&self) -> Result> { + Err(Error::empty()) + } +} + +impl IAsyncOperationWithProgress_Impl for OperationWithProgress_Impl { + fn SetCompleted(&self, handler: Option<&AsyncOperationWithProgressCompletedHandler>) -> Result<()> { + self.0.set_completed(&self.as_interface(), handler) + } + fn Completed(&self) -> Result> { + Err(Error::empty()) + } + fn GetResults(&self) -> Result { + self.0.get_results() + } + fn SetProgress(&self, _: Option<&AsyncOperationProgressHandler>) -> Result<()> { + Ok(()) + } + fn Progress(&self) -> Result> { + Err(Error::empty()) + } +} + +impl IAsyncAction { + /// Creates an `IAsyncAction` that waits for the closure to execute on the Windows thread pool. + pub fn spawn(f: F) -> Self + where + F: FnOnce() -> Result<()> + Send + 'static, + { + let object = ComObject::new(Action(SyncState::new())); + let interface = object.to_interface(); + + spawn(move || { + object.0.spawn(&object.as_interface(), f); + }); + + interface + } +} + +impl IAsyncOperation { + /// Creates an `IAsyncOperation` that waits for the closure to execute on the Windows thread pool. + pub fn spawn(f: F) -> Self + where + F: FnOnce() -> Result + Send + 'static, + { + let object = ComObject::new(Operation(SyncState::new())); + let interface = object.to_interface(); + + spawn(move || { + object.0.spawn(&object.as_interface(), f); + }); + + interface + } +} + +impl IAsyncActionWithProgress

{ + /// Creates an `IAsyncActionWithProgress

` that waits for the closure to execute on the Windows thread pool. + pub fn spawn(f: F) -> Self + where + F: FnOnce() -> Result<()> + Send + 'static, + { + let object = ComObject::new(ActionWithProgress(SyncState::new())); + let interface = object.to_interface(); + + spawn(move || { + object.0.spawn(&object.as_interface(), f); + }); + + interface + } +} + +impl IAsyncOperationWithProgress { + /// Creates an `IAsyncOperationWithProgress` that waits for the closure to execute on the Windows thread pool. + pub fn spawn(f: F) -> Self + where + F: FnOnce() -> Result + Send + 'static, + { + let object = ComObject::new(OperationWithProgress(SyncState::new())); + let interface = object.to_interface(); + + spawn(move || { + object.0.spawn(&object.as_interface(), f); + }); + + interface + } +} + +fn spawn(f: F) { + type PTP_SIMPLE_CALLBACK = unsafe extern "system" fn(instance: *const c_void, context: *const c_void); + windows_targets::link!("kernel32.dll" "system" fn TrySubmitThreadpoolCallback(callback: PTP_SIMPLE_CALLBACK, context: *const c_void, environment: *const c_void) -> i32); + + unsafe extern "system" fn callback(_: *const c_void, callback: *const c_void) { + Box::from_raw(callback as *mut F)(); + } + + unsafe { + if TrySubmitThreadpoolCallback(callback::, Box::into_raw(Box::new(f)) as _, core::ptr::null()) == 0 { + panic!("allocation failed"); + } + } +} diff --git a/crates/tests/futures/Cargo.toml b/crates/tests/async/Cargo.toml similarity index 80% rename from crates/tests/futures/Cargo.toml rename to crates/tests/async/Cargo.toml index e4e456f770..5780bc35fa 100644 --- a/crates/tests/futures/Cargo.toml +++ b/crates/tests/async/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "test_futures" +name = "test_async" version = "0.0.0" edition = "2021" publish = false @@ -11,8 +11,10 @@ doctest = false [dependencies.windows] path = "../../libs/windows" features = [ - "System_Threading", + "implement", "Storage_Streams", + "System_Threading", + "Win32_Foundation", ] [dev-dependencies] diff --git a/crates/tests/futures/src/lib.rs b/crates/tests/async/src/lib.rs similarity index 100% rename from crates/tests/futures/src/lib.rs rename to crates/tests/async/src/lib.rs diff --git a/crates/tests/async/tests/async_info.rs b/crates/tests/async/tests/async_info.rs new file mode 100644 index 0000000000..da6cd87ece --- /dev/null +++ b/crates/tests/async/tests/async_info.rs @@ -0,0 +1,58 @@ +// Tests `IAsyncInfo` for all stock implementations. The only one that really matters is `Status` +// but the remaining methods are tested just to confirm stable behavior. + +use windows::{core::*, Foundation::*}; + +#[test] +fn test() -> Result<()> { + let a = IAsyncAction::ready(Ok(())); + a.get()?; + async_info(&a.cast()?)?; + + let a = IAsyncActionWithProgress::::ready(Ok(())); + a.get()?; + async_info(&a.cast()?)?; + + let a = IAsyncOperation::::ready(Ok(123)); + assert_eq!(a.get()?, 123); + async_info(&a.cast()?)?; + + let a = IAsyncOperationWithProgress::::ready(Ok(123)); + assert_eq!(a.get()?, 123); + async_info(&a.cast()?)?; + + let a = IAsyncAction::spawn(|| Ok(())); + a.get()?; + async_info(&a.cast()?)?; + + let a = IAsyncOperation::spawn(|| Ok(123)); + assert_eq!(a.get()?, 123); + async_info(&a.cast()?)?; + + let a = IAsyncActionWithProgress::::spawn(|| Ok(())); + a.get()?; + async_info(&a.cast()?)?; + + let a = IAsyncOperationWithProgress::::spawn(|| Ok(123)); + assert_eq!(a.get()?, 123); + async_info(&a.cast()?)?; + + Ok(()) +} + +fn async_info(info: &IAsyncInfo) -> Result<()> { + // Stock implementations return 1 + assert_eq!(info.Id()?, 1); + + // Different status codes are tested elsewhere. + assert_eq!(info.Status()?, AsyncStatus::Completed); + + // Different error codes are tested elsewhere. + assert_eq!(info.ErrorCode()?, HRESULT(0)); + + // Stock implementations ignore `Cancel` and `Close` requests. + info.Cancel()?; + info.Close()?; + + Ok(()) +} diff --git a/crates/tests/async/tests/completed_finished.rs b/crates/tests/async/tests/completed_finished.rs new file mode 100644 index 0000000000..60e13c543c --- /dev/null +++ b/crates/tests/async/tests/completed_finished.rs @@ -0,0 +1,184 @@ +// The stock `spawn` implementations can receive the `Completed` handler after execution has finished +// and must call the handler immediately. + +use std::sync::mpsc::channel; +use std::thread; +use windows::{core::*, Foundation::*}; + +#[test] +fn action() -> Result<()> { + let (spawn_finish_send, spawn_finish_recv) = channel(); + let (completed_send, completed_recv) = channel(); + + let test_thread = thread::current().id(); + + let a = IAsyncAction::spawn(move || { + let spawn_thread = thread::current().id(); + + spawn_finish_send.send(spawn_thread).unwrap(); + Ok(()) + }); + + let spawn_thread = spawn_finish_recv.recv().unwrap(); + + while a.Status()? == AsyncStatus::Started { + println!("yield"); + thread::yield_now(); + } + + assert_eq!(a.Status()?, AsyncStatus::Completed); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionCompletedHandler::new(move |sender, status| { + let completed_thread = thread::current().id(); + + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + + completed_send.send(completed_thread).unwrap(); + Ok(()) + }))?; + + let completed_thread = completed_recv.recv().unwrap(); + + assert_ne!(test_thread, spawn_thread); + assert_eq!(test_thread, completed_thread); + + a.GetResults()?; + Ok(()) +} + +#[test] +fn operation() -> Result<()> { + let (spawn_finish_send, spawn_finish_recv) = channel(); + let (completed_send, completed_recv) = channel(); + + let test_thread = thread::current().id(); + + let a = IAsyncOperation::spawn(move || { + let spawn_thread = thread::current().id(); + + spawn_finish_send.send(spawn_thread).unwrap(); + Ok(123) + }); + + let spawn_thread = spawn_finish_recv.recv().unwrap(); + + while a.Status()? == AsyncStatus::Started { + println!("yield"); + thread::yield_now(); + } + + assert_eq!(a.Status()?, AsyncStatus::Completed); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationCompletedHandler::new( + move |sender, status| { + let completed_thread = thread::current().id(); + + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + + completed_send.send(completed_thread).unwrap(); + Ok(()) + }, + ))?; + + let completed_thread = completed_recv.recv().unwrap(); + + assert_ne!(test_thread, spawn_thread); + assert_eq!(test_thread, completed_thread); + + assert_eq!(a.GetResults()?, 123); + Ok(()) +} + +#[test] +fn action_with_progress() -> Result<()> { + let (spawn_finish_send, spawn_finish_recv) = channel(); + let (completed_send, completed_recv) = channel(); + + let test_thread = thread::current().id(); + + let a = IAsyncActionWithProgress::::spawn(move || { + let spawn_thread = thread::current().id(); + + spawn_finish_send.send(spawn_thread).unwrap(); + Ok(()) + }); + + let spawn_thread = spawn_finish_recv.recv().unwrap(); + + while a.Status()? == AsyncStatus::Started { + println!("yield"); + thread::yield_now(); + } + + assert_eq!(a.Status()?, AsyncStatus::Completed); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionWithProgressCompletedHandler::new( + move |sender, status| { + let completed_thread = thread::current().id(); + + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + + completed_send.send(completed_thread).unwrap(); + Ok(()) + }, + ))?; + + let completed_thread = completed_recv.recv().unwrap(); + + assert_ne!(test_thread, spawn_thread); + assert_eq!(test_thread, completed_thread); + + a.GetResults()?; + Ok(()) +} + +#[test] +fn operation_with_progress() -> Result<()> { + let (spawn_finish_send, spawn_finish_recv) = channel(); + let (completed_send, completed_recv) = channel(); + + let test_thread = thread::current().id(); + + let a = IAsyncOperationWithProgress::::spawn(move || { + let spawn_thread = thread::current().id(); + + spawn_finish_send.send(spawn_thread).unwrap(); + Ok(123) + }); + + let spawn_thread = spawn_finish_recv.recv().unwrap(); + + while a.Status()? == AsyncStatus::Started { + println!("yield"); + thread::yield_now(); + } + + assert_eq!(a.Status()?, AsyncStatus::Completed); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationWithProgressCompletedHandler::new( + move |sender, status| { + let completed_thread = thread::current().id(); + + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + + completed_send.send(completed_thread).unwrap(); + Ok(()) + }, + ))?; + + let completed_thread = completed_recv.recv().unwrap(); + + assert_ne!(test_thread, spawn_thread); + assert_eq!(test_thread, completed_thread); + + assert_eq!(a.GetResults()?, 123); + Ok(()) +} diff --git a/crates/tests/async/tests/completed_get.rs b/crates/tests/async/tests/completed_get.rs new file mode 100644 index 0000000000..cf3484b8fb --- /dev/null +++ b/crates/tests/async/tests/completed_get.rs @@ -0,0 +1,41 @@ +// All stock implementations of `Completed` return S_OK and a null pointer giving them impression +// that they do not store the `Completed` handler for consistency. + +use windows::{core::*, Foundation::*}; + +#[test] +fn test() -> Result<()> { + let a = IAsyncAction::ready(Ok(())); + a.get()?; + assert_eq!(a.Completed(), Err(Error::empty())); + + let a = IAsyncActionWithProgress::::ready(Ok(())); + a.get()?; + assert_eq!(a.Completed(), Err(Error::empty())); + + let a = IAsyncOperation::::ready(Ok(1)); + a.get()?; + assert_eq!(a.Completed(), Err(Error::empty())); + + let a = IAsyncOperationWithProgress::::ready(Ok(1)); + a.get()?; + assert_eq!(a.Completed(), Err(Error::empty())); + + let a = IAsyncAction::spawn(|| Ok(())); + a.get()?; + assert_eq!(a.Completed(), Err(Error::empty())); + + let a = IAsyncOperation::spawn(|| Ok(123)); + a.get()?; + assert_eq!(a.Completed(), Err(Error::empty())); + + let a = IAsyncActionWithProgress::::spawn(|| Ok(())); + a.get()?; + assert_eq!(a.Completed(), Err(Error::empty())); + + let a = IAsyncOperationWithProgress::::spawn(|| Ok(123)); + a.get()?; + assert_eq!(a.Completed(), Err(Error::empty())); + + Ok(()) +} diff --git a/crates/tests/async/tests/completed_once.rs b/crates/tests/async/tests/completed_once.rs new file mode 100644 index 0000000000..cc179afe16 --- /dev/null +++ b/crates/tests/async/tests/completed_once.rs @@ -0,0 +1,208 @@ +// Implementations of `SetCompleted` must fail with `E_ILLEGAL_DELEGATE_ASSIGNMENT` if they are called twice. +// Also tests that any error from the handler is ignored by the implementation. + +use windows::{core::*, Foundation::*, Win32::Foundation::*}; + +#[test] +fn action_ready() -> Result<()> { + let a = IAsyncAction::ready(Ok(())); + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionCompletedHandler::new(move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) + }))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(None).unwrap_err().code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + a.GetResults()?; + Ok(()) +} + +#[test] +fn action_with_progress_ready() -> Result<()> { + let a = IAsyncActionWithProgress::::ready(Ok(())); + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionWithProgressCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) + }, + ))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(None).unwrap_err().code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + a.GetResults()?; + Ok(()) +} + +#[test] +fn operation_ready() -> Result<()> { + let a = IAsyncOperation::::ready(Ok(123)); + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) + }, + ))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(None).unwrap_err().code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + assert_eq!(a.GetResults()?, 123); + Ok(()) +} + +#[test] +fn operation_with_progress_ready() -> Result<()> { + let a = IAsyncOperationWithProgress::::ready(Ok(123)); + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationWithProgressCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) + }, + ))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(None).unwrap_err().code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + assert_eq!(a.GetResults()?, 123); + Ok(()) +} + +#[test] +fn action_spawn() -> Result<()> { + let a = IAsyncAction::spawn(|| Ok(())); + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionCompletedHandler::new(move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) + }))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(None).unwrap_err().code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + a.GetResults()?; + Ok(()) +} + +#[test] +fn operation_spawn() -> Result<()> { + let a = IAsyncOperation::spawn(|| Ok(123)); + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) + }, + ))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(None).unwrap_err().code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + assert_eq!(a.GetResults()?, 123); + Ok(()) +} + +#[test] +fn action_with_progress_spawn() -> Result<()> { + let a = IAsyncActionWithProgress::::spawn(|| Ok(())); + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionWithProgressCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) + }, + ))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(None).unwrap_err().code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + a.GetResults()?; + Ok(()) +} + +#[test] +fn operation_with_progress_spawn() -> Result<()> { + let a = IAsyncOperationWithProgress::::spawn(|| Ok(123)); + let (send, recv) = std::sync::mpsc::channel::<()>(); + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationWithProgressCompletedHandler::new( + move |sender, status| { + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + send.send(()).unwrap(); + Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) + }, + ))?; + + recv.recv().unwrap(); + + assert_eq!( + a.SetCompleted(None).unwrap_err().code(), + E_ILLEGAL_DELEGATE_ASSIGNMENT + ); + + assert_eq!(a.GetResults()?, 123); + Ok(()) +} diff --git a/crates/tests/async/tests/completed_started.rs b/crates/tests/async/tests/completed_started.rs new file mode 100644 index 0000000000..778d15629b --- /dev/null +++ b/crates/tests/async/tests/completed_started.rs @@ -0,0 +1,172 @@ +// The stock `spawn` implementations can receive the `Completed` handler while still in the `Started` state +// and must hold on to the handler and call it when execution completes. + +use std::sync::mpsc::channel; +use std::thread; +use windows::{core::*, Foundation::*}; + +#[test] +fn action() -> Result<()> { + let (spawn_start_send, spawn_start_recv) = channel(); + let (spawn_finish_send, spawn_finish_recv) = channel(); + let (completed_send, completed_recv) = channel(); + let test_thread = thread::current().id(); + + let a = IAsyncAction::spawn(move || { + let spawn_thread = thread::current().id(); + spawn_start_recv.recv().unwrap(); + spawn_finish_send.send(spawn_thread).unwrap(); + Ok(()) + }); + + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionCompletedHandler::new(move |sender, status| { + let completed_thread = thread::current().id(); + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + completed_send.send(completed_thread).unwrap(); + Ok(()) + }))?; + + assert_eq!(a.Status()?, AsyncStatus::Started); + assert!(spawn_finish_recv.try_recv().is_err()); + assert!(completed_recv.try_recv().is_err()); + + spawn_start_send.send(()).unwrap(); + let spawn_thread = spawn_finish_recv.recv().unwrap(); + let completed_thread = completed_recv.recv().unwrap(); + + assert_ne!(test_thread, spawn_thread); + assert_ne!(test_thread, completed_thread); + assert_eq!(spawn_thread, completed_thread); + + a.GetResults()?; + Ok(()) +} + +#[test] +fn operation() -> Result<()> { + let (spawn_start_send, spawn_start_recv) = channel(); + let (spawn_finish_send, spawn_finish_recv) = channel(); + let (completed_send, completed_recv) = channel(); + let test_thread = thread::current().id(); + + let a = IAsyncOperation::spawn(move || { + let spawn_thread = thread::current().id(); + spawn_start_recv.recv().unwrap(); + spawn_finish_send.send(spawn_thread).unwrap(); + Ok(123) + }); + + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationCompletedHandler::new( + move |sender, status| { + let completed_thread = thread::current().id(); + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + completed_send.send(completed_thread).unwrap(); + Ok(()) + }, + ))?; + + assert_eq!(a.Status()?, AsyncStatus::Started); + assert!(spawn_finish_recv.try_recv().is_err()); + assert!(completed_recv.try_recv().is_err()); + + spawn_start_send.send(()).unwrap(); + let spawn_thread = spawn_finish_recv.recv().unwrap(); + let completed_thread = completed_recv.recv().unwrap(); + + assert_ne!(test_thread, spawn_thread); + assert_ne!(test_thread, completed_thread); + assert_eq!(spawn_thread, completed_thread); + + assert_eq!(a.GetResults()?, 123); + Ok(()) +} + +#[test] +fn action_with_progress() -> Result<()> { + let (spawn_start_send, spawn_start_recv) = channel(); + let (spawn_finish_send, spawn_finish_recv) = channel(); + let (completed_send, completed_recv) = channel(); + let test_thread = thread::current().id(); + + let a = IAsyncActionWithProgress::::spawn(move || { + let spawn_thread = thread::current().id(); + spawn_start_recv.recv().unwrap(); + spawn_finish_send.send(spawn_thread).unwrap(); + Ok(()) + }); + + let a_clone = a.clone(); + + a.SetCompleted(&AsyncActionWithProgressCompletedHandler::new( + move |sender, status| { + let completed_thread = thread::current().id(); + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + completed_send.send(completed_thread).unwrap(); + Ok(()) + }, + ))?; + + assert_eq!(a.Status()?, AsyncStatus::Started); + assert!(spawn_finish_recv.try_recv().is_err()); + assert!(completed_recv.try_recv().is_err()); + + spawn_start_send.send(()).unwrap(); + let spawn_thread = spawn_finish_recv.recv().unwrap(); + let completed_thread = completed_recv.recv().unwrap(); + + assert_ne!(test_thread, spawn_thread); + assert_ne!(test_thread, completed_thread); + assert_eq!(spawn_thread, completed_thread); + + a.GetResults()?; + Ok(()) +} + +#[test] +fn operation_with_progress() -> Result<()> { + let (spawn_start_send, spawn_start_recv) = channel(); + let (spawn_finish_send, spawn_finish_recv) = channel(); + let (completed_send, completed_recv) = channel(); + let test_thread = thread::current().id(); + + let a = IAsyncOperationWithProgress::::spawn(move || { + let spawn_thread = thread::current().id(); + spawn_start_recv.recv().unwrap(); + spawn_finish_send.send(spawn_thread).unwrap(); + Ok(123) + }); + + let a_clone = a.clone(); + + a.SetCompleted(&AsyncOperationWithProgressCompletedHandler::new( + move |sender, status| { + let completed_thread = thread::current().id(); + assert_eq!(sender.unwrap(), &a_clone); + assert_eq!(status, AsyncStatus::Completed); + completed_send.send(completed_thread).unwrap(); + Ok(()) + }, + ))?; + + assert_eq!(a.Status()?, AsyncStatus::Started); + assert!(spawn_finish_recv.try_recv().is_err()); + assert!(completed_recv.try_recv().is_err()); + + spawn_start_send.send(()).unwrap(); + let spawn_thread = spawn_finish_recv.recv().unwrap(); + let completed_thread = completed_recv.recv().unwrap(); + + assert_ne!(test_thread, spawn_thread); + assert_ne!(test_thread, completed_thread); + assert_eq!(spawn_thread, completed_thread); + + assert_eq!(a.GetResults()?, 123); + Ok(()) +} diff --git a/crates/tests/async/tests/dropped.rs b/crates/tests/async/tests/dropped.rs new file mode 100644 index 0000000000..0e125058d3 --- /dev/null +++ b/crates/tests/async/tests/dropped.rs @@ -0,0 +1,65 @@ +// The stock `spawn` implementation hold a strong reference to the async object on the thread pool +// so the caller can drop its reference if necessary. + +use std::sync::mpsc::channel; +use windows::{core::*, Foundation::*}; + +#[test] +fn action() -> Result<()> { + let (spawn_start_send, spawn_start_recv) = channel(); + + let a = IAsyncAction::spawn(move || { + spawn_start_recv.recv().unwrap(); + Ok(()) + }); + + drop(a); + spawn_start_send.send(()).unwrap(); + + Ok(()) +} + +#[test] +fn operation() -> Result<()> { + let (spawn_start_send, spawn_start_recv) = channel(); + + let a = IAsyncOperation::spawn(move || { + spawn_start_recv.recv().unwrap(); + Ok(123) + }); + + drop(a); + spawn_start_send.send(()).unwrap(); + + Ok(()) +} + +#[test] +fn action_with_progress() -> Result<()> { + let (spawn_start_send, spawn_start_recv) = channel(); + + let a = IAsyncActionWithProgress::::spawn(move || { + spawn_start_recv.recv().unwrap(); + Ok(()) + }); + + drop(a); + spawn_start_send.send(()).unwrap(); + + Ok(()) +} + +#[test] +fn operation_with_progress() -> Result<()> { + let (spawn_start_send, spawn_start_recv) = channel(); + + let a = IAsyncOperationWithProgress::::spawn(move || { + spawn_start_recv.recv().unwrap(); + Ok(123) + }); + + drop(a); + spawn_start_send.send(()).unwrap(); + + Ok(()) +} diff --git a/crates/tests/async/tests/error.rs b/crates/tests/async/tests/error.rs new file mode 100644 index 0000000000..11c41941a8 --- /dev/null +++ b/crates/tests/async/tests/error.rs @@ -0,0 +1,117 @@ +// Tests error propagation for all stock implementations. Here we're using `get` which calls `GetResults` +// as that is the normal path for most callers. Older callers may also use `ErrorCode` so that is tested +// as well. + +use windows::{core::*, Foundation::*, Win32::Foundation::*}; + +#[test] +fn action_ready() -> Result<()> { + let a = IAsyncAction::ready(Err(Error::new( + E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, + "async", + ))); + let e = a.get().unwrap_err(); + assert_eq!(e.message(), "async"); + assert_eq!(e.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.Status()?, AsyncStatus::Error); + + Ok(()) +} + +#[test] +fn action_with_progress_ready() -> Result<()> { + let a = IAsyncActionWithProgress::::ready(Err(Error::new( + E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, + "async", + ))); + let e = a.get().unwrap_err(); + assert_eq!(e.message(), "async"); + assert_eq!(e.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.Status()?, AsyncStatus::Error); + + Ok(()) +} + +#[test] +fn operation_ready() -> Result<()> { + let a = IAsyncOperation::::ready(Err(Error::new( + E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, + "async", + ))); + let e = a.get().unwrap_err(); + assert_eq!(e.message(), "async"); + assert_eq!(e.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.Status()?, AsyncStatus::Error); + + Ok(()) +} + +#[test] +fn operation_with_progress_ready() -> Result<()> { + let a = IAsyncOperationWithProgress::::ready(Err(Error::new( + E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, + "async", + ))); + let e = a.get().unwrap_err(); + assert_eq!(e.message(), "async"); + assert_eq!(e.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.Status()?, AsyncStatus::Error); + + Ok(()) +} + +#[test] +fn action_spawn() -> Result<()> { + let error_clone = Error::new(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, "async"); + let a = IAsyncAction::spawn(move || Err(error_clone)); + let e = a.get().unwrap_err(); + assert_eq!(e.message(), "async"); + assert_eq!(e.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.Status()?, AsyncStatus::Error); + + Ok(()) +} + +#[test] +fn operation_spawn() -> Result<()> { + let error_clone = Error::new(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, "async"); + let a = IAsyncOperation::::spawn(move || Err(error_clone)); + let e = a.get().unwrap_err(); + assert_eq!(e.message(), "async"); + assert_eq!(e.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.Status()?, AsyncStatus::Error); + + Ok(()) +} + +#[test] +fn action_with_progress_spawn() -> Result<()> { + let error_clone = Error::new(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, "async"); + let a = IAsyncActionWithProgress::::spawn(move || Err(error_clone)); + let e = a.get().unwrap_err(); + assert_eq!(e.message(), "async"); + assert_eq!(e.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.Status()?, AsyncStatus::Error); + + Ok(()) +} + +#[test] +fn operation_with_progress_spawn() -> Result<()> { + let error_clone = Error::new(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, "async"); + let a = IAsyncOperationWithProgress::::spawn(move || Err(error_clone)); + let e = a.get().unwrap_err(); + assert_eq!(e.message(), "async"); + assert_eq!(e.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); + assert_eq!(a.Status()?, AsyncStatus::Error); + + Ok(()) +} diff --git a/crates/tests/futures/tests/tests.rs b/crates/tests/async/tests/futures.rs similarity index 93% rename from crates/tests/futures/tests/tests.rs rename to crates/tests/async/tests/futures.rs index ac4b30441b..9fe3928988 100644 --- a/crates/tests/futures/tests/tests.rs +++ b/crates/tests/async/tests/futures.rs @@ -1,3 +1,6 @@ +// Tests that the async interfaces can be used with Rust futures because `IntoFuture` and `Future` implementations +// are provided by the `windows` crate. + use windows::{core::*, Storage::Streams::*, System::Threading::*}; // A simple example of blocking synchronously with the `get` method. diff --git a/crates/tests/async/tests/progress.rs b/crates/tests/async/tests/progress.rs new file mode 100644 index 0000000000..e5dc032735 --- /dev/null +++ b/crates/tests/async/tests/progress.rs @@ -0,0 +1,28 @@ +// All stock implementations don't support progress notifications. + +use windows::{core::*, Foundation::*}; + +#[test] +fn test() -> Result<()> { + let a = IAsyncActionWithProgress::::ready(Ok(())); + a.get()?; + a.SetProgress(&AsyncActionProgressHandler::new(|_, _| Ok(())))?; + assert_eq!(a.Progress(), Err(Error::empty())); + + let a = IAsyncOperationWithProgress::::ready(Ok(1)); + a.get()?; + a.SetProgress(&AsyncOperationProgressHandler::new(|_, _| Ok(())))?; + assert_eq!(a.Progress(), Err(Error::empty())); + + let a = IAsyncActionWithProgress::::spawn(|| Ok(())); + a.get()?; + a.SetProgress(&AsyncActionProgressHandler::new(|_, _| Ok(())))?; + assert_eq!(a.Progress(), Err(Error::empty())); + + let a = IAsyncOperationWithProgress::::spawn(|| Ok(123)); + a.get()?; + a.SetProgress(&AsyncOperationProgressHandler::new(|_, _| Ok(())))?; + assert_eq!(a.Progress(), Err(Error::empty())); + + Ok(()) +} diff --git a/crates/tests/async/tests/started.rs b/crates/tests/async/tests/started.rs new file mode 100644 index 0000000000..bc76e94d9d --- /dev/null +++ b/crates/tests/async/tests/started.rs @@ -0,0 +1,73 @@ +// The stock `ready` implementations are never in the `Started` state as they're either `Completed` and `Error`. +// This tests the `spawn` implementations to confirm that we can observe the `Started` state. +// The `GetResults` method may not be called in this state. + +use windows::{core::*, Foundation::*, Win32::Foundation::*}; + +#[test] +fn action() -> Result<()> { + let (send, recv) = std::sync::mpsc::channel::<()>(); + + let a = IAsyncAction::spawn(move || { + recv.recv().unwrap(); + Ok(()) + }); + + assert_eq!(a.Status()?, AsyncStatus::Started); + assert_eq!(a.GetResults().unwrap_err().code(), E_ILLEGAL_METHOD_CALL); + send.send(()).unwrap(); + a.get()?; + + Ok(()) +} + +#[test] +fn operation() -> Result<()> { + let (send, recv) = std::sync::mpsc::channel::<()>(); + + let a = IAsyncOperation::spawn(move || { + recv.recv().unwrap(); + Ok(123) + }); + + assert_eq!(a.Status()?, AsyncStatus::Started); + assert_eq!(a.GetResults().unwrap_err().code(), E_ILLEGAL_METHOD_CALL); + send.send(()).unwrap(); + assert_eq!(a.get()?, 123); + + Ok(()) +} + +#[test] +fn action_with_progress() -> Result<()> { + let (send, recv) = std::sync::mpsc::channel::<()>(); + + let a = IAsyncActionWithProgress::::spawn(move || { + recv.recv().unwrap(); + Ok(()) + }); + + assert_eq!(a.Status()?, AsyncStatus::Started); + assert_eq!(a.GetResults().unwrap_err().code(), E_ILLEGAL_METHOD_CALL); + send.send(()).unwrap(); + a.get()?; + + Ok(()) +} + +#[test] +fn operation_with_progress() -> Result<()> { + let (send, recv) = std::sync::mpsc::channel::<()>(); + + let a = IAsyncOperationWithProgress::::spawn(move || { + recv.recv().unwrap(); + Ok(123) + }); + + assert_eq!(a.Status()?, AsyncStatus::Started); + assert_eq!(a.GetResults().unwrap_err().code(), E_ILLEGAL_METHOD_CALL); + send.send(()).unwrap(); + assert_eq!(a.get()?, 123); + + Ok(()) +} diff --git a/crates/tests/futures_impl/Cargo.toml b/crates/tests/futures_impl/Cargo.toml deleted file mode 100644 index 1380c81a50..0000000000 --- a/crates/tests/futures_impl/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "test_futures_impl" -version = "0.0.0" -edition = "2021" -publish = false - -[lib] -doc = false -doctest = false - -[dependencies.windows] -path = "../../libs/windows" -features = [ - "implement", - "Foundation", - "Win32_Foundation", -] diff --git a/crates/tests/futures_impl/src/lib.rs b/crates/tests/futures_impl/src/lib.rs deleted file mode 100644 index 8b13789179..0000000000 --- a/crates/tests/futures_impl/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/crates/tests/futures_impl/tests/ready_action.rs b/crates/tests/futures_impl/tests/ready_action.rs deleted file mode 100644 index a45a4685ac..0000000000 --- a/crates/tests/futures_impl/tests/ready_action.rs +++ /dev/null @@ -1,62 +0,0 @@ -use windows::{core::*, Foundation::*, Win32::Foundation::*}; - -#[test] -fn ok() -> Result<()> { - let a = IAsyncAction::ready(Ok(())); - let _: IAsyncInfo = a.cast()?; - assert_eq!(a.Id()?, 1); - assert_eq!(a.Status()?, AsyncStatus::Completed); - assert_eq!(a.ErrorCode()?, HRESULT(0)); - assert_eq!(a.Completed(), Err(Error::empty())); - assert_eq!(a.GetResults(), Ok(())); - a.Cancel()?; - a.Close()?; - - let (send, recv) = std::sync::mpsc::channel::<()>(); - let a_clone = a.clone(); - - a.SetCompleted(&AsyncActionCompletedHandler::new(move |sender, status| { - assert_eq!(sender.unwrap(), &a_clone); - assert_eq!(status, AsyncStatus::Completed); - send.send(()).unwrap(); - Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) // handler error ignored - }))?; - - recv.recv().unwrap(); - - assert_eq!( - a.SetCompleted(&AsyncActionCompletedHandler::new(move |_, _| { Ok(()) })) - .unwrap_err() - .code(), - E_ILLEGAL_DELEGATE_ASSIGNMENT - ); - - IAsyncAction::ready(Ok(())).get()?; - Ok(()) -} - -#[test] -fn err() -> Result<()> { - let a = IAsyncAction::ready(Err(Error::new( - E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, - "async", - ))); - assert_eq!(a.Status()?, AsyncStatus::Error); - assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); - let error = a.GetResults().unwrap_err(); - assert_eq!(error.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); - assert_eq!(error.message(), "async"); - - let (send, recv) = std::sync::mpsc::channel::<()>(); - let a_clone = a.clone(); - - a.SetCompleted(&AsyncActionCompletedHandler::new(move |sender, status| { - assert_eq!(sender.unwrap(), &a_clone); - assert_eq!(status, AsyncStatus::Error); - send.send(()).unwrap(); - Ok(()) - }))?; - - recv.recv().unwrap(); - Ok(()) -} diff --git a/crates/tests/futures_impl/tests/ready_action_progress.rs b/crates/tests/futures_impl/tests/ready_action_progress.rs deleted file mode 100644 index a683ae14d6..0000000000 --- a/crates/tests/futures_impl/tests/ready_action_progress.rs +++ /dev/null @@ -1,68 +0,0 @@ -use windows::{core::*, Foundation::*, Win32::Foundation::*}; - -#[test] -fn ok() -> Result<()> { - let a = IAsyncActionWithProgress::::ready(Ok(())); - let _: IAsyncInfo = a.cast()?; - assert_eq!(a.Id()?, 1); - assert_eq!(a.Status()?, AsyncStatus::Completed); - assert_eq!(a.ErrorCode()?, HRESULT(0)); - assert_eq!(a.Completed(), Err(Error::empty())); - assert_eq!(a.GetResults(), Ok(())); - a.Cancel()?; - a.Close()?; - - let (send, recv) = std::sync::mpsc::channel::<()>(); - let a_clone = a.clone(); - - a.SetCompleted(&AsyncActionWithProgressCompletedHandler::new( - move |sender, status| { - assert_eq!(sender.unwrap(), &a_clone); - assert_eq!(status, AsyncStatus::Completed); - send.send(()).unwrap(); - Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) // handler error ignored - }, - ))?; - - recv.recv().unwrap(); - - assert_eq!( - a.SetCompleted(&AsyncActionWithProgressCompletedHandler::new( - move |_, _| { Ok(()) } - )) - .unwrap_err() - .code(), - E_ILLEGAL_DELEGATE_ASSIGNMENT - ); - - IAsyncActionWithProgress::::ready(Ok(())).get()?; - Ok(()) -} - -#[test] -fn err() -> Result<()> { - let a = IAsyncActionWithProgress::::ready(Err(Error::new( - E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, - "async", - ))); - assert_eq!(a.Status()?, AsyncStatus::Error); - assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); - let error = a.GetResults().unwrap_err(); - assert_eq!(error.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); - assert_eq!(error.message(), "async"); - - let (send, recv) = std::sync::mpsc::channel::<()>(); - let a_clone = a.clone(); - - a.SetCompleted(&AsyncActionWithProgressCompletedHandler::new( - move |sender, status| { - assert_eq!(sender.unwrap(), &a_clone); - assert_eq!(status, AsyncStatus::Error); - send.send(()).unwrap(); - Ok(()) - }, - ))?; - - recv.recv().unwrap(); - Ok(()) -} diff --git a/crates/tests/futures_impl/tests/ready_operation.rs b/crates/tests/futures_impl/tests/ready_operation.rs deleted file mode 100644 index 31716a4fa0..0000000000 --- a/crates/tests/futures_impl/tests/ready_operation.rs +++ /dev/null @@ -1,70 +0,0 @@ -use windows::{core::*, Foundation::*, Win32::Foundation::*}; - -#[test] -fn ok() -> Result<()> { - let a = IAsyncOperation::ready(Ok(123)); - let _: IAsyncInfo = a.cast()?; - assert_eq!(a.Id()?, 1); - assert_eq!(a.Status()?, AsyncStatus::Completed); - assert_eq!(a.ErrorCode()?, HRESULT(0)); - assert_eq!(a.Completed(), Err(Error::empty())); - assert_eq!(a.GetResults(), Ok(123)); - a.Cancel()?; - a.Close()?; - - let (send, recv) = std::sync::mpsc::channel::<()>(); - let a_clone = a.clone(); - - a.SetCompleted(&AsyncOperationCompletedHandler::new( - move |sender, status| { - assert_eq!(sender.unwrap(), &a_clone); - assert_eq!(status, AsyncStatus::Completed); - send.send(()).unwrap(); - Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) // handler error ignored - }, - ))?; - - recv.recv().unwrap(); - - assert_eq!( - a.SetCompleted(&AsyncOperationCompletedHandler::new(move |_, _| { Ok(()) })) - .unwrap_err() - .code(), - E_ILLEGAL_DELEGATE_ASSIGNMENT - ); - - assert_eq!( - IAsyncOperation::ready(Ok(HSTRING::from("hello"))).get()?, - "hello" - ); - - Ok(()) -} - -#[test] -fn err() -> Result<()> { - let a = IAsyncOperation::::ready(Err(Error::new( - E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, - "async", - ))); - assert_eq!(a.Status()?, AsyncStatus::Error); - assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); - let error = a.GetResults().unwrap_err(); - assert_eq!(error.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); - assert_eq!(error.message(), "async"); - - let (send, recv) = std::sync::mpsc::channel::<()>(); - let a_clone = a.clone(); - - a.SetCompleted(&AsyncOperationCompletedHandler::new( - move |sender, status| { - assert_eq!(sender.unwrap(), &a_clone); - assert_eq!(status, AsyncStatus::Error); - send.send(()).unwrap(); - Ok(()) - }, - ))?; - - recv.recv().unwrap(); - Ok(()) -} diff --git a/crates/tests/futures_impl/tests/ready_operation_progress.rs b/crates/tests/futures_impl/tests/ready_operation_progress.rs deleted file mode 100644 index 6c52df359d..0000000000 --- a/crates/tests/futures_impl/tests/ready_operation_progress.rs +++ /dev/null @@ -1,72 +0,0 @@ -use windows::{core::*, Foundation::*, Win32::Foundation::*}; - -#[test] -fn ok() -> Result<()> { - let a = IAsyncOperationWithProgress::::ready(Ok(123)); - let _: IAsyncInfo = a.cast()?; - assert_eq!(a.Id()?, 1); - assert_eq!(a.Status()?, AsyncStatus::Completed); - assert_eq!(a.ErrorCode()?, HRESULT(0)); - assert_eq!(a.Completed(), Err(Error::empty())); - assert_eq!(a.GetResults(), Ok(123)); - a.Cancel()?; - a.Close()?; - - let (send, recv) = std::sync::mpsc::channel::<()>(); - let a_clone = a.clone(); - - a.SetCompleted(&AsyncOperationWithProgressCompletedHandler::new( - move |sender, status| { - assert_eq!(sender.unwrap(), &a_clone); - assert_eq!(status, AsyncStatus::Completed); - send.send(()).unwrap(); - Err(E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED.into()) // handler error ignored - }, - ))?; - - recv.recv().unwrap(); - - assert_eq!( - a.SetCompleted(&AsyncOperationWithProgressCompletedHandler::new( - move |_, _| { Ok(()) } - )) - .unwrap_err() - .code(), - E_ILLEGAL_DELEGATE_ASSIGNMENT - ); - - assert_eq!( - IAsyncOperationWithProgress::::ready(Ok(HSTRING::from("hello"))).get()?, - "hello" - ); - - Ok(()) -} - -#[test] -fn err() -> Result<()> { - let a = IAsyncOperationWithProgress::::ready(Err(Error::new( - E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED, - "async", - ))); - assert_eq!(a.Status()?, AsyncStatus::Error); - assert_eq!(a.ErrorCode()?, E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); - let error = a.GetResults().unwrap_err(); - assert_eq!(error.code(), E_PROTOCOL_EXTENSIONS_NOT_SUPPORTED); - assert_eq!(error.message(), "async"); - - let (send, recv) = std::sync::mpsc::channel::<()>(); - let a_clone = a.clone(); - - a.SetCompleted(&AsyncOperationWithProgressCompletedHandler::new( - move |sender, status| { - assert_eq!(sender.unwrap(), &a_clone); - assert_eq!(status, AsyncStatus::Error); - send.send(()).unwrap(); - Ok(()) - }, - ))?; - - recv.recv().unwrap(); - Ok(()) -}