diff --git a/configure b/configure index a123a6355e7..c3f116b3c4f 100755 --- a/configure +++ b/configure @@ -21960,6 +21960,7 @@ wine_fn_config_makefile dlls/tbs enable_tbs wine_fn_config_makefile dlls/tdh enable_tdh wine_fn_config_makefile dlls/tdi.sys enable_tdi_sys wine_fn_config_makefile dlls/threadpoolwinrt enable_threadpoolwinrt +wine_fn_config_makefile dlls/threadpoolwinrt/tests enable_tests wine_fn_config_makefile dlls/toolhelp.dll16 enable_win16 wine_fn_config_makefile dlls/traffic enable_traffic wine_fn_config_makefile dlls/twain.dll16 enable_win16 diff --git a/configure.ac b/configure.ac index 2bce5d82284..5605703d11b 100644 --- a/configure.ac +++ b/configure.ac @@ -3065,6 +3065,7 @@ WINE_CONFIG_MAKEFILE(dlls/tbs) WINE_CONFIG_MAKEFILE(dlls/tdh) WINE_CONFIG_MAKEFILE(dlls/tdi.sys) WINE_CONFIG_MAKEFILE(dlls/threadpoolwinrt) +WINE_CONFIG_MAKEFILE(dlls/threadpoolwinrt/tests) WINE_CONFIG_MAKEFILE(dlls/toolhelp.dll16,enable_win16) WINE_CONFIG_MAKEFILE(dlls/traffic) WINE_CONFIG_MAKEFILE(dlls/twain.dll16,enable_win16) diff --git a/dlls/threadpoolwinrt/main.c b/dlls/threadpoolwinrt/main.c index fbb822842a8..9fa475487ca 100644 --- a/dlls/threadpoolwinrt/main.c +++ b/dlls/threadpoolwinrt/main.c @@ -17,6 +17,7 @@ */ #include +#include #define COBJMACROS #include "windef.h" @@ -52,6 +53,13 @@ struct threadpool_factory LONG refcount; }; +struct async_action +{ + IAsyncAction IAsyncAction_iface; + IAsyncInfo IAsyncInfo_iface; + LONG refcount; +}; + static inline struct threadpool_factory *impl_from_IActivationFactory(IActivationFactory *iface) { return CONTAINING_RECORD(iface, struct threadpool_factory, IActivationFactory_iface); @@ -62,6 +70,374 @@ static inline struct threadpool_factory *impl_from_IThreadPoolStatics(IThreadPoo return CONTAINING_RECORD(iface, struct threadpool_factory, IThreadPoolStatics_iface); } +static inline struct async_action *impl_from_IAsyncAction(IAsyncAction *iface) +{ + return CONTAINING_RECORD(iface, struct async_action, IAsyncAction_iface); +} + +static inline struct async_action *impl_from_IAsyncInfo(IAsyncInfo *iface) +{ + return CONTAINING_RECORD(iface, struct async_action, IAsyncInfo_iface); +} + +static HRESULT STDMETHODCALLTYPE async_action_QueryInterface(IAsyncAction *iface, REFIID iid, void **out) +{ + struct async_action *action = impl_from_IAsyncAction(iface); + + TRACE("iface %p, iid %s, out %p.\n", iface, debugstr_guid(iid), out); + + if (IsEqualIID(iid, &IID_IAsyncAction) + || IsEqualIID(iid, &IID_IInspectable) + || IsEqualIID(iid, &IID_IUnknown)) + { + *out = iface; + } + else if (IsEqualIID(iid, &IID_IAsyncInfo)) + { + *out = &action->IAsyncInfo_iface; + } + else + { + *out = NULL; + WARN("Unsupported interface %s.\n", debugstr_guid(iid)); + } + + IUnknown_AddRef((IUnknown *)*out); + return S_OK; +} + +static ULONG STDMETHODCALLTYPE async_action_AddRef(IAsyncAction *iface) +{ + struct async_action *action = impl_from_IAsyncAction(iface); + ULONG refcount = InterlockedIncrement(&action->refcount); + + TRACE("iface %p, refcount %lu.\n", iface, refcount); + + return refcount; +} + +static ULONG STDMETHODCALLTYPE async_action_Release(IAsyncAction *iface) +{ + struct async_action *action = impl_from_IAsyncAction(iface); + ULONG refcount = InterlockedDecrement(&action->refcount); + + TRACE("iface %p, refcount %lu.\n", iface, refcount); + + if (!refcount) + free(action); + + return refcount; +} + +static HRESULT STDMETHODCALLTYPE async_action_GetIids( + IAsyncAction *iface, ULONG *iid_count, IID **iids) +{ + FIXME("iface %p, iid_count %p, iids %p stub!\n", iface, iid_count, iids); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE async_action_GetRuntimeClassName( + IAsyncAction *iface, HSTRING *class_name) +{ + FIXME("iface %p, class_name %p stub!\n", iface, class_name); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE async_action_GetTrustLevel( + IAsyncAction *iface, TrustLevel *trust_level) +{ + FIXME("iface %p, trust_level %p stub!\n", iface, trust_level); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE async_action_put_Completed(IAsyncAction *iface, IAsyncActionCompletedHandler *handler) +{ + FIXME("iface %p, handler %p stub!\n", iface, handler); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE async_action_get_Completed(IAsyncAction *iface, IAsyncActionCompletedHandler **handler) +{ + FIXME("iface %p, handler %p stub!\n", iface, handler); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE async_action_GetResults(IAsyncAction *iface) +{ + FIXME("iface %p stub!\n", iface); + + return E_NOTIMPL; +} + +static const IAsyncActionVtbl async_action_vtbl = +{ + async_action_QueryInterface, + async_action_AddRef, + async_action_Release, + async_action_GetIids, + async_action_GetRuntimeClassName, + async_action_GetTrustLevel, + async_action_put_Completed, + async_action_get_Completed, + async_action_GetResults, +}; + +static HRESULT STDMETHODCALLTYPE async_info_QueryInterface(IAsyncInfo *iface, REFIID iid, void **out) +{ + struct async_action *action = impl_from_IAsyncInfo(iface); + return IAsyncAction_QueryInterface(&action->IAsyncAction_iface, iid, out); +} + +static ULONG STDMETHODCALLTYPE async_info_AddRef(IAsyncInfo *iface) +{ + struct async_action *action = impl_from_IAsyncInfo(iface); + return IAsyncAction_AddRef(&action->IAsyncAction_iface); +} + +static ULONG STDMETHODCALLTYPE async_info_Release(IAsyncInfo *iface) +{ + struct async_action *action = impl_from_IAsyncInfo(iface); + return IAsyncAction_AddRef(&action->IAsyncAction_iface); +} + +static HRESULT STDMETHODCALLTYPE async_info_GetIids(IAsyncInfo *iface, ULONG *iid_count, IID **iids) +{ + struct async_action *action = impl_from_IAsyncInfo(iface); + return IAsyncAction_GetIids(&action->IAsyncAction_iface, iid_count, iids); +} + +static HRESULT STDMETHODCALLTYPE async_info_GetRuntimeClassName(IAsyncInfo *iface, HSTRING *class_name) +{ + struct async_action *action = impl_from_IAsyncInfo(iface); + return IAsyncAction_GetRuntimeClassName(&action->IAsyncAction_iface, class_name); +} + +static HRESULT STDMETHODCALLTYPE async_info_GetTrustLevel(IAsyncInfo *iface, TrustLevel *trust_level) +{ + struct async_action *action = impl_from_IAsyncInfo(iface); + return IAsyncAction_GetTrustLevel(&action->IAsyncAction_iface, trust_level); +} + +static HRESULT STDMETHODCALLTYPE async_info_get_Id(IAsyncInfo *iface, UINT32 *id) +{ + FIXME("iface %p, id %p stub!\n", iface, id); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE async_info_get_Status(IAsyncInfo *iface, AsyncStatus *status) +{ + FIXME("iface %p, status %p stub!\n", iface, status); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE async_info_get_ErrorCode(IAsyncInfo *iface, HRESULT *error_code) +{ + FIXME("iface %p, error_code %p stub!\n", iface, error_code); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE async_info_Cancel(IAsyncInfo *iface) +{ + FIXME("iface %p stub!\n", iface); + + return E_NOTIMPL; +} + +static HRESULT STDMETHODCALLTYPE async_info_Close(IAsyncInfo *iface) +{ + FIXME("iface %p stub!\n", iface); + + return E_NOTIMPL; +} + +static const IAsyncInfoVtbl async_info_vtbl = +{ + async_info_QueryInterface, + async_info_AddRef, + async_info_Release, + async_info_GetIids, + async_info_GetRuntimeClassName, + async_info_GetTrustLevel, + async_info_get_Id, + async_info_get_Status, + async_info_get_ErrorCode, + async_info_Cancel, + async_info_Close, +}; + +static HRESULT async_action_create(IAsyncAction **ret) +{ + struct async_action *object; + + *ret = NULL; + + if (!(object = calloc(1, sizeof(*object)))) + return E_OUTOFMEMORY; + + object->IAsyncAction_iface.lpVtbl = &async_action_vtbl; + object->IAsyncInfo_iface.lpVtbl = &async_info_vtbl; + object->refcount = 1; + + *ret = &object->IAsyncAction_iface; + + return S_OK; +} + +struct work_item +{ + IWorkItemHandler *handler; + IAsyncAction *action; +}; + +static void release_work_item(struct work_item *item) +{ + IWorkItemHandler_Release(item->handler); + IAsyncAction_Release(item->action); + free(item); +} + +static HRESULT alloc_work_item(IWorkItemHandler *handler, struct work_item **ret) +{ + struct work_item *object; + HRESULT hr; + + *ret = NULL; + + if (!(object = calloc(1, sizeof(*object)))) + return E_OUTOFMEMORY; + + if (FAILED(hr = async_action_create(&object->action))) + { + free(object); + return hr; + } + + IWorkItemHandler_AddRef((object->handler = handler)); + + *ret = object; + + return S_OK; +} + +static void work_item_invoke_release(struct work_item *item) +{ + IWorkItemHandler_Invoke(item->handler, item->action); + release_work_item(item); +} + +static DWORD WINAPI sliced_thread_proc(void *arg) +{ + struct work_item *item = arg; + work_item_invoke_release(item); + return 0; +} + +struct thread_pool +{ + INIT_ONCE init_once; + TP_POOL *pool; +}; + +static struct thread_pool pools[3]; + +static BOOL CALLBACK pool_init_once(INIT_ONCE *init_once, void *param, void **context) +{ + struct thread_pool *pool = param; + + if (!(pool->pool = CreateThreadpool(NULL))) return FALSE; + + SetThreadpoolThreadMaximum(pool->pool, 10); + + return TRUE; +} + +static void CALLBACK pool_work_callback(TP_CALLBACK_INSTANCE *instance, void *context, TP_WORK *work) +{ + struct work_item *item = context; + work_item_invoke_release(item); +} + +static HRESULT submit_threadpool_work(struct work_item *item, WorkItemPriority priority, IAsyncAction **action) +{ + struct thread_pool *pool; + TP_WORK *work; + + assert(priority == WorkItemPriority_Low + || priority == WorkItemPriority_Normal + || priority == WorkItemPriority_High); + + pool = &pools[priority + 1]; + + if (!InitOnceExecuteOnce(&pool->init_once, pool_init_once, pool, NULL)) + return E_FAIL; + + if (!(work = CreateThreadpoolWork(pool_work_callback, item, NULL))) + return E_FAIL; + + IAsyncAction_AddRef((*action = item->action)); + SubmitThreadpoolWork(work); + + return S_OK; +} + +static HRESULT submit_standalone_thread_work(struct work_item *item, WorkItemPriority priority, IAsyncAction **action) +{ + HANDLE thread; + + if (!(thread = CreateThread(NULL, 0, sliced_thread_proc, item, priority == WorkItemPriority_Normal ? + 0 : CREATE_SUSPENDED, NULL))) + { + WARN("Failed to create a thread, error %ld.\n", GetLastError()); + return HRESULT_FROM_WIN32(GetLastError()); + } + + IAsyncAction_AddRef((*action = item->action)); + if (priority != WorkItemPriority_Normal) + { + SetThreadPriority(thread, priority == WorkItemPriority_High ? THREAD_PRIORITY_HIGHEST : THREAD_PRIORITY_LOWEST); + ResumeThread(thread); + } + CloseHandle(thread); + + return S_OK; +} + +static HRESULT run_async(IWorkItemHandler *handler, WorkItemPriority priority, WorkItemOptions options, + IAsyncAction **action) +{ + struct work_item *item; + HRESULT hr; + + *action = NULL; + + if (!handler) + return E_INVALIDARG; + + if (priority < WorkItemPriority_Low || priority > WorkItemPriority_High) + return E_INVALIDARG; + + if (FAILED(hr = alloc_work_item(handler, &item))) + return hr; + + if (options == WorkItemOptions_TimeSliced) + hr = submit_standalone_thread_work(item, priority, action); + else + hr = submit_threadpool_work(item, priority, action); + + if (FAILED(hr)) + release_work_item(item); + + return hr; +} + static HRESULT STDMETHODCALLTYPE threadpool_factory_QueryInterface( IActivationFactory *iface, REFIID iid, void **out) { @@ -195,23 +571,26 @@ static HRESULT STDMETHODCALLTYPE threadpool_statics_GetTrustLevel( static HRESULT STDMETHODCALLTYPE threadpool_statics_RunAsync( IThreadPoolStatics *iface, IWorkItemHandler *handler, IAsyncAction **operation) { - FIXME("iface %p, handler %p, operation %p stub!\n", iface, handler, operation); - return E_NOTIMPL; + TRACE("iface %p, handler %p, operation %p.\n", iface, handler, operation); + + return run_async(handler, WorkItemPriority_Normal, WorkItemOptions_None, operation); } static HRESULT STDMETHODCALLTYPE threadpool_statics_RunWithPriorityAsync( IThreadPoolStatics *iface, IWorkItemHandler *handler, WorkItemPriority priority, IAsyncAction **operation) { - FIXME("iface %p, handler %p, priority %d, operation %p stub!\n", iface, handler, priority, operation); - return E_NOTIMPL; + TRACE("iface %p, handler %p, priority %d, operation %p.\n", iface, handler, priority, operation); + + return run_async(handler, priority, WorkItemOptions_None, operation); } static HRESULT STDMETHODCALLTYPE threadpool_statics_RunWithPriorityAndOptionsAsync( IThreadPoolStatics *iface, IWorkItemHandler *handler, WorkItemPriority priority, WorkItemOptions options, IAsyncAction **operation) { - FIXME("iface %p, handler %p, priority %d, options %d, operation %p stub!\n", iface, handler, priority, options, operation); - return E_NOTIMPL; + TRACE("iface %p, handler %p, priority %d, options %d, operation %p.\n", iface, handler, priority, options, operation); + + return run_async(handler, priority, options, operation); } static const struct IThreadPoolStaticsVtbl threadpool_statics_vtbl = diff --git a/dlls/threadpoolwinrt/tests/Makefile.in b/dlls/threadpoolwinrt/tests/Makefile.in new file mode 100644 index 00000000000..ca1206d68da --- /dev/null +++ b/dlls/threadpoolwinrt/tests/Makefile.in @@ -0,0 +1,5 @@ +TESTDLL = threadpoolwinrt.dll +IMPORTS = combase uuid + +C_SRCS = \ + threadpool.c diff --git a/dlls/threadpoolwinrt/tests/threadpool.c b/dlls/threadpoolwinrt/tests/threadpool.c new file mode 100644 index 00000000000..715180818e1 --- /dev/null +++ b/dlls/threadpoolwinrt/tests/threadpool.c @@ -0,0 +1,292 @@ +/* + * Copyright 2022 Nikolay Sivov for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ +#define COBJMACROS +#include + +#include "windef.h" +#include "winbase.h" +#include "winerror.h" +#include "winstring.h" + +#include "initguid.h" +#include "roapi.h" + +#define WIDL_using_Windows_Foundation +#define WIDL_using_Windows_Foundation_Collections +#include "windows.foundation.h" +#define WIDL_using_Windows_System_Threading +#include "windows.system.threading.h" + +#include "wine/test.h" + +#define check_interface(a, b, c) check_interface_(__LINE__, a, b, c) +static void check_interface_(unsigned int line, void *iface_ptr, REFIID iid, BOOL supported) +{ + IUnknown *iface = iface_ptr; + HRESULT hr, expected_hr; + IUnknown *unk; + + expected_hr = supported ? S_OK : E_NOINTERFACE; + + hr = IUnknown_QueryInterface(iface, iid, (void **)&unk); + ok_(__FILE__, line)(hr == expected_hr, "Got hr %#lx, expected %#lx.\n", hr, expected_hr); + if (SUCCEEDED(hr)) + IUnknown_Release(unk); +} + +static const WCHAR *threadpool_class_name = L"Windows.System.Threading.ThreadPool"; + +static void test_interfaces(void) +{ + IThreadPoolStatics *threadpool_statics; + IActivationFactory *factory; + HSTRING classid; + HRESULT hr; + + hr = RoInitialize(RO_INIT_MULTITHREADED); + ok(SUCCEEDED(hr), "Unexpected hr %#lx.\n", hr); + + hr = WindowsCreateString(threadpool_class_name, wcslen(threadpool_class_name), &classid); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = RoGetActivationFactory(classid, &IID_IActivationFactory, (void **)&factory); + ok(hr == S_OK || broken(hr == REGDB_E_CLASSNOTREG), "Unexpected hr %#lx.\n", hr); + if (hr == REGDB_E_CLASSNOTREG) + { + win_skip("%s runtimeclass not registered, skipping tests.\n", wine_dbgstr_w(threadpool_class_name)); + return; + } + + check_interface(factory, &IID_IInspectable, TRUE); + check_interface(factory, &IID_IAgileObject, TRUE); + check_interface(factory, &IID_IThreadPoolStatics, TRUE); + + hr = IActivationFactory_QueryInterface(factory, &IID_IThreadPoolStatics, (void **)&threadpool_statics); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + check_interface(threadpool_statics, &IID_IInspectable, TRUE); + check_interface(threadpool_statics, &IID_IAgileObject, TRUE); + + IThreadPoolStatics_Release(threadpool_statics); + IActivationFactory_Release(factory); + WindowsDeleteString(classid); + + RoUninitialize(); +} + +struct work_item +{ + IWorkItemHandler IWorkItemHandler_iface; + LONG refcount; + HANDLE event; +}; + +static struct work_item * impl_from_IWorkItemHandler(IWorkItemHandler *iface) +{ + return CONTAINING_RECORD(iface, struct work_item, IWorkItemHandler_iface); +} + +static HRESULT STDMETHODCALLTYPE work_item_QueryInterface(IWorkItemHandler *iface, REFIID riid, void **obj) +{ + if (IsEqualIID(riid, &IID_IWorkItemHandler) + || IsEqualIID(riid, &IID_IUnknown)) + { + *obj = iface; + IWorkItemHandler_AddRef(iface); + return S_OK; + } + + *obj = NULL; + return E_NOINTERFACE; +} + +static ULONG STDMETHODCALLTYPE work_item_AddRef(IWorkItemHandler *iface) +{ + struct work_item *item = impl_from_IWorkItemHandler(iface); + return InterlockedIncrement(&item->refcount); +} + +static ULONG STDMETHODCALLTYPE work_item_Release(IWorkItemHandler *iface) +{ + struct work_item *item = impl_from_IWorkItemHandler(iface); + ULONG refcount = InterlockedDecrement(&item->refcount); + + if (!refcount) + { + CloseHandle(item->event); + free(item); + } + + return refcount; +} + +static HRESULT STDMETHODCALLTYPE work_item_Invoke(IWorkItemHandler *iface, IAsyncAction *action) +{ + struct work_item *item = impl_from_IWorkItemHandler(iface); + IAsyncActionCompletedHandler *handler; + IAsyncInfo *async_info; + AsyncStatus status; + HRESULT hr; + + hr = IAsyncAction_QueryInterface(action, &IID_IAsyncInfo, (void **)&async_info); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + hr = IAsyncInfo_get_Status(async_info, &status); + todo_wine + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (SUCCEEDED(hr)) ok(status == Started, "Unexpected status %d.\n", status); + + hr = IAsyncInfo_Cancel(async_info); + todo_wine + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + IAsyncInfo_Release(async_info); + + hr = IAsyncAction_GetResults(action); + todo_wine + ok(hr == E_ILLEGAL_METHOD_CALL, "Unexpected hr %#lx.\n", hr); + + handler = (void *)0xdeadbeef; + hr = IAsyncAction_get_Completed(action, &handler); + todo_wine + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine + ok(!handler, "Unexpected pointer %p.\n", handler); + + hr = IAsyncAction_put_Completed(action, NULL); + todo_wine + ok(hr == E_POINTER, "Unexpected hr %#lx.\n", hr); + + SetEvent(item->event); + + return S_OK; +} + +static const IWorkItemHandlerVtbl work_item_vtbl = +{ + work_item_QueryInterface, + work_item_AddRef, + work_item_Release, + work_item_Invoke, +}; + +static HRESULT create_work_item(IWorkItemHandler **item) +{ + struct work_item *object; + + *item = NULL; + + if (!(object = calloc(1, sizeof(*object)))) + return E_OUTOFMEMORY; + + object->IWorkItemHandler_iface.lpVtbl = &work_item_vtbl; + object->refcount = 1; + object->event = CreateEventW(NULL, FALSE, FALSE, NULL); + + *item = &object->IWorkItemHandler_iface; + + return S_OK; +} + +static void test_RunAsync(void) +{ + IActivationFactory *factory = NULL; + IThreadPoolStatics *threadpool_statics; + IWorkItemHandler *item_iface; + struct work_item *item; + IAsyncAction *action; + HSTRING classid; + HRESULT hr; + DWORD ret; + + hr = RoInitialize(RO_INIT_MULTITHREADED); + ok(SUCCEEDED(hr), "Unexpected hr %#lx.\n", hr); + + hr = WindowsCreateString(threadpool_class_name, wcslen(threadpool_class_name), &classid); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = RoGetActivationFactory(classid, &IID_IActivationFactory, (void **)&factory); + ok(hr == S_OK || broken(hr == REGDB_E_CLASSNOTREG), "Unexpected hr %#lx.\n", hr); + if (hr == REGDB_E_CLASSNOTREG) + return; + + hr = IActivationFactory_QueryInterface(factory, &IID_IThreadPoolStatics, (void **)&threadpool_statics); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + + hr = IThreadPoolStatics_RunAsync(threadpool_statics, NULL, &action); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + hr = create_work_item(&item_iface); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + item = impl_from_IWorkItemHandler(item_iface); + + hr = IThreadPoolStatics_RunWithPriorityAsync(threadpool_statics, item_iface, WorkItemPriority_High + 1, &action); + ok(hr == E_INVALIDARG, "Unexpected hr %#lx.\n", hr); + + hr = IThreadPoolStatics_RunAsync(threadpool_statics, item_iface, &action); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ret = WaitForSingleObject(item->event, 1000); + ok(!ret, "Unexpected wait result %lu.\n", ret); + + check_interface(action, &IID_IAsyncAction, TRUE); + check_interface(action, &IID_IAsyncInfo, TRUE); + check_interface(action, &IID_IInspectable, TRUE); + + IAsyncAction_Release(action); + + hr = IThreadPoolStatics_RunWithPriorityAsync(threadpool_statics, item_iface, WorkItemPriority_Normal, &action); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ret = WaitForSingleObject(item->event, 1000); + ok(!ret, "Unexpected wait result %lu.\n", ret); + IAsyncAction_Release(action); + + hr = IThreadPoolStatics_RunWithPriorityAndOptionsAsync(threadpool_statics, item_iface, WorkItemPriority_Normal, + WorkItemOptions_TimeSliced, &action); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ret = WaitForSingleObject(item->event, 1000); + ok(!ret, "Unexpected wait result %lu.\n", ret); + IAsyncAction_Release(action); + + hr = IThreadPoolStatics_RunWithPriorityAndOptionsAsync(threadpool_statics, item_iface, WorkItemPriority_Low, + WorkItemOptions_TimeSliced, &action); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ret = WaitForSingleObject(item->event, 1000); + ok(!ret, "Unexpected wait result %lu.\n", ret); + IAsyncAction_Release(action); + + hr = IThreadPoolStatics_RunWithPriorityAndOptionsAsync(threadpool_statics, item_iface, WorkItemPriority_High, + WorkItemOptions_TimeSliced, &action); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ret = WaitForSingleObject(item->event, 1000); + ok(!ret, "Unexpected wait result %lu.\n", ret); + IAsyncAction_Release(action); + + IWorkItemHandler_Release(item_iface); + + IThreadPoolStatics_Release(threadpool_statics); + IActivationFactory_Release(factory); + + WindowsDeleteString(classid); + + RoUninitialize(); +} + +START_TEST(threadpool) +{ + test_interfaces(); + test_RunAsync(); +}