From 743ec3e36949d022fe49c8fe22a38751cf16727c Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Sat, 2 Sep 2023 10:36:48 +0300 Subject: [PATCH 001/146] Fix non-dependent friend function __get_placeholder_offset Because the friend function is non-dependent, it causes an invalid redeclaration when __placeholder is instantiated multiple times. --- include/stdexec/__detail/__meta.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/stdexec/__detail/__meta.hpp b/include/stdexec/__detail/__meta.hpp index d6c420726..d9faa3a40 100644 --- a/include/stdexec/__detail/__meta.hpp +++ b/include/stdexec/__detail/__meta.hpp @@ -903,7 +903,7 @@ namespace stdexec { template struct __placeholder { - friend constexpr std::size_t __get_placeholder_offset(void*) noexcept { + friend constexpr std::size_t __get_placeholder_offset(void*, __placeholder* = nullptr) noexcept { return _Np; } }; From 599c62ac2511c211b71ff1e3b6dbbcaace947197 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Sat, 2 Sep 2023 10:23:43 +0300 Subject: [PATCH 002/146] Fix configuration and add detection of clang-cl --- CMakeLists.txt | 60 ++++++++++++++++----------- include/stdexec/__detail/__config.hpp | 6 +++ 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92338fc41..8e4edb2fb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,13 @@ rapids_find_package(Threads REQUIRED ############################################################################## # - Main library targets ----------------------------------------------------- +# Detect the compiler frontend (GNU, Clang, MSVC, etc.) +if(DEFINED CMAKE_CXX_COMPILER_FRONTEND_VARIANT) + set(stdexec_compiler_frontend ${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}) +else() + set(stdexec_compiler_frontend ${CMAKE_CXX_COMPILER_ID}) +endif() + set(stdexec_export_targets) # Define the main library @@ -152,34 +159,39 @@ set(CMAKE_SKIP_INSTALL_ALL_DEPENDENCY ON) add_library(stdexec_executable_flags INTERFACE) # Enable warnings -target_compile_options(stdexec_executable_flags INTERFACE - $<$,$,$>: - -Wall> - $<$: - /W4>) +if("${stdexec_compiler_frontend}" STREQUAL "GNU" OR "${stdexec_compiler_frontend}" STREQUAL "AppleClang") + target_compile_options(stdexec_executable_flags INTERFACE -Wall) +endif() +if("${stdexec_compiler_frontend}" STREQUAL "MSVC") + target_compile_options(stdexec_executable_flags INTERFACE /W4) +endif() -# Increase the error limit with NVC++ -target_compile_options(stdexec_executable_flags INTERFACE - $<$:-e1000> - ) +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "NVHPC") + # Increase the error limit with NVC++ + target_compile_options(stdexec_executable_flags INTERFACE -e1000) -# Silence warnings with GCC -target_compile_options(stdexec_executable_flags INTERFACE - $<$:-Wno-non-template-friend> - ) + # Silence warnings with NVHPC + target_compile_options(stdexec_executable_flags INTERFACE --diag_suppress177,550,111,497,554) +endif() -# Silence warnings with NVHPC -target_compile_options(stdexec_executable_flags INTERFACE - $<$:--diag_suppress177,550,111,497,554> - ) +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") + # Silence warnings with GCC + target_compile_options(stdexec_executable_flags INTERFACE -Wno-non-template-friend) +endif() -# Template backtrace limit -target_compile_options(stdexec_executable_flags INTERFACE - $<$,$>: - -ferror-limit=0 - -fmacro-backtrace-limit=0 - -ftemplate-backtrace-limit=0> - ) +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang") + set(clang_option_prefix "") + + if("${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC") + set(clang_option_prefix "/clang:") + endif() + + # Template backtrace limit + target_compile_options(stdexec_executable_flags INTERFACE + "${clang_option_prefix}-ferror-limit=0" + "${clang_option_prefix}-fmacro-backtrace-limit=0" + "${clang_option_prefix}-ftemplate-backtrace-limit=0") +endif() # # Always enable colored output # target_compile_options(stdexec_executable_flags INTERFACE diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index 5369fc289..c20805e0e 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -56,6 +56,9 @@ #define STDEXEC_NVHPC() 1 #elif defined(__clang__) #define STDEXEC_CLANG() 1 +#if defined(_MSC_VER) +#define STDEXEC_CLANG_CL() 1 +#endif #elif defined(__GNUC__) #define STDEXEC_GCC() 1 #elif defined(_MSC_VER) @@ -71,6 +74,9 @@ #ifndef STDEXEC_CLANG #define STDEXEC_CLANG() 0 #endif +#ifndef STDEXEC_CLANG_CL +#define STDEXEC_CLANG_CL() 0 +#endif #ifndef STDEXEC_GCC #define STDEXEC_GCC() 0 #endif From 18c55406dba3110736a5c1a41194d7751e539c84 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Sat, 2 Sep 2023 10:27:57 +0300 Subject: [PATCH 003/146] MSVC: Use [[msvc::no_unique_address]] in MSVC and Clang-CL --- include/stdexec/__detail/__config.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index c20805e0e..e56d5a294 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -141,6 +141,10 @@ // NVBUG #4067067 #if STDEXEC_NVHPC() #define STDEXEC_NO_UNIQUE_ADDRESS +#elif STDEXEC_MSVC() +#define STDEXEC_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#elif STDEXEC_CLANG_CL() +#define STDEXEC_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] #else #define STDEXEC_NO_UNIQUE_ADDRESS [[no_unique_address]] #endif @@ -151,7 +155,7 @@ #if STDEXEC_GCC() #define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS #else -#define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS [[no_unique_address]] +#define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS STDEXEC_NO_UNIQUE_ADDRESS #endif #if STDEXEC_CLANG() && defined(__CUDACC__) From 2c74dc730e5851767694b9393b9dbae717c93d14 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Wed, 23 Aug 2023 18:22:16 +0300 Subject: [PATCH 004/146] MSVC: Add workaround for any_sender_of __schedule_sender_fn --- include/exec/any_sender_of.hpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/include/exec/any_sender_of.hpp b/include/exec/any_sender_of.hpp index 221ec42e7..7e54a881d 100644 --- a/include/exec/any_sender_of.hpp +++ b/include/exec/any_sender_of.hpp @@ -1146,9 +1146,19 @@ namespace exec { __ret_equals_to>>, decltype(_SenderQueries)...>; +#if STDEXEC_MSVC() + // MSVCBUG https://developercommunity.visualstudio.com/t/ICE-and-non-ICE-bug-in-NTTP-argument-w/10361081 + + static constexpr auto __any_scheduler_noexcept_signature = + stdexec::get_completion_scheduler.signature; + template + using __schedule_sender_fn = typename __schedule_receiver::template any_sender< + __any_scheduler_noexcept_signature>; +#else template using __schedule_sender_fn = typename __schedule_receiver::template any_sender< stdexec::get_completion_scheduler.template signature>; +#endif using __schedule_sender = stdexec::__mapply, schedule_sender_queries>; From 3bfad3e9a96fe133c8928c51660f9d57e773fe34 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Wed, 23 Aug 2023 18:22:23 +0300 Subject: [PATCH 005/146] MSVC: Add workaround for __nth_pack_element --- include/stdexec/__detail/__meta.hpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/stdexec/__detail/__meta.hpp b/include/stdexec/__detail/__meta.hpp index d9faa3a40..79eca818f 100644 --- a/include/stdexec/__detail/__meta.hpp +++ b/include/stdexec/__detail/__meta.hpp @@ -928,10 +928,26 @@ namespace stdexec { template using __ignore_t = __ignore; +#if STDEXEC_MSVC() + // MSVCBUG https://developercommunity.visualstudio.com/t/Incorrect-function-template-argument-sub/10437827 + + template + struct __msvc_ignore_t { + __msvc_ignore_t() = default; + constexpr __msvc_ignore_t(auto&&...) noexcept { + } + }; + + template + _Ty&& __nth_pack_element_(__msvc_ignore_t<_Is>..., _Ty&& __t, _Us&&...) noexcept { + return (_Ty&&)__t; + } +#else template _Ty&& __nth_pack_element_(__ignore_t<_Is>..., _Ty&& __t, _Us&&...) noexcept { return (_Ty&&) __t; } +#endif template constexpr decltype(auto) __nth_pack_element(_Ts&&... __ts) noexcept { From 0e9e8a16177c443c22840b309520721ba6cf0772 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Wed, 23 Aug 2023 18:22:26 +0300 Subject: [PATCH 006/146] MSVC: Add workaround for static_thread_pool --- include/exec/static_thread_pool.hpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/include/exec/static_thread_pool.hpp b/include/exec/static_thread_pool.hpp index ed74b7c96..5c3c43363 100644 --- a/include/exec/static_thread_pool.hpp +++ b/include/exec/static_thread_pool.hpp @@ -55,6 +55,17 @@ namespace exec { Shape, stdexec::__x>>; +#if STDEXEC_MSVC() + // MSVCBUG https://developercommunity.visualstudio.com/t/Alias-template-with-pack-expansion-in-no/10437850 + + template + struct __bulk_non_throwing + { + using __t = stdexec::__decayed_tuple; + static constexpr bool __v = noexcept(__t(std::declval()...)); + }; +#endif + template requires stdexec::__callable using bulk_non_throwing = // @@ -62,7 +73,11 @@ namespace exec { // If function invocation doesn't throw stdexec::__nothrow_callable && // and emplacing a tuple doesn't throw +#if STDEXEC_MSVC() + __bulk_non_throwing::__v +#else noexcept(stdexec::__decayed_tuple(std::declval()...)) +#endif // there's no need to advertise completion with `exception_ptr` >; From b0cdc0bf91799fce30c2ec90a5577651b8bfbe57 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Wed, 23 Aug 2023 18:22:28 +0300 Subject: [PATCH 007/146] MSVC: Add workaround for __debug::__normalize_sig_t --- include/stdexec/execution.hpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index cf4885460..d9f762f50 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -930,6 +930,21 @@ namespace stdexec { struct __completion_signatures { }; +#if STDEXEC_MSVC() + // MSVCBUG https://developercommunity.visualstudio.com/t/Explicit-variable-template-specialisatio/10360032 + // MSVCBUG https://developercommunity.visualstudio.com/t/Non-function-type-interpreted-as-functio/10447831 + + template + struct __normalize_sig; + + template + struct __normalize_sig<_Tag(_Args...)> { + using __type = _Tag (*)(_Args&&...); + }; + + template + using __normalize_sig_t = typename __normalize_sig<_Sig>::__type; +#else template extern int __normalize_sig; @@ -938,6 +953,7 @@ namespace stdexec { template using __normalize_sig_t = decltype(__normalize_sig<_Sig>); +#endif template struct __valid_completions { From ad7b680690d067d1753fdd82afe0050ee1df3701 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Wed, 23 Aug 2023 22:24:48 +0300 Subject: [PATCH 008/146] MSVC: Add workaround for exec::__any::__rec::__ref --- include/exec/any_sender_of.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/exec/any_sender_of.hpp b/include/exec/any_sender_of.hpp index 7e54a881d..139bf2181 100644 --- a/include/exec/any_sender_of.hpp +++ b/include/exec/any_sender_of.hpp @@ -631,7 +631,11 @@ namespace exec { template requires(__is_not_stop_token_query<_Queries> && ...) struct __ref, _Queries...> { +#if !STDEXEC_MSVC() + // MSVCBUG https://developercommunity.visualstudio.com/t/Private-member-inaccessible-when-used-in/10448363 + private: +#endif using __vtable_t = stdexec::__t<__vtable, _Queries...>>; struct __env_t { @@ -694,7 +698,11 @@ namespace exec { template requires(__is_stop_token_query<_Queries> || ...) struct __ref, _Queries...> { +#if !STDEXEC_MSVC() + // MSVCBUG https://developercommunity.visualstudio.com/t/Private-member-inaccessible-when-used-in/10448363 + private: +#endif using _FilteredQueries = __minvoke<__remove_if<__q<__is_never_stop_token_query>>, _Queries...>; using __vtable_t = stdexec::__t< From 194ecb2a4b85472ec033b41b7a58072d3a542f53 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Sat, 26 Aug 2023 11:34:42 +0300 Subject: [PATCH 009/146] MSVC: Add workaround for ICE in __sender_facade.hpp --- include/exec/__detail/__sender_facade.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/exec/__detail/__sender_facade.hpp b/include/exec/__detail/__sender_facade.hpp index 88edbf8fd..9e8defbd4 100644 --- a/include/exec/__detail/__sender_facade.hpp +++ b/include/exec/__detail/__sender_facade.hpp @@ -163,7 +163,7 @@ namespace exec { template auto __compute_completions_(completion_signatures<_Sigs...>*) -> decltype(__stl::__all_completions( - (__completions_from_sig_t<_Kernel, _Env, _Sigs>) nullptr...)); + static_cast<__completions_from_sig_t<_Kernel, _Env, _Sigs>>(nullptr)...)); template auto __compute_completions_(_NoCompletions*) -> _NoCompletions; From 80e66617c2486c8483bb16c976474d9202db5ac9 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Sun, 27 Aug 2023 16:07:39 +0300 Subject: [PATCH 010/146] MSVC: Add workaround for __get_awaiter co_await constraint --- include/stdexec/coroutine.hpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/include/stdexec/coroutine.hpp b/include/stdexec/coroutine.hpp index b2da3cd04..cd108fe31 100644 --- a/include/stdexec/coroutine.hpp +++ b/include/stdexec/coroutine.hpp @@ -50,11 +50,25 @@ namespace stdexec { } && // __with_await_suspend<_Awaiter, _Promise>; +#if STDEXEC_MSVC() + // MSVCBUG https://developercommunity.visualstudio.com/t/operator-co_await-not-found-in-requires/10452721 + + template + void __co_await_constraint(_Awaitable&& __awaitable) + requires requires { operator co_await((_Awaitable&&) __awaitable); }; +#endif + template decltype(auto) __get_awaiter(_Awaitable&& __awaitable, void*) { if constexpr (requires { ((_Awaitable&&) __awaitable).operator co_await(); }) { return ((_Awaitable&&) __awaitable).operator co_await(); - } else if constexpr (requires { operator co_await((_Awaitable&&) __awaitable); }) { + } else if constexpr (requires { +#if STDEXEC_MSVC() + __co_await_constraint((_Awaitable&&) __awaitable); +#else + operator co_await((_Awaitable&&) __awaitable); +#endif + }) { return operator co_await((_Awaitable&&) __awaitable); } else { return (_Awaitable&&) __awaitable; @@ -69,7 +83,13 @@ namespace stdexec { requires { __promise->await_transform((_Awaitable&&) __awaitable).operator co_await(); }) { return __promise->await_transform((_Awaitable&&) __awaitable).operator co_await(); } else if constexpr ( - requires { operator co_await(__promise->await_transform((_Awaitable&&) __awaitable)); }) { + requires { +#if STDEXEC_MSVC() + __co_await_constraint(__promise->await_transform((_Awaitable&&) __awaitable)); +#else + operator co_await(__promise->await_transform((_Awaitable&&) __awaitable)); +#endif + }) { return operator co_await(__promise->await_transform((_Awaitable&&) __awaitable)); } else { return __promise->await_transform((_Awaitable&&) __awaitable); From 556c9e659d8c2d1643bec417472a3ad7bb74dc26 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Sat, 2 Sep 2023 13:36:10 +0300 Subject: [PATCH 011/146] MSVC: Add workaround for std::false_type await_ready See https://developercommunity.visualstudio.com/t/Incorrect-codegen-in-coroutine-when-awai/10454054 --- include/exec/on_coro_disposition.hpp | 4 ++-- include/exec/task.hpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/include/exec/on_coro_disposition.hpp b/include/exec/on_coro_disposition.hpp index 44ea44126..cf2a784bf 100644 --- a/include/exec/on_coro_disposition.hpp +++ b/include/exec/on_coro_disposition.hpp @@ -101,8 +101,8 @@ namespace exec { private: struct __final_awaitable { - static std::false_type await_ready() noexcept { - return {}; + static constexpr bool await_ready() noexcept { + return false; } static __coro::coroutine_handle<> diff --git a/include/exec/task.hpp b/include/exec/task.hpp index e9fb05b0a..1c51624e4 100644 --- a/include/exec/task.hpp +++ b/include/exec/task.hpp @@ -334,8 +334,8 @@ namespace exec { private: struct __final_awaitable { - static std::false_type await_ready() noexcept { - return {}; + static constexpr bool await_ready() noexcept { + return false; } static __coro::coroutine_handle<> @@ -422,8 +422,8 @@ namespace exec { __coro_.destroy(); } - static std::false_type await_ready() noexcept { - return {}; + static constexpr bool await_ready() noexcept { + return false; } template From 72749f4d8aee11e3f221c484f1e26f2e040b8053 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Tue, 29 Aug 2023 19:26:39 +0300 Subject: [PATCH 012/146] MSVC: Add workaround for __sync_wait::__cust_sigs --- include/stdexec/execution.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index d9f762f50..e97389587 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -6456,7 +6456,7 @@ namespace stdexec { using _Sender = __0; template using __cust_sigs = __types< - tag_invoke_t(_Tag, __get_sender_domain_t(_Sender), _Sender), + tag_invoke_t(_Tag, __get_sender_domain_t(*)(_Sender), _Sender), tag_invoke_t(_Tag, _Sender)>; template From 4ccd7534fb6cb0b27874cde99ad50fef07b20fe7 Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Sat, 2 Sep 2023 10:29:26 +0300 Subject: [PATCH 013/146] MSVC: Disable [[msvc::no_unique_address]] due to incorrect codegen --- include/stdexec/__detail/__config.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index e56d5a294..d6a448501 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -142,7 +142,8 @@ #if STDEXEC_NVHPC() #define STDEXEC_NO_UNIQUE_ADDRESS #elif STDEXEC_MSVC() -#define STDEXEC_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +// MSVCBUG https://developercommunity.visualstudio.com/t/Incorrect-codegen-when-using-msvc::no_/10452874 +#define STDEXEC_NO_UNIQUE_ADDRESS // [[msvc::no_unique_address]] #elif STDEXEC_CLANG_CL() #define STDEXEC_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] #else From 3a03072ecfb2437e1762067a3c25acbb2d9fbf9b Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Sat, 2 Sep 2023 15:04:19 +0300 Subject: [PATCH 014/146] MSVC: Add workaround for __connect_awaitable --- include/stdexec/execution.hpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index e97389587..3e9e77ca0 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -1740,8 +1740,19 @@ namespace stdexec { } ~__operation_base() { - if (__coro_) + if (__coro_) { +#if STDEXEC_MSVC() + // MSVCBUG https://developercommunity.visualstudio.com/t/Double-destroy-of-a-local-in-coroutine-d/10456428 + + // Reassign __coro_ before calling destroy to make the mutation + // observable and to hopefully ensure that the compiler does not eliminate it. + auto __coro = __coro_; + __coro_ = {}; + __coro.destroy(); +#else __coro_.destroy(); +#endif + } } friend void tag_invoke(start_t, __operation_base& __self) noexcept { From 98427d0b6b990d675ef6304730be609f3e6fbe8c Mon Sep 17 00:00:00 2001 From: Lauri Vasama Date: Sat, 2 Sep 2023 16:30:33 +0300 Subject: [PATCH 015/146] MSVC: Add workaround for coroutine destroy in await_suspend --- include/exec/at_coroutine_exit.hpp | 3 +- include/exec/on_coro_disposition.hpp | 3 +- include/stdexec/execution.hpp | 101 +++++++++++++++++++++++++++ 3 files changed, 103 insertions(+), 4 deletions(-) diff --git a/include/exec/at_coroutine_exit.hpp b/include/exec/at_coroutine_exit.hpp index d37c2e47a..8b04f4c46 100644 --- a/include/exec/at_coroutine_exit.hpp +++ b/include/exec/at_coroutine_exit.hpp @@ -160,8 +160,7 @@ namespace exec { auto __coro = __p.__is_unhandled_stopped_ ? __p.continuation().unhandled_stopped() : __p.continuation().handle(); - __h.destroy(); - return __coro; + return STDEXEC_DESTROY_AND_CONTINUE(__h, __coro); } void await_resume() const noexcept { diff --git a/include/exec/on_coro_disposition.hpp b/include/exec/on_coro_disposition.hpp index cf2a784bf..6ccd8c3b7 100644 --- a/include/exec/on_coro_disposition.hpp +++ b/include/exec/on_coro_disposition.hpp @@ -111,8 +111,7 @@ namespace exec { auto __coro = __p.__is_unhandled_stopped_ ? __p.continuation().unhandled_stopped() : __p.continuation().handle(); - __h.destroy(); - return __coro; + return STDEXEC_DESTROY_AND_CONTINUE(__h, __coro); } void await_resume() const noexcept { diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 3e9e77ca0..356f2c5e0 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -6662,6 +6662,107 @@ namespace stdexec { using __bad_pipe_sink_t = __mexception<_CANNOT_PIPE_INTO_A_SENDER_<>, _WITH_SENDER_<_Sender>>; } // namespace stdexec +#if STDEXEC_MSVC() +namespace stdexec { + // MSVCBUG https://developercommunity.visualstudio.com/t/Incorrect-codegen-in-await_suspend-aroun/10454102 + + // MSVC incorrectly allocates the return buffer for await_suspend calls within the suspended coroutine + // frame. When the suspended coroutine is destroyed within await_suspend, the continuation coroutine handle + // is not only used after free, but also overwritten by the debug malloc implementation when NRVO is in play. + + // This workaround delays the destruction of the suspended coroutine by wrapping the continuation in another + // coroutine which destroys the former and transfers execution to the original continuation. + + // The wrapping coroutine is thread-local and is reused within the thread for each destroy-and-continue sequence. + // The wrapping coroutine itself is destroyed at thread exit. + + namespace __destroy_and_continue_msvc { + struct __task { + struct promise_type { + __task get_return_object() noexcept { + return { __coro::coroutine_handle::from_promise(*this) }; + } + + static std::suspend_never initial_suspend() noexcept { + return {}; + } + + static std::suspend_never final_suspend() noexcept { + STDEXEC_ASSERT(!"Should never get here"); + return {}; + } + + static void return_void() noexcept { + STDEXEC_ASSERT(!"Should never get here"); + } + + static void unhandled_exception() noexcept { + STDEXEC_ASSERT(!"Should never get here"); + } + }; + + __coro::coroutine_handle<> __coro_; + }; + + struct __continue_t { + static constexpr bool await_ready() noexcept { + return false; + } + + __coro::coroutine_handle<> await_suspend(__coro::coroutine_handle<>) noexcept { + return __continue_; + } + + static void await_resume() noexcept { + } + + __coro::coroutine_handle<> __continue_; + }; + + struct __context { + __coro::coroutine_handle<> __destroy_; + __coro::coroutine_handle<> __continue_; + }; + + inline __task __co_impl(__context& __c) { + while (true) { + co_await __continue_t{ __c.__continue_ }; + __c.__destroy_.destroy(); + } + } + + struct __context_and_coro { + __context_and_coro() { + __context_.__continue_ = __coro::noop_coroutine(); + __coro_ = __co_impl(__context_).__coro_; + } + + ~__context_and_coro() { + __coro_.destroy(); + } + + __context __context_; + __coro::coroutine_handle<> __coro_; + }; + + inline __coro::coroutine_handle<> __impl( + __coro::coroutine_handle<> __destroy, + __coro::coroutine_handle<> __continue) { + static thread_local __context_and_coro __c; + __c.__context_.__destroy_ = __destroy; + __c.__context_.__continue_ = __continue; + return __c.__coro_; + } + } // namespace __destroy_and_continue_msvc +} // namespace stdexec + +#define STDEXEC_DESTROY_AND_CONTINUE(__destroy, __continue) \ + (::stdexec::__destroy_and_continue_msvc::__impl(__destroy, __continue)) +#else +#define STDEXEC_DESTROY_AND_CONTINUE(__destroy, __continue) \ + (__destroy.destroy(), __continue) +#endif + // For issuing a meaningful diagnostic for the erroneous `snd1 | snd2`. template requires stdexec::__ok> From 34d05302c0b23dda7e62350d9d4d4345e22ba07b Mon Sep 17 00:00:00 2001 From: Jake Hemstad Date: Tue, 26 Sep 2023 13:06:15 -0500 Subject: [PATCH 016/146] Enable verbose compiler output in cmake build step --- .github/workflows/ci.gpu.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.gpu.yml b/.github/workflows/ci.gpu.yml index b3d151bd5..5d3bf649b 100644 --- a/.github/workflows/ci.gpu.yml +++ b/.github/workflows/ci.gpu.yml @@ -51,7 +51,7 @@ jobs: -DCMAKE_CUDA_COMPILER="$cxx" \ -DCMAKE_CUDA_ARCHITECTURES=${{ matrix.sm }}; # Compile - cmake --build build; + cmake --build build -v; # Tests ctest --test-dir build --verbose --output-on-failure --timeout 60; # Examples From cb522b406fbb1b8f30e95e1a237bec2633ef64ac Mon Sep 17 00:00:00 2001 From: Jake Hemstad Date: Tue, 26 Sep 2023 15:04:27 -0500 Subject: [PATCH 017/146] Add printing sccache stats. --- .github/workflows/ci.gpu.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.gpu.yml b/.github/workflows/ci.gpu.yml index 5d3bf649b..7429a474c 100644 --- a/.github/workflows/ci.gpu.yml +++ b/.github/workflows/ci.gpu.yml @@ -52,6 +52,10 @@ jobs: -DCMAKE_CUDA_ARCHITECTURES=${{ matrix.sm }}; # Compile cmake --build build -v; + + # Print sccache stats + sccache -s + # Tests ctest --test-dir build --verbose --output-on-failure --timeout 60; # Examples @@ -59,6 +63,7 @@ jobs: ./build/examples/nvexec/maxwell_cpu_mt --iterations=1000 --N=512 --run-std --run-stdpar --run-thread-pool-scheduler ./build/examples/nvexec/maxwell_gpu_s --iterations=1000 --N=512 --run-cuda --run-stdpar --run-stream-scheduler + ci-gpu: runs-on: ubuntu-latest name: CI (GPU) From 9e26b1b78aef1d6a57e9275f9854e0473a6a80bb Mon Sep 17 00:00:00 2001 From: Maikel Nadolski Date: Thu, 28 Sep 2023 21:29:17 +0200 Subject: [PATCH 018/146] Fix completion signatures of ignore_all_values --- include/exec/sequence_senders.hpp | 6 +++- test/exec/sequence/test_ignore_all_values.cpp | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 5dbc92cc8..868f7ac47 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -267,7 +267,11 @@ namespace exec { template using __sequence_completion_signatures_of_t = stdexec::__concat_completion_signatures_t< - stdexec::completion_signatures, + stdexec::__try_make_completion_signatures< + _Sequence, + _Env, + stdexec::completion_signatures, + stdexec::__mconst>>, stdexec::__mapply< stdexec::__q, stdexec::__mapply< diff --git a/test/exec/sequence/test_ignore_all_values.cpp b/test/exec/sequence/test_ignore_all_values.cpp index b4e6df8c1..2716ffac9 100644 --- a/test/exec/sequence/test_ignore_all_values.cpp +++ b/test/exec/sequence/test_ignore_all_values.cpp @@ -72,4 +72,35 @@ TEST_CASE("ignore_all_values - ignore just_error()", "[sequence_senders][ignore_ completion_signatures, stdexec::completion_signatures_of_t>); CHECK_THROWS(stdexec::sync_wait(sndr)); +} + +struct sequence_op { + friend void tag_invoke(stdexec::start_t, sequence_op&) noexcept { + } +}; + +template +struct sequence { + using is_sender = exec::sequence_tag; + + using completion_signatures = + stdexec::completion_signatures; + + using item_types = exec::item_types; + + friend sequence_op tag_invoke(exec::subscribe_t, sequence, stdexec::__ignore) noexcept { + return sequence_op{}; + } +}; + +TEST_CASE("ignore_all_values - Merge error and stop signatures from sequence and items") { + using just_t = decltype(stdexec::just_error(std::make_exception_ptr(std::runtime_error("test")))); + sequence seq; + auto ignore = exec::ignore_all_values(seq); + using Sigs = stdexec::completion_signatures_of_t; + using ExpectedSigs = stdexec::completion_signatures< + stdexec::set_value_t(), + stdexec::set_error_t(int), + stdexec::set_error_t(std::exception_ptr)>; + STATIC_REQUIRE(std::same_as); } \ No newline at end of file From c4c979bc791b3b2990afb1f1ff34bce569ed8b12 Mon Sep 17 00:00:00 2001 From: Maikel Nadolski Date: Thu, 28 Sep 2023 21:37:02 +0200 Subject: [PATCH 019/146] Avoid collision to stdexec::__transform. Change namespace name __transform to __transform_each --- include/exec/sequence/transform_each.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/exec/sequence/transform_each.hpp b/include/exec/sequence/transform_each.hpp index b09582598..4bdb1f86a 100644 --- a/include/exec/sequence/transform_each.hpp +++ b/include/exec/sequence/transform_each.hpp @@ -21,7 +21,7 @@ #include "../__detail/__basic_sequence.hpp" namespace exec { - namespace __transform { + namespace __transform_each { using namespace stdexec; template @@ -199,6 +199,6 @@ namespace exec { }; } - using __transform::transform_each_t; + using __transform_each::transform_each_t; inline constexpr transform_each_t transform_each{}; } \ No newline at end of file From d008f670e0fe617641293d5e12f1848114f6a6d7 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Thu, 28 Sep 2023 13:05:24 -0700 Subject: [PATCH 020/146] add a debug config for cuda debugging the current target --- .vscode/launch.json | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 52857b46c..53d36e65c 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -47,6 +47,17 @@ "initCommands": ["settings set target.disable-aslr false"], "args": "${input:CXX_PROGRAM_ARGS}", }, + { + "name": "CUDA Current Target (cuda-gdb)", + "type": "cuda-gdb", + "request": "launch", + "stopAtEntry": false, + "breakOnLaunch": false, + "internalConsoleOptions": "neverOpen", + "program": "${command:cmake.launchTargetPath}", + "cwd": "${command:cmake.launchTargetDirectory}", + "args": "${input:CXX_PROGRAM_ARGS}", + }, ], "inputs": [ { From 11028870cd3d0f237316fb8e67242c9a445e02ae Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Thu, 28 Sep 2023 13:04:06 -0700 Subject: [PATCH 021/146] port the write() adaptor to __sexpr and transform_sender --- include/stdexec/execution.hpp | 103 +++++++++++++++------------------- 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 2f180c919..594f45324 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -5617,16 +5617,17 @@ namespace stdexec { using __receiver_t = // __t<__detail::__receiver_with< &__operation_base<_ReceiverId, _Env>::__rcvr_, __get_env>>; - template + template struct __operation : __operation_base<_ReceiverId, _Env> { - using _Sender = __t<_SenderId>; + using _CvrefSender = __cvref_t<_CvrefSenderId>; + using _Receiver = __t<_ReceiverId>; using __base_t = __operation_base<_ReceiverId, _Env>; using __receiver_t = __write_::__receiver_t<_ReceiverId, _Env>; - connect_result_t<_Sender, __receiver_t> __state_; + connect_result_t<_CvrefSender, __receiver_t> __state_; - __operation(_Sender&& __sndr, auto&& __rcvr, auto&& __env) - : __base_t{(decltype(__rcvr)) __rcvr, (decltype(__env)) __env} - , __state_{stdexec::connect((_Sender&&) __sndr, __receiver_t{{}, this})} { + __operation(_CvrefSender&& __sndr, _Receiver&& __rcvr, auto&& __env) + : __base_t{(_Receiver&&) __rcvr, (decltype(__env)) __env} + , __state_{stdexec::connect((_CvrefSender&&) __sndr, __receiver_t{{}, this})} { } friend void tag_invoke(start_t, __operation& __self) noexcept { @@ -5634,69 +5635,55 @@ namespace stdexec { } }; - template - struct __sender { - using _Sender = stdexec::__t<_SenderId>; - - template - using __receiver_t = __write_::__receiver_t<__id<_Receiver>, _Env>; - template - using __operation_t = - __operation<__id<__copy_cvref_t<_Self, _Sender>>, __id<_Receiver>, _Env>; - - struct __t { - using is_sender = void; - using __id = __sender; - _Sender __sndr_; - _Env __env_; + struct __write_t : __default_get_env<__write_t> { + template + auto operator()(_Sender&& __sndr, _Envs... __envs) const { + auto __domain = __get_sender_domain(__sndr); + return transform_sender( + __domain, + make_sender_expr<__write_t>(__join_env(std::move(__envs)...), (_Sender&&) __sndr)); + } - template <__decays_to<__t> _Self, receiver _Receiver> - requires sender_to<__copy_cvref_t<_Self, _Sender>, __receiver_t<_Receiver>> - friend auto tag_invoke(connect_t, _Self&& __self, _Receiver __rcvr) - -> __operation_t<_Self, _Receiver> { - return {((_Self&&) __self).__sndr_, (_Receiver&&) __rcvr, ((_Self&&) __self).__env_}; - } + template + auto operator()(_Envs... __envs) const -> __binder_back<__write_t, _Envs...> { + return {{}, {}, {std::move(__envs)...}}; + } - friend auto tag_invoke(stdexec::get_env_t, const __t& __self) // - noexcept(stdexec::__nothrow_callable) - -> stdexec::env_of_t { - return stdexec::get_env(__self.__sndr_); - } +#if STDEXEC_FRIENDSHIP_IS_LEXICAL() + private: + template + friend struct stdexec::__sexpr; +#endif - template <__decays_to<__t> _Self, class _BaseEnv> - friend auto tag_invoke(get_completion_signatures_t, _Self&&, _BaseEnv&&) - -> stdexec::__completion_signatures_of_t< - __copy_cvref_t<_Self, _Sender>, - __env::__env_join_t<_Env, _BaseEnv>> { - return {}; - } - }; - }; + template + using __receiver_t = __write_::__receiver_t<__id<_Receiver>, __decay_t<__data_of<_Self>>>; - struct __write_t { - template - using __sender_t = __t<__sender<__id<__decay_t<_Sender>>, __env::__env_join_t<_Envs...>>>; + template + using __operation_t = + __operation<__cvref_id<__child_of<_Self>>, __id<_Receiver>, __decay_t<__data_of<_Self>>>; - template - auto operator()(_Sender&& __sndr, _Envs... __envs) const - -> __sender_t<_Sender, __env::__env_join_t<_Envs...>> { - return {(_Sender&&) __sndr, __join_env(std::move(__envs)...)}; + template _Self, receiver _Receiver> + requires sender_to<__child_of<_Self>, __receiver_t<_Self, _Receiver>> + static auto connect(_Self&& __self, _Receiver __rcvr) -> __operation_t<_Self, _Receiver> { + return apply_sender( + (_Self&&) __self, + [&](__write_t, _Env&& __env, _Child&& __child) // + -> __operation_t<_Self, _Receiver> { + return {(_Child&&) __child, (_Receiver&&) __rcvr, (_Env&&) __env}; + }); } - template - auto operator()(_Envs... __envs) const -> __binder_back<__write_t, _Envs...> { - return {{}, {}, {std::move(__envs)...}}; + template _Self, class _Env> + static auto get_completion_signatures(_Self&&, _Env&&) + -> stdexec::__completion_signatures_of_t< + __child_of<_Self>, + __env::__env_join_t>&, _Env>> { } }; } // namespace __write_ - inline constexpr __write_::__write_t __write{}; - - namespace __detail { - template - inline constexpr __mconst< __write_::__sender<__name_of<__t<_SenderId>>, _Env > > - __name_of_v< __write_::__sender<_SenderId, _Env > >{}; - } + using __write_::__write_t; + inline constexpr __write_t __write{}; ///////////////////////////////////////////////////////////////////////////// // [execution.senders.adaptors.on] From b89f23fdf55c3f4978577276f4704d61dd9072ff Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Tue, 26 Sep 2023 06:52:46 -0700 Subject: [PATCH 022/146] exec::inline_scheduler is an alias for stdexec::__inln::__scheduler --- include/exec/inline_scheduler.hpp | 49 +------------------------------ include/stdexec/execution.hpp | 21 +++++++++---- 2 files changed, 16 insertions(+), 54 deletions(-) diff --git a/include/exec/inline_scheduler.hpp b/include/exec/inline_scheduler.hpp index f587d150e..7a8ff9015 100644 --- a/include/exec/inline_scheduler.hpp +++ b/include/exec/inline_scheduler.hpp @@ -22,52 +22,5 @@ namespace exec { // A simple scheduler that executes its continuation inline, on the // thread of the caller of start(). - struct inline_scheduler { - template - struct __op { - using R = stdexec::__t; - STDEXEC_NO_UNIQUE_ADDRESS R rec_; - - friend void tag_invoke(stdexec::start_t, __op& op) noexcept { - stdexec::set_value((R&&) op.rec_); - } - }; - - struct __sender { - using is_sender = void; - using completion_signatures = stdexec::completion_signatures; - - template - friend auto tag_invoke(stdexec::connect_t, __sender, R&& rec) // - noexcept(stdexec::__nothrow_constructible_from, R>) - -> __op>> { - return {(R&&) rec}; - } - - struct __env { - friend inline_scheduler - tag_invoke(stdexec::get_completion_scheduler_t, const __env&) // - noexcept { - return {}; - } - }; - - friend __env tag_invoke(stdexec::get_env_t, const __sender&) noexcept { - return {}; - } - }; - - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend __sender - tag_invoke(stdexec::schedule_t, const inline_scheduler&) noexcept { - return {}; - } - - friend stdexec::forward_progress_guarantee - tag_invoke(stdexec::get_forward_progress_guarantee_t, const inline_scheduler&) noexcept { - return stdexec::forward_progress_guarantee::weakly_parallel; - } - - bool operator==(const inline_scheduler&) const noexcept = default; - }; + using inline_scheduler = stdexec::__inln::__scheduler; } diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 594f45324..0b6da43b3 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -2503,6 +2503,8 @@ namespace stdexec { inline constexpr __submit_t __submit{}; namespace __inln { + struct __scheduler; + template struct __op : __immovable { _Receiver __recv_; @@ -2513,7 +2515,8 @@ namespace stdexec { }; struct __schedule_t { - static auto get_env(__ignore) noexcept; + static auto get_env(__ignore) noexcept + -> __env::__prop, __scheduler>; using __compl_sigs = stdexec::completion_signatures; static __compl_sigs get_completion_signatures(__ignore, __ignore); @@ -2529,17 +2532,23 @@ namespace stdexec { using __t = __scheduler; using __id = __scheduler; - friend auto tag_invoke(schedule_t, __scheduler) { + STDEXEC_DETAIL_CUDACC_HOST_DEVICE // + friend auto + tag_invoke(schedule_t, __scheduler) { return make_sender_expr<__schedule_t>(); } + friend forward_progress_guarantee + tag_invoke(get_forward_progress_guarantee_t, __scheduler) noexcept { + return forward_progress_guarantee::weakly_parallel; + } + bool operator==(const __scheduler&) const noexcept = default; }; - inline auto __schedule_t::get_env(__ignore) noexcept { - return __env::__env_fn{[](get_completion_scheduler_t) noexcept { - return __scheduler{}; - }}; + inline auto __schedule_t::get_env(__ignore) noexcept + -> __env::__prop, __scheduler> { + return __mkprop(get_completion_scheduler, __scheduler{}); } } From db35d371f7a24273b0f96a6b9bc239e3f11cf191 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sat, 30 Sep 2023 12:50:01 -0700 Subject: [PATCH 023/146] disable extra type checks by default; add cmake option to opt-in --- CMakeLists.txt | 9 +++++++++ include/stdexec/__detail/__config.hpp | 23 ++++++++++++++++++++--- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index e3489a1ce..c51589301 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,6 +143,12 @@ target_compile_options(stdexec INTERFACE $<$:/Zc:__cplusplus /Zc:preprocessor> ) +option(STDEXEC_ENABLE_EXTRA_TYPE_CHECKING "Enable extra type checking that is costly at compile-time" OFF) + +if (STDEXEC_ENABLE_EXTRA_TYPE_CHECKING) + target_compile_definitions(stdexec INTERFACE STDEXEC_ENABLE_EXTRA_TYPE_CHECKING) +endif() + add_library(STDEXEC::stdexec ALIAS stdexec) # Don't require building everything when installing @@ -199,6 +205,9 @@ target_compile_options(stdexec_executable_flags INTERFACE -include stdexec/__detail/__force_include.hpp> ) +target_compile_definitions(stdexec_executable_flags INTERFACE + STDEXEC_ENABLE_EXTRA_TYPE_CHECKING) + # Support target for examples and tests add_library(nvexec_executable_flags INTERFACE) diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index 5264d9ec3..ac95d5be1 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -239,14 +239,31 @@ #define STDEXEC_FUN_ARGS(...) STDEXEC_CAT(STDEXEC_EAT_THIS_DETAIL_, __VA_ARGS__)) #endif +// Configure extra type checking +#define STDEXEC_TYPE_CHECKING_ZERO() 0 +#define STDEXEC_TYPE_CHECKING_ONE() 1 +#define STDEXEC_TYPE_CHECKING_TWO() 2 + +#define STDEXEC_PROBE_TYPE_CHECKING_ STDEXEC_TYPE_CHECKING_ONE +#define STDEXEC_PROBE_TYPE_CHECKING_0 STDEXEC_TYPE_CHECKING_ZERO +#define STDEXEC_PROBE_TYPE_CHECKING_1 STDEXEC_TYPE_CHECKING_ONE +#define STDEXEC_PROBE_TYPE_CHECKING_STDEXEC_ENABLE_EXTRA_TYPE_CHECKING STDEXEC_TYPE_CHECKING_TWO + +#define STDEXEC_TYPE_CHECKING_WHICH3(...) STDEXEC_PROBE_TYPE_CHECKING_ ## __VA_ARGS__ +#define STDEXEC_TYPE_CHECKING_WHICH2(...) STDEXEC_TYPE_CHECKING_WHICH3(__VA_ARGS__) +#define STDEXEC_TYPE_CHECKING_WHICH STDEXEC_TYPE_CHECKING_WHICH2(STDEXEC_ENABLE_EXTRA_TYPE_CHECKING) + #ifndef STDEXEC_ENABLE_EXTRA_TYPE_CHECKING -// Compile times are bad enough on nvhpc. Disable extra type checking by default. -#if STDEXEC_NVHPC() +#define STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() 0 +#elif STDEXEC_TYPE_CHECKING_WHICH() == 2 +// do nothing +#elif STDEXEC_TYPE_CHECKING_WHICH() == 0 +#undef STDEXEC_ENABLE_EXTRA_TYPE_CHECKING #define STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() 0 #else +#undef STDEXEC_ENABLE_EXTRA_TYPE_CHECKING #define STDEXEC_ENABLE_EXTRA_TYPE_CHECKING() 1 #endif -#endif namespace stdexec { } From 17c9031e8ee4336f9e986c830f55b9cc604c6d3f Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 1 Oct 2023 14:35:35 -0700 Subject: [PATCH 024/146] avoid potentially using a moved-from scheduler in `continue_on` --- include/stdexec/execution.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 0b6da43b3..66bb8f604 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -6830,7 +6830,7 @@ namespace stdexec { return __write( transfer( ((_Closure&&) __clsur)( - transfer(__write((_Child&&) __child, __mkenv(__old)), (_Scheduler&&) __sched)), + transfer(__write((_Child&&) __child, __mkenv(__old)), __sched)), __old), __mkenv(__sched)); }), From d330d4266f859a4420ddbf85bdfdd4985cc2aaab Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 1 Oct 2023 15:39:39 -0700 Subject: [PATCH 025/146] name demangling for some nvexec sender types --- include/nvexec/stream/bulk.cuh | 12 ++++++++++++ include/nvexec/stream/ensure_started.cuh | 7 +++++++ include/nvexec/stream/launch.cuh | 7 +++++++ include/nvexec/stream/let_xxx.cuh | 7 +++++++ include/nvexec/stream/reduce.cuh | 7 +++++++ include/nvexec/stream/split.cuh | 7 +++++++ include/nvexec/stream/then.cuh | 7 +++++++ include/nvexec/stream/upon_error.cuh | 7 +++++++ include/nvexec/stream/upon_stopped.cuh | 7 +++++++ include/nvexec/stream/when_all.cuh | 7 +++++++ 10 files changed, 75 insertions(+) diff --git a/include/nvexec/stream/bulk.cuh b/include/nvexec/stream/bulk.cuh index 5ba807e05..bdf94fd74 100644 --- a/include/nvexec/stream/bulk.cuh +++ b/include/nvexec/stream/bulk.cuh @@ -371,3 +371,15 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { }; }; } + +namespace stdexec::__detail { + template + inline constexpr __mconst< + nvexec::STDEXEC_STREAM_DETAIL_NS::bulk_sender_t<__name_of<__t>, Shape, Fun>> + __name_of_v>{}; + + template + inline constexpr __mconst< + nvexec::STDEXEC_STREAM_DETAIL_NS::multi_gpu_bulk_sender_t<__name_of<__t>, Shape, Fun>> + __name_of_v>{}; +} diff --git a/include/nvexec/stream/ensure_started.cuh b/include/nvexec/stream/ensure_started.cuh index 9f9618907..312692013 100644 --- a/include/nvexec/stream/ensure_started.cuh +++ b/include/nvexec/stream/ensure_started.cuh @@ -359,3 +359,10 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { }; }; } + +namespace stdexec::__detail { + template + inline constexpr __mconst< + nvexec::STDEXEC_STREAM_DETAIL_NS::ensure_started_sender_t<__name_of<__t>>> + __name_of_v>{}; +} diff --git a/include/nvexec/stream/launch.cuh b/include/nvexec/stream/launch.cuh index e7bdbefd5..87535c3a5 100644 --- a/include/nvexec/stream/launch.cuh +++ b/include/nvexec/stream/launch.cuh @@ -179,3 +179,10 @@ namespace nvexec { inline constexpr STDEXEC_STREAM_DETAIL_NS::launch_t launch{}; } // namespace nvexec + +namespace stdexec::__detail { + template + inline constexpr __mconst< + nvexec::STDEXEC_STREAM_DETAIL_NS::launch_sender_t<__name_of<__t>, Fun>> + __name_of_v>{}; +} diff --git a/include/nvexec/stream/let_xxx.cuh b/include/nvexec/stream/let_xxx.cuh index 01eb7afa7..2582576e5 100644 --- a/include/nvexec/stream/let_xxx.cuh +++ b/include/nvexec/stream/let_xxx.cuh @@ -251,3 +251,10 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { }; }; } + +namespace stdexec::__detail { + template + inline constexpr __mconst< + nvexec::STDEXEC_STREAM_DETAIL_NS::let_sender_t<__name_of<__t>, Fun, Set>> + __name_of_v>{}; +} diff --git a/include/nvexec/stream/reduce.cuh b/include/nvexec/stream/reduce.cuh index 41f08b8d0..49fb51df4 100644 --- a/include/nvexec/stream/reduce.cuh +++ b/include/nvexec/stream/reduce.cuh @@ -150,3 +150,10 @@ namespace nvexec { inline constexpr STDEXEC_STREAM_DETAIL_NS::reduce_t reduce{}; } + +namespace stdexec::__detail { + template + extern __mconst< + nvexec::STDEXEC_STREAM_DETAIL_NS::reduce_::sender_t<__name_of<__t>, Init, Fun>> + __name_of_v>; +} diff --git a/include/nvexec/stream/split.cuh b/include/nvexec/stream/split.cuh index 99c53b37c..77566de4c 100644 --- a/include/nvexec/stream/split.cuh +++ b/include/nvexec/stream/split.cuh @@ -342,3 +342,10 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { }; }; } + +namespace stdexec::__detail { + template + extern __mconst< + nvexec::STDEXEC_STREAM_DETAIL_NS::split_sender_t<__name_of<__t>>> + __name_of_v>; +} diff --git a/include/nvexec/stream/then.cuh b/include/nvexec/stream/then.cuh index 2d80372c9..985481d73 100644 --- a/include/nvexec/stream/then.cuh +++ b/include/nvexec/stream/then.cuh @@ -195,3 +195,10 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { }; }; } + +namespace stdexec::__detail { + template + inline constexpr __mconst< + nvexec::STDEXEC_STREAM_DETAIL_NS::then_sender_t<__name_of<__t>, Fun>> + __name_of_v>{}; +} diff --git a/include/nvexec/stream/upon_error.cuh b/include/nvexec/stream/upon_error.cuh index 39ed2b2e2..d0089a5cd 100644 --- a/include/nvexec/stream/upon_error.cuh +++ b/include/nvexec/stream/upon_error.cuh @@ -178,3 +178,10 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { }; }; } + +namespace stdexec::__detail { + template + inline constexpr __mconst< + nvexec::STDEXEC_STREAM_DETAIL_NS::upon_error_sender_t<__name_of<__t>, Fun>> + __name_of_v>{}; +} diff --git a/include/nvexec/stream/upon_stopped.cuh b/include/nvexec/stream/upon_stopped.cuh index a80663cdb..005c093f9 100644 --- a/include/nvexec/stream/upon_stopped.cuh +++ b/include/nvexec/stream/upon_stopped.cuh @@ -153,3 +153,10 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { }; }; } + +namespace stdexec::__detail { + template + inline constexpr __mconst< + nvexec::STDEXEC_STREAM_DETAIL_NS::upon_stopped_sender_t<__name_of<__t>, Fun>> + __name_of_v>{}; +} diff --git a/include/nvexec/stream/when_all.cuh b/include/nvexec/stream/when_all.cuh index 26c3396f1..58ab89763 100644 --- a/include/nvexec/stream/when_all.cuh +++ b/include/nvexec/stream/when_all.cuh @@ -415,3 +415,10 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { }; }; } + +namespace stdexec::__detail { + template + inline constexpr __mconst< + nvexec::STDEXEC_STREAM_DETAIL_NS::when_all_sender_t>...>> + __name_of_v>{}; +} From ba691c8980a51473bbccb2f540b5deccd24a1d5d Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 1 Oct 2023 17:33:47 -0700 Subject: [PATCH 026/146] give the just senders proper tag types --- include/stdexec/execution.hpp | 28 ++++++++++++++++------------ test/exec/test_variant_sender.cpp | 8 ++++---- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 66bb8f604..f3329d456 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -2704,36 +2704,40 @@ namespace stdexec { } }; - inline constexpr struct __just_t : __just_impl<__just_t, set_value_t> { + struct just_t : __just_impl { template <__movable_value... _Ts> STDEXEC_DETAIL_CUDACC_HOST_DEVICE // auto operator()(_Ts&&... __ts) const noexcept((__nothrow_decay_copyable<_Ts> && ...)) { - return make_sender_expr<__just_t>(__decayed_tuple<_Ts...>{(_Ts&&) __ts...}); + return make_sender_expr(__decayed_tuple<_Ts...>{(_Ts&&) __ts...}); } - } just{}; + }; - inline constexpr struct __just_error_t : __just_impl<__just_error_t, set_error_t> { + struct just_error_t : __just_impl { template <__movable_value _Error> STDEXEC_DETAIL_CUDACC_HOST_DEVICE // auto operator()(_Error&& __err) const noexcept(__nothrow_decay_copyable<_Error>) { - return make_sender_expr<__just_error_t>(__decayed_tuple<_Error>{(_Error&&) __err}); + return make_sender_expr(__decayed_tuple<_Error>{(_Error&&) __err}); } - } just_error{}; + }; - inline constexpr struct __just_stopped_t : __just_impl<__just_stopped_t, set_stopped_t> { + struct just_stopped_t : __just_impl { STDEXEC_DETAIL_CUDACC_HOST_DEVICE // auto operator()() const noexcept { - return make_sender_expr<__just_stopped_t>(__decayed_tuple<>()); + return make_sender_expr(__decayed_tuple<>()); } - } just_stopped{}; + }; } - using __just::just; - using __just::just_error; - using __just::just_stopped; + using __just::just_t; + using __just::just_error_t; + using __just::just_stopped_t; + + inline constexpr just_t just {}; + inline constexpr just_error_t just_error {}; + inline constexpr just_stopped_t just_stopped {}; ///////////////////////////////////////////////////////////////////////////// // [execution.execute] diff --git a/test/exec/test_variant_sender.cpp b/test/exec/test_variant_sender.cpp index c51a105d8..72220725d 100644 --- a/test/exec/test_variant_sender.cpp +++ b/test/exec/test_variant_sender.cpp @@ -30,17 +30,17 @@ template overloaded(Ts...) -> overloaded; using just_int_t = decltype(just(0)); -using just_t = decltype(just()); +using just_void_t = decltype(just()); TEST_CASE("variant_sender - default constructible", "[types][variant_sender]") { - variant_sender variant{just()}; + variant_sender variant{just()}; CHECK(variant.index() == 0); } TEST_CASE("variant_sender - using an overloaded then adaptor", "[types][variant_sender]") { - variant_sender variant = just(); + variant_sender variant = just(); int index = -1; - STATIC_REQUIRE(sender>); + STATIC_REQUIRE(sender>); sync_wait(variant | then([&index](auto... xs) { index = sizeof...(xs); })); CHECK(index == 0); From 111e0c045beaa182a4b4536064e3ea14e3d00c4d Mon Sep 17 00:00:00 2001 From: Chris Cotter Date: Wed, 4 Oct 2023 13:13:54 -0600 Subject: [PATCH 027/146] Fix compiler warnings --- include/stdexec/execution.hpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index f3329d456..65d71f464 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -3307,7 +3307,7 @@ namespace stdexec { #endif template _Sender, class _Env> - static auto get_completion_signatures(_Sender&& __sndr, _Env&&) + static auto get_completion_signatures(_Sender&&, _Env&&) -> __completion_signatures_t<__decay_t<__data_of<_Sender>>, __child_of<_Sender>, _Env> { return {}; } @@ -3448,7 +3448,7 @@ namespace stdexec { #endif template _Sender, class _Env> - static auto get_completion_signatures(_Sender&& __sndr, _Env&&) + static auto get_completion_signatures(_Sender&&, _Env&&) -> __completion_signatures_t<__decay_t<__data_of<_Sender>>, __child_of<_Sender>, _Env> { return {}; } @@ -3592,7 +3592,7 @@ namespace stdexec { #endif template _Sender, class _Env> - static auto get_completion_signatures(_Sender&& __sndr, _Env&&) + static auto get_completion_signatures(_Sender&&, _Env&&) -> __completion_signatures_t<__decay_t<__data_of<_Sender>>, __child_of<_Sender>, _Env> { return {}; } @@ -3799,7 +3799,7 @@ namespace stdexec { using __shape_t = decltype(__decay_t<__data_of<_Sender>>::__shape_); template _Sender, class _Env> - static auto get_completion_signatures(_Sender&& __sndr, _Env&&) + static auto get_completion_signatures(_Sender&&, _Env&&) -> __completion_signatures<__child_of<_Sender>, _Env, __shape_t<_Sender>, __fun_t<_Sender>> { return {}; } @@ -4074,7 +4074,7 @@ namespace stdexec { } template _Self, class _OtherEnv> - static auto get_completion_signatures(_Self&& __self, _OtherEnv&&) + static auto get_completion_signatures(_Self&&, _OtherEnv&&) -> __call_result_t> { return {}; } @@ -4411,7 +4411,7 @@ namespace stdexec { } template _Self, class _OtherEnv> - static auto get_completion_signatures(_Self&& __self, _OtherEnv&&) + static auto get_completion_signatures(_Self&&, _OtherEnv&&) -> __call_result_t> { return {}; } @@ -4726,7 +4726,7 @@ namespace stdexec { template __t(_Sender&& __sndr, _Receiver2&& __rcvr, _Fun __fun) - : __op_base_t{{{}, (_Receiver2&&) __rcvr, query_or(get_completion_scheduler<_Set>, get_env(__sndr), __none_such())}, (_Fun&&) __fun} + : __op_base_t{{{}, (_Receiver2&&) __rcvr, query_or(get_completion_scheduler<_Set>, get_env(__sndr), __none_such())}, (_Fun&&) __fun, {}, {}} , __op_state2_(connect((_Sender&&) __sndr, __receiver_t{this})) { } @@ -5491,7 +5491,7 @@ namespace stdexec { } template _Sender, class _Env> - static auto get_completion_signatures(_Sender&& __sndr, const _Env&) noexcept + static auto get_completion_signatures(_Sender&&, const _Env&) noexcept -> __completions_t<__scheduler_t<_Sender>, __child_of<_Sender>, _Env> { return {}; } @@ -6434,7 +6434,7 @@ namespace stdexec { __children_of<_Self, __mbind_front_q<__completions_t, __env_t<_Env>>>; template _Self, class _Env> - static auto get_completion_signatures(_Self&& __self, _Env&&) { + static auto get_completion_signatures(_Self&&, _Env&&) { return __minvoke<__mtry_catch<__q<__completions>, __q<__error>>, _Self, _Env>(); } @@ -6829,7 +6829,6 @@ namespace stdexec { (_Sender&&) __sndr, [&](__ignore, _Data&& __data, _Child&& __child) { auto&& [__sched, __clsur] = (_Data&&) __data; - using _Scheduler = decltype(__sched); using _Closure = decltype(__clsur); return __write( transfer( From 5f4f500c3af391749b4285bb4e2356c6191ad30e Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Mon, 9 Oct 2023 08:57:53 -0700 Subject: [PATCH 028/146] fix nvc++ build broken by __get_placeholder_offset change --- include/stdexec/__detail/__meta.hpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/include/stdexec/__detail/__meta.hpp b/include/stdexec/__detail/__meta.hpp index 79eca818f..62b32e9dc 100644 --- a/include/stdexec/__detail/__meta.hpp +++ b/include/stdexec/__detail/__meta.hpp @@ -903,7 +903,12 @@ namespace stdexec { template struct __placeholder { - friend constexpr std::size_t __get_placeholder_offset(void*, __placeholder* = nullptr) noexcept { + __placeholder() = default; + + constexpr __placeholder(void*) noexcept { + } + + friend constexpr std::size_t __get_placeholder_offset(__placeholder) noexcept { return _Np; } }; From f4730f636db14edbf9ef1d861ba5ae41cd5deb77 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Mon, 9 Oct 2023 09:00:50 -0700 Subject: [PATCH 029/146] clang-format-16 --- include/exec/any_sender_of.hpp | 10 ++++----- include/exec/static_thread_pool.hpp | 9 ++++---- include/nvexec/stream/launch.cuh | 24 ++++++++++++--------- include/nvexec/stream/let_xxx.cuh | 10 ++++----- include/nvexec/stream/sync_wait.cuh | 6 ++++-- include/nvexec/stream/when_all.cuh | 6 +++++- include/stdexec/__detail/__meta.hpp | 9 ++++---- include/stdexec/__detail/__sender_utils.hpp | 4 ++-- include/stdexec/coroutine.hpp | 6 +++--- include/stdexec/execution.hpp | 16 ++++++-------- test/nvexec/common.cuh | 1 - test/nvexec/let_value.cpp | 4 +--- 12 files changed, 55 insertions(+), 50 deletions(-) diff --git a/include/exec/any_sender_of.hpp b/include/exec/any_sender_of.hpp index 139bf2181..cb6bdc390 100644 --- a/include/exec/any_sender_of.hpp +++ b/include/exec/any_sender_of.hpp @@ -632,7 +632,7 @@ namespace exec { requires(__is_not_stop_token_query<_Queries> && ...) struct __ref, _Queries...> { #if !STDEXEC_MSVC() - // MSVCBUG https://developercommunity.visualstudio.com/t/Private-member-inaccessible-when-used-in/10448363 + // MSVCBUG https://developercommunity.visualstudio.com/t/Private-member-inaccessible-when-used-in/10448363 private: #endif @@ -699,7 +699,7 @@ namespace exec { requires(__is_stop_token_query<_Queries> || ...) struct __ref, _Queries...> { #if !STDEXEC_MSVC() - // MSVCBUG https://developercommunity.visualstudio.com/t/Private-member-inaccessible-when-used-in/10448363 + // MSVCBUG https://developercommunity.visualstudio.com/t/Private-member-inaccessible-when-used-in/10448363 private: #endif @@ -1158,10 +1158,10 @@ namespace exec { // MSVCBUG https://developercommunity.visualstudio.com/t/ICE-and-non-ICE-bug-in-NTTP-argument-w/10361081 static constexpr auto __any_scheduler_noexcept_signature = - stdexec::get_completion_scheduler.signature; + stdexec::get_completion_scheduler.signature; template - using __schedule_sender_fn = typename __schedule_receiver::template any_sender< - __any_scheduler_noexcept_signature>; + using __schedule_sender_fn = + typename __schedule_receiver::template any_sender< __any_scheduler_noexcept_signature>; #else template using __schedule_sender_fn = typename __schedule_receiver::template any_sender< diff --git a/include/exec/static_thread_pool.hpp b/include/exec/static_thread_pool.hpp index 5c3c43363..33a79fb42 100644 --- a/include/exec/static_thread_pool.hpp +++ b/include/exec/static_thread_pool.hpp @@ -59,10 +59,9 @@ namespace exec { // MSVCBUG https://developercommunity.visualstudio.com/t/Alias-template-with-pack-expansion-in-no/10437850 template - struct __bulk_non_throwing - { - using __t = stdexec::__decayed_tuple; - static constexpr bool __v = noexcept(__t(std::declval()...)); + struct __bulk_non_throwing { + using __t = stdexec::__decayed_tuple; + static constexpr bool __v = noexcept(__t(std::declval()...)); }; #endif @@ -72,7 +71,7 @@ namespace exec { stdexec::__mbool< // If function invocation doesn't throw stdexec::__nothrow_callable && - // and emplacing a tuple doesn't throw + // and emplacing a tuple doesn't throw #if STDEXEC_MSVC() __bulk_non_throwing::__v #else diff --git a/include/nvexec/stream/launch.cuh b/include/nvexec/stream/launch.cuh index 46780ae40..e7bdbefd5 100644 --- a/include/nvexec/stream/launch.cuh +++ b/include/nvexec/stream/launch.cuh @@ -44,7 +44,7 @@ namespace nvexec { Fun fun_; launch_params params_; - public: + public: using __id = receiver_t; template @@ -86,13 +86,12 @@ namespace nvexec { __minvoke<__callable_error<"In nvexec::launch()..."__csz>, Fun, cudaStream_t, As&...>; template - using _set_value_t = - __minvoke< - __if_c< - __callable, - __mcompose<__q, __qf>, - __mbind_front_q>, - As...>; + using _set_value_t = __minvoke< + __if_c< + __callable, + __mcompose<__q, __qf>, + __mbind_front_q>, + As...>; template using completions_t = // @@ -165,8 +164,13 @@ namespace nvexec { } template <__movable_value Fun> - __binder_back operator()(launch_params params, Fun&& fun) const { - return {{}, {}, {params, (Fun&&) fun}}; + __binder_back + operator()(launch_params params, Fun&& fun) const { + return { + {}, + {}, + {params, (Fun&&) fun} + }; } }; diff --git a/include/nvexec/stream/let_xxx.cuh b/include/nvexec/stream/let_xxx.cuh index 64cd358f2..01eb7afa7 100644 --- a/include/nvexec/stream/let_xxx.cuh +++ b/include/nvexec/stream/let_xxx.cuh @@ -225,11 +225,11 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { __completion_signatures_of_t<_Sender, _Env>>; template <__decays_to<__t> _Self, receiver _Receiver> - requires receiver_of< // - _Receiver, // - __completions< // - __copy_cvref_t<_Self, _Sender>, // - stream_env>>> // + requires receiver_of< // + _Receiver, // + __completions< // + __copy_cvref_t<_Self, _Sender>, // + stream_env>>> // friend auto tag_invoke(connect_t, _Self&& __self, _Receiver __rcvr) -> __operation_t<_Self, _Receiver> { return __operation_t<_Self, _Receiver>{ diff --git a/include/nvexec/stream/sync_wait.cuh b/include/nvexec/stream/sync_wait.cuh index 4aa839037..726d37f83 100644 --- a/include/nvexec/stream/sync_wait.cuh +++ b/include/nvexec/stream/sync_wait.cuh @@ -182,8 +182,10 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { namespace _sync_wait { #if STDEXEC_NVHPC() // For reporting better diagnostics with nvc++ template > - auto operator()(context_state_t context_state, _Sender&&, [[maybe_unused]] _Error __diagnostic = {}) const - -> std::optional> = delete; + auto operator()( + context_state_t context_state, + _Sender&&, + [[maybe_unused]] _Error __diagnostic = {}) const -> std::optional> = delete; #endif }; } // namespace _sync_wait diff --git a/include/nvexec/stream/when_all.cuh b/include/nvexec/stream/when_all.cuh index 426098533..cda384fad 100644 --- a/include/nvexec/stream/when_all.cuh +++ b/include/nvexec/stream/when_all.cuh @@ -65,7 +65,11 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { __concat_completion_signatures_t< completion_signatures< set_error_t(cudaError_t), set_stopped_t()>, - __try_make_completion_signatures< Senders, Env, completion_signatures<>, __q>...>; + __try_make_completion_signatures< + Senders, + Env, + completion_signatures<>, + __q>...>; using values = // __minvoke< __mconcat<__qf>, diff --git a/include/stdexec/__detail/__meta.hpp b/include/stdexec/__detail/__meta.hpp index 62b32e9dc..c1c166a97 100644 --- a/include/stdexec/__detail/__meta.hpp +++ b/include/stdexec/__detail/__meta.hpp @@ -938,14 +938,15 @@ namespace stdexec { template struct __msvc_ignore_t { - __msvc_ignore_t() = default; - constexpr __msvc_ignore_t(auto&&...) noexcept { - } + __msvc_ignore_t() = default; + + constexpr __msvc_ignore_t(auto&&...) noexcept { + } }; template _Ty&& __nth_pack_element_(__msvc_ignore_t<_Is>..., _Ty&& __t, _Us&&...) noexcept { - return (_Ty&&)__t; + return (_Ty&&) __t; } #else template diff --git a/include/stdexec/__detail/__sender_utils.hpp b/include/stdexec/__detail/__sender_utils.hpp index 197bdd8d2..15109fd42 100644 --- a/include/stdexec/__detail/__sender_utils.hpp +++ b/include/stdexec/__detail/__sender_utils.hpp @@ -96,8 +96,8 @@ namespace stdexec { -> __msecond< __if_c>, decltype(__self.__tag().get_completion_signatures((_Self&&) __self, (_Env&&) __env))> { - return {}; - } + return {}; + } // BUGBUG fix receiver constraint here: template < diff --git a/include/stdexec/coroutine.hpp b/include/stdexec/coroutine.hpp index cd108fe31..ad79d4ffd 100644 --- a/include/stdexec/coroutine.hpp +++ b/include/stdexec/coroutine.hpp @@ -64,11 +64,11 @@ namespace stdexec { return ((_Awaitable&&) __awaitable).operator co_await(); } else if constexpr (requires { #if STDEXEC_MSVC() - __co_await_constraint((_Awaitable&&) __awaitable); + __co_await_constraint((_Awaitable&&) __awaitable); #else operator co_await((_Awaitable&&) __awaitable); #endif - }) { + }) { return operator co_await((_Awaitable&&) __awaitable); } else { return (_Awaitable&&) __awaitable; @@ -89,7 +89,7 @@ namespace stdexec { #else operator co_await(__promise->await_transform((_Awaitable&&) __awaitable)); #endif - }) { + }) { return operator co_await(__promise->await_transform((_Awaitable&&) __awaitable)); } else { return __promise->await_transform((_Awaitable&&) __awaitable); diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 356f2c5e0..cf3c7bad1 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -2380,7 +2380,7 @@ namespace stdexec { : __operation_base<_ReceiverId>{ (_CvrefReceiver&&) __rcvr, [](__operation_base<_ReceiverId>* __self) noexcept { - delete static_cast<__operation*>(__self); + delete static_cast<__operation*>(__self); }} , __op_state_(connect((_Sender&&) __sndr, __receiver_t<_ReceiverId>{this})) { } @@ -6467,7 +6467,7 @@ namespace stdexec { using _Sender = __0; template using __cust_sigs = __types< - tag_invoke_t(_Tag, __get_sender_domain_t(*)(_Sender), _Sender), + tag_invoke_t(_Tag, __get_sender_domain_t (*)(_Sender), _Sender), tag_invoke_t(_Tag, _Sender)>; template @@ -6680,7 +6680,7 @@ namespace stdexec { struct __task { struct promise_type { __task get_return_object() noexcept { - return { __coro::coroutine_handle::from_promise(*this) }; + return {__coro::coroutine_handle::from_promise(*this)}; } static std::suspend_never initial_suspend() noexcept { @@ -6726,7 +6726,7 @@ namespace stdexec { inline __task __co_impl(__context& __c) { while (true) { - co_await __continue_t{ __c.__continue_ }; + co_await __continue_t{__c.__continue_}; __c.__destroy_.destroy(); } } @@ -6745,9 +6745,8 @@ namespace stdexec { __coro::coroutine_handle<> __coro_; }; - inline __coro::coroutine_handle<> __impl( - __coro::coroutine_handle<> __destroy, - __coro::coroutine_handle<> __continue) { + inline __coro::coroutine_handle<> + __impl(__coro::coroutine_handle<> __destroy, __coro::coroutine_handle<> __continue) { static thread_local __context_and_coro __c; __c.__context_.__destroy_ = __destroy; __c.__context_.__continue_ = __continue; @@ -6759,8 +6758,7 @@ namespace stdexec { #define STDEXEC_DESTROY_AND_CONTINUE(__destroy, __continue) \ (::stdexec::__destroy_and_continue_msvc::__impl(__destroy, __continue)) #else -#define STDEXEC_DESTROY_AND_CONTINUE(__destroy, __continue) \ - (__destroy.destroy(), __continue) +#define STDEXEC_DESTROY_AND_CONTINUE(__destroy, __continue) (__destroy.destroy(), __continue) #endif // For issuing a meaningful diagnostic for the erroneous `snd1 | snd2`. diff --git a/test/nvexec/common.cuh b/test/nvexec/common.cuh index 9bfff114f..adadce9fe 100644 --- a/test/nvexec/common.cuh +++ b/test/nvexec/common.cuh @@ -326,7 +326,6 @@ struct move_only_t { , self_(this) { } - __host__ __device__ ~move_only_t() { if (this != self_) { // TODO Trap diff --git a/test/nvexec/let_value.cpp b/test/nvexec/let_value.cpp index 87a642798..ec3412dab 100644 --- a/test/nvexec/let_value.cpp +++ b/test/nvexec/let_value.cpp @@ -138,9 +138,7 @@ TEST_CASE("nvexec let_value can read a property", "[cuda][stream][adaptors][let_ auto flags = flags_storage.get(); auto snd = ex::schedule(sch) // - | ex::let_value([] { - return nvexec::get_stream(); - }) + | ex::let_value([] { return nvexec::get_stream(); }) | ex::then([flags](cudaStream_t stream) { if (is_on_gpu()) { flags.set(); From 66d4b5140989fe31f4e5b7d7c20298f967781f21 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Mon, 9 Oct 2023 13:35:11 -0700 Subject: [PATCH 030/146] the compiler config macros double as inline workaround macros --- include/stdexec/__detail/__config.hpp | 41 +++++++++++++++++++-------- 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index ac95d5be1..5c3e28e05 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -33,6 +33,7 @@ #define STDEXEC_EXPAND(...) __VA_ARGS__ #define STDEXEC_EVAL(_MACRO, ...) _MACRO(__VA_ARGS__) +#define STDEXEC_EAT(...) #define STDEXEC_NOT(_XP) STDEXEC_CAT(STDEXEC_NOT_, _XP) #define STDEXEC_NOT_0 1 @@ -50,37 +51,53 @@ #define STDEXEC_CHECK_N(_XP, _NP, ...) _NP #define STDEXEC_PROBE(_XP) _XP, 1, +// If tail is non-empty, expand to the tail. Otherwise, expand to the head +#define STDEXEC_HEAD_OR_TAIL(_XP, ...) STDEXEC_EXPAND __VA_OPT__((__VA_ARGS__)STDEXEC_EAT)(_XP) + +// If tail is non-empty, expand to nothing. Otherwise, expand to the head +#define STDEXEC_HEAD_OR_NULL(_XP, ...) STDEXEC_EXPAND __VA_OPT__(()STDEXEC_EAT)(_XP) + +// When used with no arguments, these macros expand to 1 if the current +// compiler corresponds to the macro name; 0, otherwise. When used with arguments, +// they expand to the arguments if if the current compiler corresponds to the +// macro name; nothing, otherwise. #if defined(__NVCC__) -#define STDEXEC_NVCC() 1 +#define STDEXEC_NVCC(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) #elif defined(__NVCOMPILER) -#define STDEXEC_NVHPC() 1 +#define STDEXEC_NVHPC(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) #elif defined(__EDG__) -#define LEGATE_EDG() 1 +#define STDEXEC_EDG(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) #elif defined(__clang__) -#define STDEXEC_CLANG() 1 +#define STDEXEC_CLANG(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) +#if defined(_MSC_VER) +#define STDEXEC_CLANG_CL(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) +#endif #elif defined(__GNUC__) -#define STDEXEC_GCC() 1 +#define STDEXEC_GCC(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) #elif defined(_MSC_VER) -#define STDEXEC_MSVC() 1 +#define STDEXEC_MSVC(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) #endif #ifndef STDEXEC_NVCC -#define STDEXEC_NVCC() 0 +#define STDEXEC_NVCC(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) #endif #ifndef STDEXEC_NVHPC -#define STDEXEC_NVHPC() 0 +#define STDEXEC_NVHPC(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) #endif #ifndef STDEXEC_EDG -#define STDEXEC_EDG() 0 +#define STDEXEC_EDG(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) #endif #ifndef STDEXEC_CLANG -#define STDEXEC_CLANG() 0 +#define STDEXEC_CLANG(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) +#endif +#ifndef STDEXEC_CLANG_CL +#define STDEXEC_CLANG_CL(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) #endif #ifndef STDEXEC_GCC -#define STDEXEC_GCC() 0 +#define STDEXEC_GCC(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) #endif #ifndef STDEXEC_MSVC -#define STDEXEC_MSVC() 0 +#define STDEXEC_MSVC(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) #endif #define STDEXEC_STRINGIZE(_ARG) #_ARG From b269913d1c8772f53a1115f25f1eae03587cc1ba Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Wed, 11 Oct 2023 11:28:45 -0700 Subject: [PATCH 031/146] STDEXEC_ATTRIBUTE portability macro --- .clang-format | 2 +- examples/nvexec/maxwell/common.cuh | 55 +++--- examples/nvexec/maxwell/snr.cuh | 12 +- include/exec/__detail/__basic_sequence.hpp | 8 +- include/exec/__detail/__sender_facade.hpp | 4 +- include/exec/any_sender_of.hpp | 8 +- include/exec/async_scope.hpp | 12 +- include/exec/create.hpp | 10 +- include/exec/env.hpp | 4 +- include/exec/linux/io_uring_context.hpp | 2 +- include/exec/repeat_effect_until.hpp | 6 +- include/exec/sequence/empty_sequence.hpp | 2 +- include/exec/sequence/ignore_all_values.hpp | 11 +- include/exec/sequence/iterate.hpp | 6 +- include/exec/sequence/transform_each.hpp | 17 +- include/exec/sequence_senders.hpp | 2 +- include/exec/task.hpp | 3 +- include/exec/trampoline_scheduler.hpp | 2 +- include/nvexec/detail/queue.cuh | 4 +- include/nvexec/detail/variant.cuh | 33 ++-- include/nvexec/stream/algorithm_base.cuh | 8 +- include/nvexec/stream/common.cuh | 19 +-- include/nvexec/stream/split.cuh | 3 +- include/nvexec/stream/when_all.cuh | 6 +- include/nvexec/stream_context.cuh | 14 +- include/stdexec/__detail/__basic_sender.hpp | 8 +- include/stdexec/__detail/__config.hpp | 116 +++++++++---- include/stdexec/__detail/__scope.hpp | 28 ++-- include/stdexec/execution.hpp | 176 ++++++++------------ include/stdexec/functional.hpp | 4 +- include/stdexec/stop_token.hpp | 2 +- test/nvexec/common.cuh | 3 +- 32 files changed, 303 insertions(+), 287 deletions(-) diff --git a/.clang-format b/.clang-format index 164e65fe7..b1ae2a76a 100644 --- a/.clang-format +++ b/.clang-format @@ -24,9 +24,9 @@ AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: Yes AttributeMacros: [ + 'STDEXEC_ATTRIBUTE', 'STDEXEC_NO_UNIQUE_ADDRESS', 'STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS', - 'STDEXEC_DETAIL_CUDACC_HOST_DEVICE', ] BinPackArguments: false BinPackParameters: false diff --git a/examples/nvexec/maxwell/common.cuh b/examples/nvexec/maxwell/common.cuh index f2737e5ee..8e3c8edb8 100644 --- a/examples/nvexec/maxwell/common.cuh +++ b/examples/nvexec/maxwell/common.cuh @@ -51,8 +51,8 @@ struct deleter_t { }; template -STDEXEC_DETAIL_CUDACC_HOST_DEVICE inline std::unique_ptr - allocate_on(bool gpu, std::size_t elements = 1) { +STDEXEC_ATTRIBUTE((host, device)) +inline std::unique_ptr allocate_on(bool gpu, std::size_t elements = 1) { T *ptr{}; #if defined(_NVHPC_CUDA) || defined(__CUDACC__) @@ -90,9 +90,7 @@ struct fields_accessor { float *base_ptr; - [[nodiscard]] STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - float * - get(field_id id) const { + STDEXEC_ATTRIBUTE((nodiscard, host, device)) float *get(field_id id) const { return base_ptr + static_cast(id) * cells; } }; @@ -124,9 +122,8 @@ struct grid_t { constexpr float C0 = 299792458.0f; // Speed of light [metres per second] -STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - inline bool - is_circle_part(float x, float y, float object_x, float object_y, float object_size) { +STDEXEC_ATTRIBUTE((host, device)) +inline bool is_circle_part(float x, float y, float object_x, float object_y, float object_size) { const float os2 = object_size * object_size; return ((x - object_x) * (x - object_x) + (y - object_y) * (y - object_y) <= os2); } @@ -140,9 +137,7 @@ struct grid_initializer_t { float dt; fields_accessor accessor; - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - void - operator()(std::size_t cell_id) const { + STDEXEC_ATTRIBUTE((host, device)) void operator()(std::size_t cell_id) const { const std::size_t row = cell_id / accessor.n; const std::size_t column = cell_id % accessor.n; @@ -185,36 +180,30 @@ inline grid_initializer_t grid_initializer(float dt, fields_accessor accessor) { return {dt, accessor}; } -STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - inline std::size_t - right_nid(std::size_t cell_id, std::size_t col, std::size_t N) { +STDEXEC_ATTRIBUTE((host, device)) +inline std::size_t right_nid(std::size_t cell_id, std::size_t col, std::size_t N) { return col == N - 1 ? cell_id - (N - 1) : cell_id + 1; } -STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - inline std::size_t - left_nid(std::size_t cell_id, std::size_t col, std::size_t N) { +STDEXEC_ATTRIBUTE((host, device)) +inline std::size_t left_nid(std::size_t cell_id, std::size_t col, std::size_t N) { return col == 0 ? cell_id + N - 1 : cell_id - 1; } -STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - inline std::size_t - bottom_nid(std::size_t cell_id, std::size_t row, std::size_t N) { +STDEXEC_ATTRIBUTE((host, device)) +inline std::size_t bottom_nid(std::size_t cell_id, std::size_t row, std::size_t N) { return row == 0 ? cell_id + N * (N - 1) : cell_id - N; } -STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - inline std::size_t - top_nid(std::size_t cell_id, std::size_t row, std::size_t N) { +STDEXEC_ATTRIBUTE((host, device)) +inline std::size_t top_nid(std::size_t cell_id, std::size_t row, std::size_t N) { return row == N - 1 ? cell_id - N * (N - 1) : cell_id + N; } struct h_field_calculator_t { fields_accessor accessor; - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - void - operator()(std::size_t cell_id) const __attribute__((always_inline)) { + STDEXEC_ATTRIBUTE((always_inline, host, device)) void operator()(std::size_t cell_id) const { const std::size_t N = accessor.n; const std::size_t column = cell_id % N; const std::size_t row = cell_id / N; @@ -240,23 +229,19 @@ struct e_field_calculator_t { fields_accessor accessor; std::size_t source_position; - [[nodiscard]] STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - float - gaussian_pulse(float t, float t_0, float tau) const { + STDEXEC_ATTRIBUTE((nodiscard, host, device)) + float gaussian_pulse(float t, float t_0, float tau) const { return exp(-(((t - t_0) / tau) * (t - t_0) / tau)); } - [[nodiscard]] STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - float - calculate_source(float t, float frequency) const { + STDEXEC_ATTRIBUTE((nodiscard, host, device)) + float calculate_source(float t, float frequency) const { const float tau = 0.5f / frequency; const float t_0 = 6.0f * tau; return gaussian_pulse(t, t_0, tau); } - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - void - operator()(std::size_t cell_id) const __attribute__((always_inline)) { + STDEXEC_ATTRIBUTE((always_inline, host, device)) void operator()(std::size_t cell_id) const { const std::size_t N = accessor.n; const std::size_t column = cell_id % N; const std::size_t row = cell_id / N; diff --git a/examples/nvexec/maxwell/snr.cuh b/examples/nvexec/maxwell/snr.cuh index 6ccdec3cc..b5018cd38 100644 --- a/examples/nvexec/maxwell/snr.cuh +++ b/examples/nvexec/maxwell/snr.cuh @@ -197,9 +197,8 @@ namespace repeat_n_detail { using is_receiver = void; template _Tag, class... _Args> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend void - tag_invoke(_Tag __tag, receiver_t&& __self, _Args&&... __args) noexcept { + STDEXEC_ATTRIBUTE((host, device)) + friend void tag_invoke(_Tag __tag, receiver_t&& __self, _Args&&... __args) noexcept { __tag(std::move(__self.op_state_.rcvr_), (_Args&&) __args...); } @@ -330,8 +329,7 @@ inline constexpr repeat_n_t repeat_n{}; template [[nodiscard]] bool is_gpu_scheduler(SchedulerT&& scheduler) { - auto snd = ex::just() - | exec::on(scheduler, ex::then([] { return nvexec::is_on_gpu(); })); + auto snd = ex::just() | exec::on(scheduler, ex::then([] { return nvexec::is_on_gpu(); })); auto [on_gpu] = stdexec::sync_wait(std::move(snd)).value(); return on_gpu; } @@ -363,9 +361,7 @@ void run_snr( time_storage_t time{is_gpu_scheduler(computer)}; fields_accessor accessor = grid.accessor(); - auto init = - ex::just() - | exec::on(computer, ex::bulk(grid.cells, grid_initializer(dt, accessor))); + auto init = ex::just() | exec::on(computer, ex::bulk(grid.cells, grid_initializer(dt, accessor))); stdexec::sync_wait(init); auto snd = maxwell_eqs_snr(dt, time.get(), write_vtk, n_iterations, accessor, computer); diff --git a/include/exec/__detail/__basic_sequence.hpp b/include/exec/__detail/__basic_sequence.hpp index bff1ca28a..ed152afbf 100644 --- a/include/exec/__detail/__basic_sequence.hpp +++ b/include/exec/__detail/__basic_sequence.hpp @@ -42,8 +42,8 @@ namespace exec { mutable _ImplFn __impl_; - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - explicit __seqexpr(_ImplFn __impl) + STDEXEC_ATTRIBUTE((host, device)) + explicit __seqexpr(_ImplFn __impl) : __impl_((_ImplFn&&) __impl) { } @@ -106,8 +106,8 @@ namespace exec { }; template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - __seqexpr(_ImplFn) -> __seqexpr<_ImplFn>; + STDEXEC_ATTRIBUTE((host, device)) + __seqexpr(_ImplFn) -> __seqexpr<_ImplFn>; #if STDEXEC_NVHPC() || (STDEXEC_GCC() && __GNUC__ < 13) namespace __detail { diff --git a/include/exec/__detail/__sender_facade.hpp b/include/exec/__detail/__sender_facade.hpp index 88edbf8fd..1c25daa1b 100644 --- a/include/exec/__detail/__sender_facade.hpp +++ b/include/exec/__detail/__sender_facade.hpp @@ -185,8 +185,8 @@ namespace exec { } _Receiver __rcvr_; - STDEXEC_NO_UNIQUE_ADDRESS _Kernel __kernel_; - STDEXEC_NO_UNIQUE_ADDRESS _Data __data_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Kernel __kernel_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Data __data_; }; struct __t { diff --git a/include/exec/any_sender_of.hpp b/include/exec/any_sender_of.hpp index 221ec42e7..b788a6487 100644 --- a/include/exec/any_sender_of.hpp +++ b/include/exec/any_sender_of.hpp @@ -350,7 +350,7 @@ namespace exec { const __vtable_t* __vtable_{__default_storage_vtable((__vtable_t*) nullptr)}; void* __object_pointer_{nullptr}; alignas(__alignment) std::byte __buffer_[__buffer_size]{}; - STDEXEC_NO_UNIQUE_ADDRESS _Allocator __allocator_{}; + STDEXEC_ATTRIBUTE((no_unique_address)) _Allocator __allocator_{}; }; }; @@ -541,7 +541,7 @@ namespace exec { const __vtable_t* __vtable_{__default_storage_vtable((__vtable_t*) nullptr)}; void* __object_pointer_{nullptr}; alignas(__alignment) std::byte __buffer_[__buffer_size]{}; - STDEXEC_NO_UNIQUE_ADDRESS _Allocator __allocator_{}; + STDEXEC_ATTRIBUTE((no_unique_address)) _Allocator __allocator_{}; }; struct __empty_vtable { @@ -771,7 +771,7 @@ namespace exec { template struct __operation_base { - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __rcvr_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __rcvr_; stdexec::in_place_stop_source __stop_source_{}; using __stop_callback = typename stdexec::stop_token_of_t< stdexec::env_of_t<_Receiver>>::template callback_type<__on_stop_t>; @@ -873,7 +873,7 @@ namespace exec { } private: - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __rec_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __rec_; __immovable_operation_storage __storage_{}; friend void tag_invoke(start_t, __t& __self) noexcept { diff --git a/include/exec/async_scope.hpp b/include/exec/async_scope.hpp index 3ca4474fe..4fb039ec1 100644 --- a/include/exec/async_scope.hpp +++ b/include/exec/async_scope.hpp @@ -55,7 +55,7 @@ namespace exec { template struct __when_empty_op_base : __task { using _Receiver = __t<_ReceiverId>; - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __rcvr_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __rcvr_; }; template @@ -120,7 +120,7 @@ namespace exec { } const __impl* __scope_; - STDEXEC_NO_UNIQUE_ADDRESS _Constrained __c_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Constrained __c_; }; template @@ -132,7 +132,7 @@ namespace exec { struct __nest_op_base : __immovable { using _Receiver = __t<_ReceiverId>; const __impl* __scope_; - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __rcvr_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __rcvr_; }; template @@ -208,7 +208,7 @@ namespace exec { using is_sender = void; const __impl* __scope_; - STDEXEC_NO_UNIQUE_ADDRESS _Constrained __c_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Constrained __c_; template using __nest_operation_t = __nest_op<_ConstrainedId, __x<_Receiver>>; @@ -333,9 +333,9 @@ namespace exec { } } - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __rcvr_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __rcvr_; std::unique_ptr<__future_state<_Sender, _Env>> __state_; - STDEXEC_NO_UNIQUE_ADDRESS __forward_consumer __forward_consumer_; + STDEXEC_ATTRIBUTE((no_unique_address)) __forward_consumer __forward_consumer_; public: ~__future_op() noexcept { diff --git a/include/exec/create.hpp b/include/exec/create.hpp index 8a4d5d2c4..70d8e4bfd 100644 --- a/include/exec/create.hpp +++ b/include/exec/create.hpp @@ -32,8 +32,8 @@ namespace exec { template struct __context { - STDEXEC_NO_UNIQUE_ADDRESS _Receiver receiver; - STDEXEC_NO_UNIQUE_ADDRESS _Args args; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver receiver; + STDEXEC_ATTRIBUTE((no_unique_address)) _Args args; }; template @@ -42,9 +42,9 @@ namespace exec { using _Result = __call_result_t<_Fun, _Context&>; using _State = __if_c, __void, std::optional<_Result>>; - STDEXEC_NO_UNIQUE_ADDRESS _Context __ctx_; - STDEXEC_NO_UNIQUE_ADDRESS _Fun __fun_; - STDEXEC_NO_UNIQUE_ADDRESS _State __state_{}; + STDEXEC_ATTRIBUTE((no_unique_address)) _Context __ctx_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fun __fun_; + STDEXEC_ATTRIBUTE((no_unique_address)) _State __state_{}; friend void tag_invoke(start_t, __operation& __self) noexcept { __self.__state_.emplace(__conv{[&]() noexcept { diff --git a/include/exec/env.hpp b/include/exec/env.hpp index 802126a90..6b95339b3 100644 --- a/include/exec/env.hpp +++ b/include/exec/env.hpp @@ -57,7 +57,7 @@ namespace exec { using _Default = __t<_DefaultId>; using _Receiver = __t<_ReceiverId>; - STDEXEC_NO_UNIQUE_ADDRESS _Default __default_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Default __default_; _Receiver __rcvr_; friend void tag_invoke(start_t, __operation& __self) noexcept { @@ -78,7 +78,7 @@ namespace exec { struct __sender { using _Default = __t<_DefaultId>; using is_sender = void; - STDEXEC_NO_UNIQUE_ADDRESS _Default __default_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Default __default_; template using __value_t = diff --git a/include/exec/linux/io_uring_context.hpp b/include/exec/linux/io_uring_context.hpp index 5b7928fd5..cc50cd673 100644 --- a/include/exec/linux/io_uring_context.hpp +++ b/include/exec/linux/io_uring_context.hpp @@ -698,7 +698,7 @@ namespace exec { struct __impl { __context& __context_; - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __receiver_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __receiver_; __impl(__context& __context, _Receiver&& __receiver) : __context_{__context} diff --git a/include/exec/repeat_effect_until.hpp b/include/exec/repeat_effect_until.hpp index 57eff5e67..1f8005542 100644 --- a/include/exec/repeat_effect_until.hpp +++ b/include/exec/repeat_effect_until.hpp @@ -47,8 +47,8 @@ namespace exec { __call_result_t; using __source_op_t = stdexec::connect_result_t<__source_on_scheduler_sender, __receiver_t>; - STDEXEC_NO_UNIQUE_ADDRESS _Source __source_; - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __rcvr_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Source __source_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __rcvr_; __manual_lifetime<__source_op_t> __source_op_; trampoline_scheduler __sched_; @@ -150,7 +150,7 @@ namespace exec { struct __t { using is_sender = void; using __id = __sender; - STDEXEC_NO_UNIQUE_ADDRESS _Source __source_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Source __source_; template using __value_t = stdexec::completion_signatures<>; diff --git a/include/exec/sequence/empty_sequence.hpp b/include/exec/sequence/empty_sequence.hpp index 0be237b62..40988ce92 100644 --- a/include/exec/sequence/empty_sequence.hpp +++ b/include/exec/sequence/empty_sequence.hpp @@ -29,7 +29,7 @@ namespace exec { struct __t { using __id = __operation; - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __rcvr_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __rcvr_; friend void tag_invoke(start_t, __t& __self) noexcept { stdexec::set_value(static_cast<_Receiver&&>(__self.__rcvr_)); diff --git a/include/exec/sequence/ignore_all_values.hpp b/include/exec/sequence/ignore_all_values.hpp index c5729fddc..aa52476b7 100644 --- a/include/exec/sequence/ignore_all_values.hpp +++ b/include/exec/sequence/ignore_all_values.hpp @@ -70,7 +70,7 @@ namespace exec { template struct __item_operation_base { - STDEXEC_NO_UNIQUE_ADDRESS _ItemReceiver __receiver_; + STDEXEC_ATTRIBUTE((no_unique_address)) _ItemReceiver __receiver_; __result_type<_ResultVariant>* __result_; }; @@ -123,10 +123,9 @@ namespace exec { __t( __result_type<_ResultVariant>* __parent, _Sender&& __sndr, - _ItemReceiver __rcvr) // - noexcept( - __nothrow_decay_copyable<_ItemReceiver> // - && __nothrow_connectable<_Sender, __item_receiver_t>) + _ItemReceiver __rcvr) // + noexcept(__nothrow_decay_copyable<_ItemReceiver> // + && __nothrow_connectable<_Sender, __item_receiver_t>) : __base_type{static_cast<_ItemReceiver&&>(__rcvr), __parent} , __op_{stdexec::connect(static_cast<_Sender&&>(__sndr), __item_receiver_t{this})} { } @@ -168,7 +167,7 @@ namespace exec { template struct __operation_base : __result_type<_ResultVariant> { - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __receiver_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __receiver_; }; template diff --git a/include/exec/sequence/iterate.hpp b/include/exec/sequence/iterate.hpp index 96ef4550f..4b7a3d3a4 100644 --- a/include/exec/sequence/iterate.hpp +++ b/include/exec/sequence/iterate.hpp @@ -34,8 +34,8 @@ namespace exec { template struct __operation_base { - STDEXEC_NO_UNIQUE_ADDRESS _Iterator __iterator_; - STDEXEC_NO_UNIQUE_ADDRESS _Sentinel __sentinel_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Iterator __iterator_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Sentinel __sentinel_; }; template @@ -46,7 +46,7 @@ namespace exec { struct __item_operation { struct __t { using __id = __item_operation; - STDEXEC_NO_UNIQUE_ADDRESS _ItemRcvr __rcvr_; + STDEXEC_ATTRIBUTE((no_unique_address)) _ItemRcvr __rcvr_; __operation_base<_Iterator, _Sentinel>* __parent_; friend void tag_invoke(start_t, __t& __self) noexcept { diff --git a/include/exec/sequence/transform_each.hpp b/include/exec/sequence/transform_each.hpp index 4bdb1f86a..a133660d3 100644 --- a/include/exec/sequence/transform_each.hpp +++ b/include/exec/sequence/transform_each.hpp @@ -44,7 +44,7 @@ namespace exec { && __callable> friend auto tag_invoke(_SetNext, _Self& __self, _Item&& __item) noexcept( __nothrow_callable<_SetNext, _Receiver&, __call_result_t<_Adaptor&, _Item>> // - && __nothrow_callable<_Adaptor&, _Item>) + && __nothrow_callable<_Adaptor&, _Item>) -> next_sender_of_t<_Receiver, __call_result_t<_Adaptor&, _Item>> { return exec::set_next( __self.__op_->__receiver_, __self.__op_->__adaptor_(static_cast<_Item&&>(__item))); @@ -104,8 +104,8 @@ namespace exec { template auto operator()(__ignore, _Adaptor __adaptor, _Sequence&& __sequence) noexcept( - __nothrow_decay_copyable<_Adaptor> && __nothrow_decay_copyable<_Sequence> - && __nothrow_decay_copyable<_Receiver>) + __nothrow_decay_copyable<_Adaptor>&& __nothrow_decay_copyable<_Sequence>&& + __nothrow_decay_copyable<_Receiver>) -> __t< __operation<_Sequence, __id<_Receiver>, _Adaptor>> { return { static_cast<_Sequence&&>(__sequence), @@ -121,8 +121,9 @@ namespace exec { struct _WITH_ITEM_SENDER_ { }; template - auto __try_call(_Item*) - -> stdexec::__mexception<_NOT_CALLABLE_ADAPTOR_<_Adaptor&>, _WITH_ITEM_SENDER_>>; + auto __try_call(_Item*) -> stdexec::__mexception< + _NOT_CALLABLE_ADAPTOR_<_Adaptor&>, + _WITH_ITEM_SENDER_>>; template requires stdexec::__callable<_Adaptor&, _Item> @@ -139,9 +140,9 @@ namespace exec { struct transform_each_t { template - auto operator()(_Sequence&& __sndr, _Adaptor&& __adaptor) const noexcept( - __nothrow_decay_copyable<_Sequence> // - && __nothrow_decay_copyable<_Adaptor>) { + auto operator()(_Sequence&& __sndr, _Adaptor&& __adaptor) const + noexcept(__nothrow_decay_copyable<_Sequence> // + && __nothrow_decay_copyable<_Adaptor>) { return make_sequence_expr( static_cast<_Adaptor&&>(__adaptor), static_cast<_Sequence&&>(__sndr)); } diff --git a/include/exec/sequence_senders.hpp b/include/exec/sequence_senders.hpp index 868f7ac47..8e2edb89a 100644 --- a/include/exec/sequence_senders.hpp +++ b/include/exec/sequence_senders.hpp @@ -81,7 +81,7 @@ namespace exec { using __id = __stopped_means_break; using _Receiver = stdexec::__t<_ReceiverId>; using _Token = stop_token_of_t>; - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __rcvr_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __rcvr_; template _GetEnv, same_as<__t> _Self> friend env_of_t<_Receiver> tag_invoke(_GetEnv, const _Self& __self) noexcept { diff --git a/include/exec/task.hpp b/include/exec/task.hpp index e28ed296e..b00f7d6b2 100644 --- a/include/exec/task.hpp +++ b/include/exec/task.hpp @@ -96,7 +96,8 @@ namespace exec { static constexpr bool __with_scheduler = _SchedulerAffinity == __scheduler_affinity::__sticky; - STDEXEC_NO_UNIQUE_ADDRESS __if_c<__with_scheduler, __any_scheduler, __ignore> // + STDEXEC_ATTRIBUTE((no_unique_address)) + __if_c<__with_scheduler, __any_scheduler, __ignore> // __scheduler_{exec::inline_scheduler{}}; in_place_stop_token __stop_token_; diff --git a/include/exec/trampoline_scheduler.hpp b/include/exec/trampoline_scheduler.hpp index 3381420c6..a098f3cb8 100644 --- a/include/exec/trampoline_scheduler.hpp +++ b/include/exec/trampoline_scheduler.hpp @@ -96,7 +96,7 @@ namespace exec { struct __t : __operation_base { using __id = __operation; - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __receiver_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __receiver_; explicit __t(_Receiver __rcvr, std::size_t __max_depth) noexcept( __nothrow_decay_copyable<_Receiver>) diff --git a/include/nvexec/detail/queue.cuh b/include/nvexec/detail/queue.cuh index d42523ffa..635285af6 100644 --- a/include/nvexec/detail/queue.cuh +++ b/include/nvexec/detail/queue.cuh @@ -41,9 +41,7 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { namespace queue { struct producer_t { task_base_t** tail_; - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - void - operator()(task_base_t* task) { + STDEXEC_ATTRIBUTE((host, device)) void operator()(task_base_t* task) { atom_task_ref tail_ref(*tail_); task_base_t* old_tail = tail_ref.load(::cuda::memory_order_acquire); diff --git a/include/nvexec/detail/variant.cuh b/include/nvexec/detail/variant.cuh index b072873df..38ba236d7 100644 --- a/include/nvexec/detail/variant.cuh +++ b/include/nvexec/detail/variant.cuh @@ -106,7 +106,8 @@ namespace nvexec { }; template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE void visit_impl( + STDEXEC_ATTRIBUTE((host, device)) + void visit_impl( std::integral_constant, VisitorT&& visitor, V&& v, @@ -117,7 +118,8 @@ namespace nvexec { } template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE void visit_impl( + STDEXEC_ATTRIBUTE((host, device)) + void visit_impl( std::integral_constant, VisitorT&& visitor, V&& v, @@ -133,7 +135,8 @@ namespace nvexec { } template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE void visit(VisitorT&& visitor, V&& v) { + STDEXEC_ATTRIBUTE((host, device)) + void visit(VisitorT&& visitor, V&& v) { detail::visit_impl( std::integral_constant::size - 1>{}, (VisitorT&&) visitor, @@ -142,7 +145,8 @@ namespace nvexec { } template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE void visit(VisitorT&& visitor, V&& v, std::size_t index) { + STDEXEC_ATTRIBUTE((host, device)) + void visit(VisitorT&& visitor, V&& v, std::size_t index) { detail::visit_impl( std::integral_constant::size - 1>{}, (VisitorT&&) visitor, @@ -165,43 +169,48 @@ namespace nvexec { using index_of = std::integral_constant< index_t, detail::find_index()>; template T> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE T& get() noexcept { + STDEXEC_ATTRIBUTE((host, device)) + T& get() noexcept { void* data = storage_.data_; return *static_cast(data); } template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE detail::nth_type& get() noexcept { + STDEXEC_ATTRIBUTE((host, device)) + detail::nth_type& get() noexcept { return get>(); } - STDEXEC_DETAIL_CUDACC_HOST_DEVICE variant_t() + STDEXEC_ATTRIBUTE((host, device)) + variant_t() requires std::default_initializable { emplace(); } - STDEXEC_DETAIL_CUDACC_HOST_DEVICE ~variant_t() { + STDEXEC_ATTRIBUTE((host, device)) ~variant_t() { destroy(); } - STDEXEC_DETAIL_CUDACC_HOST_DEVICE bool holds_alternative() const { + STDEXEC_ATTRIBUTE((host, device)) bool holds_alternative() const { return index_ != detail::npos(); } template T, class... As> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE void emplace(As&&... as) { + STDEXEC_ATTRIBUTE((host, device)) + void emplace(As&&... as) { destroy(); construct((As&&) as...); } template T, class... As> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE void construct(As&&... as) { + STDEXEC_ATTRIBUTE((host, device)) + void construct(As&&... as) { ::new (storage_.data_) T((As&&) as...); index_ = index_of(); } - STDEXEC_DETAIL_CUDACC_HOST_DEVICE void destroy() { + STDEXEC_ATTRIBUTE((host, device)) void destroy() { if (holds_alternative()) { visit( [](auto& val) noexcept { diff --git a/include/nvexec/stream/algorithm_base.cuh b/include/nvexec/stream/algorithm_base.cuh index 5ea172e2e..d08ca312e 100644 --- a/include/nvexec/stream/algorithm_base.cuh +++ b/include/nvexec/stream/algorithm_base.cuh @@ -61,8 +61,8 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS::__algo_range_init_fun { }; operation_state_base_t& op_state_; - STDEXEC_NO_UNIQUE_ADDRESS InitT init_; - STDEXEC_NO_UNIQUE_ADDRESS Fun fun_; + STDEXEC_ATTRIBUTE((no_unique_address)) InitT init_; + STDEXEC_ATTRIBUTE((no_unique_address)) Fun fun_; public: using __id = receiver_t; @@ -104,8 +104,8 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS::__algo_range_init_fun { using _set_value_t = typename DerivedSender::template _set_value_t; Sender sndr_; - STDEXEC_NO_UNIQUE_ADDRESS InitT init_; - STDEXEC_NO_UNIQUE_ADDRESS Fun fun_; + STDEXEC_ATTRIBUTE((no_unique_address)) InitT init_; + STDEXEC_ATTRIBUTE((no_unique_address)) Fun fun_; template using completion_signatures = // diff --git a/include/nvexec/stream/common.cuh b/include/nvexec/stream/common.cuh index 5e5ab5c8f..be383faee 100644 --- a/include/nvexec/stream/common.cuh +++ b/include/nvexec/stream/common.cuh @@ -59,7 +59,7 @@ namespace nvexec { } #endif - inline STDEXEC_DETAIL_CUDACC_HOST_DEVICE bool is_on_gpu() noexcept { + inline STDEXEC_ATTRIBUTE((host, device)) bool is_on_gpu() noexcept { return get_device_type() == device_type::device; } } @@ -293,9 +293,8 @@ namespace nvexec { struct set_noop { template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - void - operator()(Ts&&...) const noexcept { + STDEXEC_ATTRIBUTE((host, device)) + void operator()(Ts&&...) const noexcept { // TODO TRAP std::printf("ERROR: use of empty variant."); } @@ -320,7 +319,7 @@ namespace nvexec { return get_stream_provider(env)->own_stream_.value(); } - STDEXEC_DETAIL_CUDACC_HOST_DEVICE auto operator()() const noexcept { + STDEXEC_ATTRIBUTE((host, device)) auto operator()() const noexcept { return stdexec::read(*this); } }; @@ -389,17 +388,15 @@ namespace nvexec { using __id = stream_enqueue_receiver; template <__one_of Tag, class... As> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend void - tag_invoke(Tag, __t&& self, As&&... as) noexcept { + STDEXEC_ATTRIBUTE((host, device)) + friend void tag_invoke(Tag, __t&& self, As&&... as) noexcept { self.variant_->template emplace>(Tag(), std::move(as)...); self.producer_(self.task_); } template _Tag, class Error> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend void - tag_invoke(_Tag, __t&& self, Error&& e) noexcept { + STDEXEC_ATTRIBUTE((host, device)) + friend void tag_invoke(_Tag, __t&& self, Error&& e) noexcept { if constexpr (__decays_to) { // What is `exception_ptr` but death pending self.variant_->template emplace>( diff --git a/include/nvexec/stream/split.cuh b/include/nvexec/stream/split.cuh index 77566de4c..58ee2ea27 100644 --- a/include/nvexec/stream/split.cuh +++ b/include/nvexec/stream/split.cuh @@ -345,7 +345,6 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { namespace stdexec::__detail { template - extern __mconst< - nvexec::STDEXEC_STREAM_DETAIL_NS::split_sender_t<__name_of<__t>>> + extern __mconst< nvexec::STDEXEC_STREAM_DETAIL_NS::split_sender_t<__name_of<__t>>> __name_of_v>; } diff --git a/include/nvexec/stream/when_all.cuh b/include/nvexec/stream/when_all.cuh index 58ab89763..a671e83de 100644 --- a/include/nvexec/stream/when_all.cuh +++ b/include/nvexec/stream/when_all.cuh @@ -419,6 +419,8 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { namespace stdexec::__detail { template inline constexpr __mconst< - nvexec::STDEXEC_STREAM_DETAIL_NS::when_all_sender_t>...>> - __name_of_v>{}; + nvexec::STDEXEC_STREAM_DETAIL_NS:: + when_all_sender_t>...>> + __name_of_v>{}; } diff --git a/include/nvexec/stream_context.cuh b/include/nvexec/stream_context.cuh index ac2effb48..56c6d871f 100644 --- a/include/nvexec/stream_context.cuh +++ b/include/nvexec/stream_context.cuh @@ -139,8 +139,8 @@ namespace nvexec { return self.env_; }; - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - inline __t(context_state_t context_state) noexcept + STDEXEC_ATTRIBUTE((host, device)) + inline __t(context_state_t context_state) noexcept : env_{context_state} { } @@ -236,15 +236,13 @@ namespace nvexec { return split_sender_th(sch.context_state_, (S&&) sndr); } - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend inline sender_t - tag_invoke(schedule_t, const stream_scheduler& self) noexcept { + STDEXEC_ATTRIBUTE((host, device)) + friend inline sender_t tag_invoke(schedule_t, const stream_scheduler& self) noexcept { return {self.context_state_}; } - friend std::true_type tag_invoke( // - __has_algorithm_customizations_t, // - const stream_scheduler& self) noexcept { + friend std::true_type + tag_invoke(__has_algorithm_customizations_t, const stream_scheduler& self) noexcept { return {}; } diff --git a/include/stdexec/__detail/__basic_sender.hpp b/include/stdexec/__detail/__basic_sender.hpp index 3cfe5409e..1363d0741 100644 --- a/include/stdexec/__detail/__basic_sender.hpp +++ b/include/stdexec/__detail/__basic_sender.hpp @@ -74,8 +74,8 @@ namespace stdexec { mutable _ImplFn __impl_; - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - explicit __sexpr(_ImplFn __impl) + STDEXEC_ATTRIBUTE((host, device)) + explicit __sexpr(_ImplFn __impl) : __impl_((_ImplFn&&) __impl) { } @@ -119,8 +119,8 @@ namespace stdexec { }; template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - __sexpr(_ImplFn) -> __sexpr<_ImplFn>; + STDEXEC_ATTRIBUTE((host, device)) + __sexpr(_ImplFn) -> __sexpr<_ImplFn>; ////////////////////////////////////////////////////////////////////////////////////////////////// // make_sender_expr diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index 5c3e28e05..8d5c09b0b 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -28,6 +28,8 @@ #include #include +#define STDEXEC_STRINGIZE(_ARG) #_ARG + #define STDEXEC_CAT_(_XP, ...) _XP##__VA_ARGS__ #define STDEXEC_CAT(_XP, ...) STDEXEC_CAT_(_XP, __VA_ARGS__) @@ -47,15 +49,42 @@ STDEXEC_EXPAND(STDEXEC_COUNT_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)) #define STDEXEC_COUNT_(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _NP, ...) _NP -#define STDEXEC_CHECK(...) STDEXEC_EXPAND(STDEXEC_CHECK_N(__VA_ARGS__, 0, )) -#define STDEXEC_CHECK_N(_XP, _NP, ...) _NP -#define STDEXEC_PROBE(_XP) _XP, 1, +#define STDEXEC_CHECK(...) STDEXEC_EXPAND(STDEXEC_CHECK_(__VA_ARGS__, 0, )) +#define STDEXEC_CHECK_(_XP, _NP, ...) _NP +#define STDEXEC_PROBE(...) STDEXEC_PROBE_(__VA_ARGS__, 1) +#define STDEXEC_PROBE_(_XP, _NP, ...) _XP, _NP, + +//////////////////////////////////////////////////////////////////////////////// +// STDEXEC_FOR_EACH +// Inspired by "Recursive macros with C++20 __VA_OPT__", by David Mazières +// https://www.scs.stanford.edu/~dm/blog/va-opt.html +#define STDEXEC_EXPAND_R(...) \ + STDEXEC_EXPAND_R1(STDEXEC_EXPAND_R1(STDEXEC_EXPAND_R1(STDEXEC_EXPAND_R1(__VA_ARGS__)))) \ + /**/ +#define STDEXEC_EXPAND_R1(...) \ + STDEXEC_EXPAND_R2(STDEXEC_EXPAND_R2(STDEXEC_EXPAND_R2(STDEXEC_EXPAND_R2(__VA_ARGS__)))) \ + /**/ +#define STDEXEC_EXPAND_R2(...) \ + STDEXEC_EXPAND_R3(STDEXEC_EXPAND_R3(STDEXEC_EXPAND_R3(STDEXEC_EXPAND_R3(__VA_ARGS__)))) \ + /**/ +#define STDEXEC_EXPAND_R3(...) \ + STDEXEC_EXPAND(STDEXEC_EXPAND(STDEXEC_EXPAND(STDEXEC_EXPAND(__VA_ARGS__)))) \ + /**/ + +#define STDEXEC_PARENS () +#define STDEXEC_FOR_EACH(_MACRO, ...) \ + __VA_OPT__(STDEXEC_EXPAND_R(STDEXEC_FOR_EACH_HELPER(_MACRO, __VA_ARGS__))) \ + /**/ +#define STDEXEC_FOR_EACH_HELPER(_MACRO, _A1, ...) \ + _MACRO(_A1) __VA_OPT__(STDEXEC_FOR_EACH_AGAIN STDEXEC_PARENS(_MACRO, __VA_ARGS__)) /**/ +#define STDEXEC_FOR_EACH_AGAIN() STDEXEC_FOR_EACH_HELPER +//////////////////////////////////////////////////////////////////////////////////////////////////// // If tail is non-empty, expand to the tail. Otherwise, expand to the head -#define STDEXEC_HEAD_OR_TAIL(_XP, ...) STDEXEC_EXPAND __VA_OPT__((__VA_ARGS__)STDEXEC_EAT)(_XP) +#define STDEXEC_HEAD_OR_TAIL(_XP, ...) STDEXEC_EXPAND __VA_OPT__((__VA_ARGS__) STDEXEC_EAT)(_XP) // If tail is non-empty, expand to nothing. Otherwise, expand to the head -#define STDEXEC_HEAD_OR_NULL(_XP, ...) STDEXEC_EXPAND __VA_OPT__(()STDEXEC_EAT)(_XP) +#define STDEXEC_HEAD_OR_NULL(_XP, ...) STDEXEC_EXPAND __VA_OPT__(() STDEXEC_EAT)(_XP) // When used with no arguments, these macros expand to 1 if the current // compiler corresponds to the macro name; 0, otherwise. When used with arguments, @@ -100,7 +129,50 @@ #define STDEXEC_MSVC(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) #endif -#define STDEXEC_STRINGIZE(_ARG) #_ARG +//////////////////////////////////////////////////////////////////////////////////////////////////// +#ifdef __CUDACC__ +#define STDEXEC_CUDA(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) +#else +#define STDEXEC_CUDA(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) +#endif + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// For portably declaring attributes on functions and types +// Usage: +// +// STDEXEC_ATTRIBUTE((attr1, attr2, ...)) +// void foo() { ... } +#define STDEXEC_ATTRIBUTE(_XP) STDEXEC_FOR_EACH(STDEXEC_ATTR, STDEXEC_EXPAND _XP) + +// unknown attributes are treated like C++-style attributes +#define STDEXEC_ATTR_WHICH_0(_ATTR) [[_ATTR]] + +// custom handling for specific attribute types +#define STDEXEC_ATTR_WHICH_1(_ATTR) STDEXEC_CUDA(__host__) +#define STDEXEC_ATTR_host STDEXEC_PROBE(~, 1) +#define STDEXEC_ATTR___host__ STDEXEC_PROBE(~, 1) + +#define STDEXEC_ATTR_WHICH_2(_ATTR) STDEXEC_CUDA(__device__) +#define STDEXEC_ATTR_device STDEXEC_PROBE(~, 2) +#define STDEXEC_ATTR___device__ STDEXEC_PROBE(~, 2) + +// NVBUG #4067067: NVHPC does not fully support [[no_unique_address]] +#if STDEXEC_NVHPC() +#define STDEXEC_ATTR_WHICH_3(_ATTR) /*nothing*/ +#define STDEXEC_ATTR_no_unique_address STDEXEC_PROBE(~, 3) +#endif + +#if STDEXEC_MSVC() +#define STDEXEC_ATTR_WHICH_4(_ATTR) __forceinline +#elif defined(__GNUC__) +#define STDEXEC_ATTR_WHICH_4(_ATTR) __attribute__((always_inline)) +#else +#define STDEXEC_ATTR_WHICH_4(_ATTR) /*nothing*/ +#endif +#define STDEXEC_ATTR_always_inline STDEXEC_PROBE(~, 4) + +#define STDEXEC_ATTR(_ATTR) \ + STDEXEC_CAT(STDEXEC_ATTR_WHICH_, STDEXEC_CHECK(STDEXEC_CAT(STDEXEC_ATTR_, _ATTR)))(_ATTR) #if STDEXEC_NVCC() #define STDEXEC_PRAGMA_PUSH() _Pragma("nv_diagnostic push") @@ -114,17 +186,18 @@ #elif STDEXEC_CLANG() || STDEXEC_GCC() #define STDEXEC_PRAGMA_PUSH() _Pragma("GCC diagnostic push") #define STDEXEC_PRAGMA_POP() _Pragma("GCC diagnostic pop") -#define STDEXEC_PRAGMA_IGNORE_GNU(_ARG) _Pragma(STDEXEC_STRINGIZE(GCC diagnostic ignored _ARG)) +#define STDEXEC_PRAGMA_IGNORE_GNU(...) \ + _Pragma(STDEXEC_STRINGIZE(GCC diagnostic ignored __VA_ARGS__)) #else #define STDEXEC_PRAGMA_PUSH() #define STDEXEC_PRAGMA_POP() #endif #ifndef STDEXEC_PRAGMA_IGNORE_GNU -#define STDEXEC_PRAGMA_IGNORE_GNU(_ARG) +#define STDEXEC_PRAGMA_IGNORE_GNU(...) #endif #ifndef STDEXEC_PRAGMA_IGNORE_EDG -#define STDEXEC_PRAGMA_IGNORE_EDG(_ARG) +#define STDEXEC_PRAGMA_IGNORE_EDG(...) #endif #if !STDEXEC_MSVC() && defined(__has_builtin) @@ -170,32 +243,19 @@ #define STDEXEC_IMMOVABLE(_XP) _XP(_XP&&) = delete #endif -// NVBUG #4067067 -#if STDEXEC_NVHPC() -#define STDEXEC_NO_UNIQUE_ADDRESS -#else -#define STDEXEC_NO_UNIQUE_ADDRESS [[no_unique_address]] -#endif - // BUG (gcc PR93711): copy elision fails when initializing a // [[no_unique_address]] field from a function returning an object // of class type by value #if STDEXEC_GCC() #define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS #else -#define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS [[no_unique_address]] -#endif - -#if STDEXEC_CLANG() && defined(__CUDACC__) -#define STDEXEC_DETAIL_CUDACC_HOST_DEVICE __host__ __device__ -#else -#define STDEXEC_DETAIL_CUDACC_HOST_DEVICE +#define STDEXEC_IMMOVABLE_NO_UNIQUE_ADDRESS STDEXEC_ATTRIBUTE((no_unique_address)) #endif #if STDEXEC_NVHPC() #include #define STDEXEC_TERMINATE() NV_IF_TARGET(NV_IS_HOST, (std::terminate();), (__trap();)) void() -#elif STDEXEC_CLANG() && defined(__CUDACC__) && defined(__CUDA_ARCH__) +#elif STDEXEC_CLANG() && STDEXEC_CUDA() && defined(__CUDA_ARCH__) #define STDEXEC_TERMINATE() \ __trap(); \ __builtin_unreachable() @@ -239,12 +299,12 @@ #endif #if defined(__cpp_explicit_this_parameter) && (__cpp_explicit_this_parameter >= 202110) -#define STDEXEC_HAS_EXPLICIT_THIS() 1 +#define STDEXEC_EXPLICIT_THIS(...) STDEXEC_HEAD_OR_TAIL(1, __VA_ARGS__) #else -#define STDEXEC_HAS_EXPLICIT_THIS() 0 +#define STDEXEC_EXPLICIT_THIS(...) STDEXEC_HEAD_OR_NULL(0, __VA_ARGS__) #endif -#if STDEXEC_HAS_EXPLICIT_THIS() +#if STDEXEC_EXPLICIT_THIS() #define STDEXEC_DEFINE_EXPLICIT_THIS_MEMFN(...) __VA_ARGS__ #define STDEXEC_CALL_EXPLICIT_THIS_MEMFN(_OBJ, _NAME) (_OBJ)._NAME( STDEXEC_CALL_EXPLICIT_THIS_MEMFN_DETAIL #define STDEXEC_CALL_EXPLICIT_THIS_MEMFN_DETAIL(...) __VA_ARGS__ ) @@ -266,7 +326,7 @@ #define STDEXEC_PROBE_TYPE_CHECKING_1 STDEXEC_TYPE_CHECKING_ONE #define STDEXEC_PROBE_TYPE_CHECKING_STDEXEC_ENABLE_EXTRA_TYPE_CHECKING STDEXEC_TYPE_CHECKING_TWO -#define STDEXEC_TYPE_CHECKING_WHICH3(...) STDEXEC_PROBE_TYPE_CHECKING_ ## __VA_ARGS__ +#define STDEXEC_TYPE_CHECKING_WHICH3(...) STDEXEC_PROBE_TYPE_CHECKING_##__VA_ARGS__ #define STDEXEC_TYPE_CHECKING_WHICH2(...) STDEXEC_TYPE_CHECKING_WHICH3(__VA_ARGS__) #define STDEXEC_TYPE_CHECKING_WHICH STDEXEC_TYPE_CHECKING_WHICH2(STDEXEC_ENABLE_EXTRA_TYPE_CHECKING) diff --git a/include/stdexec/__detail/__scope.hpp b/include/stdexec/__detail/__scope.hpp index 22b8b3dc9..9baf0fe50 100644 --- a/include/stdexec/__detail/__scope.hpp +++ b/include/stdexec/__detail/__scope.hpp @@ -24,8 +24,8 @@ namespace stdexec { template struct __scope_guard<_Fn> { - STDEXEC_NO_UNIQUE_ADDRESS _Fn __fn_; - STDEXEC_NO_UNIQUE_ADDRESS __immovable __hidden_{}; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fn __fn_; + STDEXEC_ATTRIBUTE((no_unique_address)) __immovable __hidden_{}; bool __dismissed_{false}; ~__scope_guard() { @@ -40,9 +40,9 @@ namespace stdexec { template struct __scope_guard<_Fn, _T0> { - STDEXEC_NO_UNIQUE_ADDRESS _Fn __fn_; - STDEXEC_NO_UNIQUE_ADDRESS _T0 __t0_; - STDEXEC_NO_UNIQUE_ADDRESS __immovable __hidden_{}; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fn __fn_; + STDEXEC_ATTRIBUTE((no_unique_address)) _T0 __t0_; + STDEXEC_ATTRIBUTE((no_unique_address)) __immovable __hidden_{}; bool __dismissed_{false}; @@ -58,10 +58,10 @@ namespace stdexec { template struct __scope_guard<_Fn, _T0, _T1> { - STDEXEC_NO_UNIQUE_ADDRESS _Fn __fn_; - STDEXEC_NO_UNIQUE_ADDRESS _T0 __t0_; - STDEXEC_NO_UNIQUE_ADDRESS _T1 __t1_; - STDEXEC_NO_UNIQUE_ADDRESS __immovable __hidden_{}; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fn __fn_; + STDEXEC_ATTRIBUTE((no_unique_address)) _T0 __t0_; + STDEXEC_ATTRIBUTE((no_unique_address)) _T1 __t1_; + STDEXEC_ATTRIBUTE((no_unique_address)) __immovable __hidden_{}; bool __dismissed_{false}; @@ -77,11 +77,11 @@ namespace stdexec { template struct __scope_guard<_Fn, _T0, _T1, _T2> { - STDEXEC_NO_UNIQUE_ADDRESS _Fn __fn_; - STDEXEC_NO_UNIQUE_ADDRESS _T0 __t0_; - STDEXEC_NO_UNIQUE_ADDRESS _T1 __t1_; - STDEXEC_NO_UNIQUE_ADDRESS _T2 __t2_; - STDEXEC_NO_UNIQUE_ADDRESS __immovable __hidden_{}; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fn __fn_; + STDEXEC_ATTRIBUTE((no_unique_address)) _T0 __t0_; + STDEXEC_ATTRIBUTE((no_unique_address)) _T1 __t1_; + STDEXEC_ATTRIBUTE((no_unique_address)) _T2 __t2_; + STDEXEC_ATTRIBUTE((no_unique_address)) __immovable __hidden_{}; bool __dismissed_{false}; diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 65d71f464..10328d182 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -185,7 +185,7 @@ namespace stdexec { return __base_; } - STDEXEC_NO_UNIQUE_ADDRESS _Base __base_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Base __base_; }; } @@ -472,7 +472,7 @@ namespace stdexec { struct __env_fn { using __t = __env_fn; using __id = __env_fn; - STDEXEC_NO_UNIQUE_ADDRESS _Fun __fun_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fun __fun_; template requires __callable @@ -490,7 +490,7 @@ namespace stdexec { static_assert(__nothrow_move_constructible<_Env>); using __t = __env_fwd; using __id = __env_fwd; - STDEXEC_NO_UNIQUE_ADDRESS _Env __env_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Env __env_; template <__forwarding_query _Tag> requires tag_invocable<_Tag, const _Env&> @@ -509,7 +509,7 @@ namespace stdexec { static_assert(__nothrow_move_constructible<_Env>); using __t = __joined_env; using __id = __joined_env; - STDEXEC_NO_UNIQUE_ADDRESS _Env __env_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Env __env_; const _Base& base() const noexcept { return this->__env_fwd<_Base>::__env_; @@ -528,7 +528,7 @@ namespace stdexec { struct __joined_env<__prop<_Tag>, _Base> : __env_fwd<_Base> { using __t = __joined_env; using __id = __joined_env; - STDEXEC_NO_UNIQUE_ADDRESS __prop<_Tag> __env_; + STDEXEC_ATTRIBUTE((no_unique_address)) __prop<_Tag> __env_; friend void tag_invoke(_Tag, const __joined_env&) noexcept = delete; }; @@ -722,8 +722,7 @@ namespace stdexec { template requires tag_invocable - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - void + STDEXEC_ATTRIBUTE((host, device)) void operator()(_Receiver&& __rcvr, _As&&... __as) const noexcept { static_assert(nothrow_tag_invocable); (void) tag_invoke(set_value_t{}, (_Receiver&&) __rcvr, (_As&&) __as...); @@ -737,8 +736,7 @@ namespace stdexec { template requires tag_invocable - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - void + STDEXEC_ATTRIBUTE((host, device)) void operator()(_Receiver&& __rcvr, _Error&& __err) const noexcept { static_assert(nothrow_tag_invocable); (void) tag_invoke(set_error_t{}, (_Receiver&&) __rcvr, (_Error&&) __err); @@ -752,9 +750,7 @@ namespace stdexec { template requires tag_invocable - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - void - operator()(_Receiver&& __rcvr) const noexcept { + STDEXEC_ATTRIBUTE((host, device)) void operator()(_Receiver&& __rcvr) const noexcept { static_assert(nothrow_tag_invocable); (void) tag_invoke(set_stopped_t{}, (_Receiver&&) __rcvr); } @@ -1055,9 +1051,7 @@ namespace stdexec { struct __valid_completions { template _Self, class _Tag, class... _Args> requires __one_of<_Tag (*)(_Args&&...), _Sigs...> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend void - tag_invoke(_Tag, _Self&&, _Args&&...) noexcept { + STDEXEC_ATTRIBUTE((host, device)) friend void tag_invoke(_Tag, _Self&&, _Args&&...) noexcept { STDEXEC_TERMINATE(); } }; @@ -1077,9 +1071,8 @@ namespace stdexec { using is_receiver = void; template _Tag> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend __debug_env_t<_Env> - tag_invoke(_Tag, __debug_receiver) noexcept { + STDEXEC_ATTRIBUTE((host, device)) + friend __debug_env_t<_Env> tag_invoke(_Tag, __debug_receiver) noexcept { STDEXEC_TERMINATE(); } }; @@ -1099,9 +1092,7 @@ namespace stdexec { [[deprecated( "The sender claims to send a particular set of completions," " but in actual fact it completes with a result that is not" - " one of the declared completion signatures.")]] STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - void - _ATTENTION_() noexcept { + " one of the declared completion signatures.")]] STDEXEC_ATTRIBUTE((host, device)) void _ATTENTION_() noexcept { } template @@ -1125,9 +1116,8 @@ namespace stdexec { }; template <__completion_tag _Tag, class... _Args> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - void - tag_invoke(_Tag, __t<__invalid_completion<_Tag(_Args...)>>, _Args&&...) noexcept { + STDEXEC_ATTRIBUTE((host, device)) + void tag_invoke(_Tag, __t<__invalid_completion<_Tag(_Args...)>>, _Args&&...) noexcept { } struct __debug_operation { @@ -1708,9 +1698,7 @@ namespace stdexec { struct schedule_t { template requires tag_invocable - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - auto - operator()(_Scheduler&& __sched) const + STDEXEC_ATTRIBUTE((host, device)) auto operator()(_Scheduler&& __sched) const noexcept(nothrow_tag_invocable) { static_assert(sender>); return tag_invoke(schedule_t{}, (_Scheduler&&) __sched); @@ -2532,9 +2520,7 @@ namespace stdexec { using __t = __scheduler; using __id = __scheduler; - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend auto - tag_invoke(schedule_t, __scheduler) { + STDEXEC_ATTRIBUTE((host, device)) friend auto tag_invoke(schedule_t, __scheduler) { return make_sender_expr<__schedule_t>(); } @@ -2562,7 +2548,7 @@ namespace stdexec { struct __t { using is_receiver = void; using __id = __detached_receiver; - STDEXEC_NO_UNIQUE_ADDRESS _Env __env_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Env __env_; template _Tag, class... _As> friend void tag_invoke(_Tag, __t&&, _As&&...) noexcept { @@ -2706,26 +2692,22 @@ namespace stdexec { struct just_t : __just_impl { template <__movable_value... _Ts> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - auto - operator()(_Ts&&... __ts) const noexcept((__nothrow_decay_copyable<_Ts> && ...)) { + STDEXEC_ATTRIBUTE((host, device)) + auto operator()(_Ts&&... __ts) const noexcept((__nothrow_decay_copyable<_Ts> && ...)) { return make_sender_expr(__decayed_tuple<_Ts...>{(_Ts&&) __ts...}); } }; struct just_error_t : __just_impl { template <__movable_value _Error> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - auto - operator()(_Error&& __err) const noexcept(__nothrow_decay_copyable<_Error>) { + STDEXEC_ATTRIBUTE((host, device)) + auto operator()(_Error&& __err) const noexcept(__nothrow_decay_copyable<_Error>) { return make_sender_expr(__decayed_tuple<_Error>{(_Error&&) __err}); } }; struct just_stopped_t : __just_impl { - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - auto - operator()() const noexcept { + STDEXEC_ATTRIBUTE((host, device)) auto operator()() const noexcept { return make_sender_expr(__decayed_tuple<>()); } }; @@ -2735,9 +2717,9 @@ namespace stdexec { using __just::just_error_t; using __just::just_stopped_t; - inline constexpr just_t just {}; - inline constexpr just_error_t just_error {}; - inline constexpr just_stopped_t just_stopped {}; + inline constexpr just_t just{}; + inline constexpr just_error_t just_error{}; + inline constexpr just_stopped_t just_stopped{}; ///////////////////////////////////////////////////////////////////////////// // [execution.execute] @@ -2813,8 +2795,8 @@ namespace stdexec { namespace __closure { template struct __compose : sender_adaptor_closure<__compose<_T0, _T1>> { - STDEXEC_NO_UNIQUE_ADDRESS _T0 __t0_; - STDEXEC_NO_UNIQUE_ADDRESS _T1 __t1_; + STDEXEC_ATTRIBUTE((no_unique_address)) _T0 __t0_; + STDEXEC_ATTRIBUTE((no_unique_address)) _T1 __t1_; template requires __callable<_T0, _Sender> && __callable<_T1, __call_result_t<_T0, _Sender>> @@ -2845,13 +2827,12 @@ namespace stdexec { template struct __binder_back : sender_adaptor_closure<__binder_back<_Fun, _As...>> { - STDEXEC_NO_UNIQUE_ADDRESS _Fun __fun_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fun __fun_; std::tuple<_As...> __as_; template requires __callable<_Fun, _Sender, _As...> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - __call_result_t<_Fun, _Sender, _As...> + STDEXEC_ATTRIBUTE((host, device)) __call_result_t<_Fun, _Sender, _As...> operator()(_Sender&& __sndr) && noexcept(__nothrow_callable<_Fun, _Sender, _As...>) { return std::apply( [&__sndr, this](_As&... __as) -> __call_result_t<_Fun, _Sender, _As...> { @@ -2862,8 +2843,7 @@ namespace stdexec { template requires __callable - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - __call_result_t + STDEXEC_ATTRIBUTE((host, device)) __call_result_t operator()(_Sender&& __sndr) const & // noexcept(__nothrow_callable) { return std::apply( @@ -2882,9 +2862,8 @@ namespace stdexec { // A derived-to-base cast that works even when the base is not // accessible from derived. template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - __copy_cvref_t<_Up&&, _Tp> - __c_cast(_Up&& u) noexcept + STDEXEC_ATTRIBUTE((host, device)) + __copy_cvref_t<_Up&&, _Tp> __c_cast(_Up&& u) noexcept requires __decays_to<_Tp, _Tp> { static_assert(std::is_reference_v<__copy_cvref_t<_Up&&, _Tp>>); @@ -2916,24 +2895,18 @@ namespace stdexec { } private: - STDEXEC_NO_UNIQUE_ADDRESS _Base __base_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Base __base_; protected: - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - _Base& - base() & noexcept { + STDEXEC_ATTRIBUTE((host, device)) _Base& base() & noexcept { return __base_; } - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - const _Base& - base() const & noexcept { + STDEXEC_ATTRIBUTE((host, device)) const _Base& base() const & noexcept { return __base_; } - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - _Base&& - base() && noexcept { + STDEXEC_ATTRIBUTE((host, device)) _Base&& base() && noexcept { return (_Base&&) __base_; } }; @@ -2952,8 +2925,8 @@ namespace stdexec { // but 'int(type::existing_member_function)' is an error (as desired). #define _DISPATCH_MEMBER(_TAG) \ template \ - STDEXEC_DETAIL_CUDACC_HOST_DEVICE static auto __call_##_TAG( \ - _Self&& __self, _Ts&&... __ts) noexcept \ + STDEXEC_ATTRIBUTE((host, device)) \ + static auto __call_##_TAG(_Self&& __self, _Ts&&... __ts) noexcept \ -> decltype(((_Self&&) __self)._TAG((_Ts&&) __ts...)) { \ static_assert(noexcept(((_Self&&) __self)._TAG((_Ts&&) __ts...))); \ return ((_Self&&) __self)._TAG((_Ts&&) __ts...); \ @@ -2998,9 +2971,8 @@ namespace stdexec { using __base_t = __minvoke<__get_base_t, _Dp&&>; template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - static __base_t<_Dp> - __get_base(_Dp&& __self) noexcept { + STDEXEC_ATTRIBUTE((host, device)) + static __base_t<_Dp> __get_base(_Dp&& __self) noexcept { if constexpr (__has_base) { return __c_cast<__t>((_Dp&&) __self).base(); } else { @@ -3009,10 +2981,9 @@ namespace stdexec { } template _SetValue, class... _As> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend auto - tag_invoke(_SetValue, _Derived&& __self, _As&&... __as) noexcept // - -> __msecond< // + STDEXEC_ATTRIBUTE((host, device)) + friend auto tag_invoke(_SetValue, _Derived&& __self, _As&&... __as) noexcept // + -> __msecond< // __if_c>, decltype(_CALL_MEMBER(set_value, (_Derived&&) __self, (_As&&) __as...))> { static_assert(noexcept(_CALL_MEMBER(set_value, (_Derived&&) __self, (_As&&) __as...))); @@ -3022,16 +2993,16 @@ namespace stdexec { template _SetValue, class _Dp = _Derived, class... _As> requires _MISSING_MEMBER(_Dp, set_value) && tag_invocable<_SetValue, __base_t<_Dp>, _As...> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend void tag_invoke(_SetValue, _Derived&& __self, _As&&... __as) noexcept { + STDEXEC_ATTRIBUTE( + (host, + device)) friend void tag_invoke(_SetValue, _Derived&& __self, _As&&... __as) noexcept { stdexec::set_value(__get_base((_Dp&&) __self), (_As&&) __as...); } template _SetError, class _Error> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend auto - tag_invoke(_SetError, _Derived&& __self, _Error&& __err) noexcept // - -> __msecond< // + STDEXEC_ATTRIBUTE((host, device)) + friend auto tag_invoke(_SetError, _Derived&& __self, _Error&& __err) noexcept // + -> __msecond< // __if_c>, decltype(_CALL_MEMBER(set_error, (_Derived&&) __self, (_Error&&) __err))> { static_assert(noexcept(_CALL_MEMBER(set_error, (_Derived&&) __self, (_Error&&) __err))); @@ -3041,16 +3012,16 @@ namespace stdexec { template _SetError, class _Error, class _Dp = _Derived> requires _MISSING_MEMBER(_Dp, set_error) && tag_invocable<_SetError, __base_t<_Dp>, _Error> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend void tag_invoke(_SetError, _Derived&& __self, _Error&& __err) noexcept { + STDEXEC_ATTRIBUTE( + (host, + device)) friend void tag_invoke(_SetError, _Derived&& __self, _Error&& __err) noexcept { stdexec::set_error(__get_base((_Derived&&) __self), (_Error&&) __err); } template _SetStopped, class _Dp = _Derived> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend auto - tag_invoke(_SetStopped, _Derived&& __self) noexcept // - -> __msecond< // + STDEXEC_ATTRIBUTE((host, device)) + friend auto tag_invoke(_SetStopped, _Derived&& __self) noexcept // + -> __msecond< // __if_c>, decltype(_CALL_MEMBER(set_stopped, (_Dp&&) __self))> { static_assert(noexcept(_CALL_MEMBER(set_stopped, (_Derived&&) __self))); @@ -3059,16 +3030,15 @@ namespace stdexec { template _SetStopped, class _Dp = _Derived> requires _MISSING_MEMBER(_Dp, set_stopped) && tag_invocable<_SetStopped, __base_t<_Dp>> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend void tag_invoke(_SetStopped, _Derived&& __self) noexcept { + STDEXEC_ATTRIBUTE( + (host, device)) friend void tag_invoke(_SetStopped, _Derived&& __self) noexcept { stdexec::set_stopped(__get_base((_Derived&&) __self)); } // Pass through the get_env receiver query template _GetEnv, class _Dp = _Derived> - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend auto - tag_invoke(_GetEnv, const _Derived& __self) noexcept + STDEXEC_ATTRIBUTE((host, device)) + friend auto tag_invoke(_GetEnv, const _Derived& __self) noexcept -> decltype(_CALL_MEMBER(get_env, (const _Dp&) __self)) { static_assert(noexcept(_CALL_MEMBER(get_env, __self))); return _CALL_MEMBER(get_env, __self); @@ -3076,8 +3046,8 @@ namespace stdexec { template _GetEnv, class _Dp = _Derived> requires _MISSING_MEMBER(_Dp, get_env) - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - friend auto tag_invoke(_GetEnv, const _Derived& __self) noexcept + STDEXEC_ATTRIBUTE( + (host, device)) friend auto tag_invoke(_GetEnv, const _Derived& __self) noexcept -> env_of_t<__base_t> { return stdexec::get_env(__get_base(__self)); } @@ -3194,7 +3164,7 @@ namespace stdexec { struct __data { _Receiver __rcvr_; - STDEXEC_NO_UNIQUE_ADDRESS _Fun __fun_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fun __fun_; }; struct __t { @@ -3333,7 +3303,7 @@ namespace stdexec { struct __data { _Receiver __rcvr_; - STDEXEC_NO_UNIQUE_ADDRESS _Fun __fun_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fun __fun_; }; struct __t { @@ -3474,7 +3444,7 @@ namespace stdexec { struct __data { _Receiver __rcvr_; - STDEXEC_NO_UNIQUE_ADDRESS _Fun __fun_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fun __fun_; }; struct __t { @@ -3618,7 +3588,7 @@ namespace stdexec { template struct __data { _Shape __shape_; - STDEXEC_NO_UNIQUE_ADDRESS _Fun __fun_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fun __fun_; static constexpr auto __mbrs_ = __mliterals<&__data::__shape_, &__data::__fun_>(); }; template @@ -3629,7 +3599,7 @@ namespace stdexec { using _Receiver = stdexec::__t<_ReceiverId>; struct __op_state : __data<_Shape, _Fun> { - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __rcvr_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __rcvr_; }; struct __t { @@ -3755,9 +3725,8 @@ namespace stdexec { struct bulk_t : __default_get_env { template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE // - auto - operator()(_Sender&& __sndr, _Shape __shape, _Fun __fun) const { + STDEXEC_ATTRIBUTE((host, device)) + auto operator()(_Sender&& __sndr, _Shape __shape, _Fun __fun) const { auto __domain = __get_sender_domain((_Sender&&) __sndr); return transform_sender( __domain, make_sender_expr(__data{__shape, (_Fun&&) __fun}, (_Sender&&) __sndr)); @@ -4538,8 +4507,9 @@ namespace stdexec { using _Receiver = stdexec::__t<_ReceiverId>; using _Scheduler = stdexec::__t<_SchedulerId>; - _Receiver __rcvr_; // the input (outer) receiver - STDEXEC_NO_UNIQUE_ADDRESS _Scheduler __sched_; // the input sender's completion scheduler + _Receiver __rcvr_; // the input (outer) receiver + STDEXEC_ATTRIBUTE((no_unique_address)) + _Scheduler __sched_; // the input sender's completion scheduler }; inline constexpr auto __get_scheduler_prop = [](auto* __op) noexcept { @@ -5025,7 +4995,7 @@ namespace stdexec { using __id = __operation; run_loop* __loop_; - STDEXEC_NO_UNIQUE_ADDRESS _Receiver __rcvr_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Receiver __rcvr_; static void __execute_impl(__task* __p) noexcept { auto& __rcvr = ((__t*) __p)->__rcvr_; @@ -6172,7 +6142,7 @@ namespace stdexec { // Could be non-atomic here and atomic_ref everywhere except __completion_fn std::atomic<__state_t> __state_{__started}; _ErrorsVariant __errors_{}; - STDEXEC_NO_UNIQUE_ADDRESS _ValuesTuple __values_{}; + STDEXEC_ATTRIBUTE((no_unique_address)) _ValuesTuple __values_{}; std::optional< typename stop_token_of_t&>::template callback_type<__on_stop_requested>> __on_stop_{}; diff --git a/include/stdexec/functional.hpp b/include/stdexec/functional.hpp index 7d0361e08..587d7fe5e 100644 --- a/include/stdexec/functional.hpp +++ b/include/stdexec/functional.hpp @@ -79,8 +79,8 @@ namespace stdexec { template struct __composed { - STDEXEC_NO_UNIQUE_ADDRESS _Fun0 __t0_; - STDEXEC_NO_UNIQUE_ADDRESS _Fun1 __t1_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fun0 __t0_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fun1 __t1_; template requires __callable<_Fun1, _Ts...> && __callable<_Fun0, __call_result_t<_Fun1, _Ts...>> diff --git a/include/stdexec/stop_token.hpp b/include/stdexec/stop_token.hpp index 4b04d2ea5..46b26b1d9 100644 --- a/include/stdexec/stop_token.hpp +++ b/include/stdexec/stop_token.hpp @@ -234,7 +234,7 @@ namespace stdexec { std::move(static_cast(cb)->__fun_)(); } - STDEXEC_NO_UNIQUE_ADDRESS _Fun __fun_; + STDEXEC_ATTRIBUTE((no_unique_address)) _Fun __fun_; }; namespace __stok { diff --git a/test/nvexec/common.cuh b/test/nvexec/common.cuh index adadce9fe..935c68799 100644 --- a/test/nvexec/common.cuh +++ b/test/nvexec/common.cuh @@ -128,7 +128,8 @@ namespace detail::a_sender { Fun f_; template - STDEXEC_DETAIL_CUDACC_HOST_DEVICE void set_value(As&&... as) && noexcept + STDEXEC_ATTRIBUTE((host, device)) + void set_value(As&&... as) && noexcept requires stdexec::__callable { using result_t = std::invoke_result_t; From 9473ad5121f66ad155f1c46eece4f8f52598034b Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Wed, 11 Oct 2023 16:16:04 -0700 Subject: [PATCH 032/146] there is no need to ever specialize `make_sender_expr` --- include/stdexec/__detail/__basic_sender.hpp | 21 ++++++----- include/stdexec/execution.hpp | 41 +++++++++++++-------- 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/include/stdexec/__detail/__basic_sender.hpp b/include/stdexec/__detail/__basic_sender.hpp index 1363d0741..d41e9120e 100644 --- a/include/stdexec/__detail/__basic_sender.hpp +++ b/include/stdexec/__detail/__basic_sender.hpp @@ -125,7 +125,7 @@ namespace stdexec { ////////////////////////////////////////////////////////////////////////////////////////////////// // make_sender_expr namespace __detail { - template > + template struct make_sender_expr_t { template constexpr auto operator()(_Data __data = {}, _Children... __children) const; @@ -179,12 +179,11 @@ namespace stdexec { }; } // anonymous namespace - template + template template constexpr auto - make_sender_expr_t<_Tag, _Domain>::operator()(_Data __data, _Children... __children) const { - return __sexpr{ - __detail::__make_tuple(_Tag(), __detail::__mbc(__data), __detail::__mbc(__children)...)}; + make_sender_expr_t<_Tag>::operator()(_Data __data, _Children... __children) const { + return __sexpr{__make_tuple(_Tag(), __detail::__mbc(__data), __detail::__mbc(__children)...)}; } #else // Anonymous namespace here is to avoid symbol name collisions with the @@ -204,17 +203,19 @@ namespace stdexec { }; } // anonymous namespace - template + template template constexpr auto - make_sender_expr_t<_Tag, _Domain>::operator()(_Data __data, _Children... __children) const { - return __sexpr{__detail::__make_tuple(_Tag(), (_Data&&) __data, (_Children&&) __children...)}; + make_sender_expr_t<_Tag>::operator()(_Data __data, _Children... __children) const { + return __sexpr{__make_tuple(_Tag(), (_Data&&) __data, (_Children&&) __children...)}; }; #endif + + template + inline constexpr make_sender_expr_t<_Tag> make_sender_expr{}; } // namespace __detail - template > - inline constexpr __detail::make_sender_expr_t<_Tag, _Domain> make_sender_expr{}; + using __detail::make_sender_expr; template using __sexpr_t = __result_of, _Data, _Children...>; diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 7b921605f..0d62aed1b 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -5851,19 +5851,32 @@ namespace stdexec { }; }; - struct on_t { + struct on_t : __default_get_env { + using _Sender = __1; + using _Scheduler = __0; + using __legacy_customizations_t = __types< + tag_invoke_t(on_t, _Scheduler, _Sender)>; + template - requires tag_invocable - auto operator()(_Scheduler&& __sched, _Sender&& __sndr) const - noexcept(nothrow_tag_invocable) - -> tag_invoke_result_t { - return tag_invoke(*this, (_Scheduler&&) __sched, (_Sender&&) __sndr); + auto operator()(_Scheduler&& __sched, _Sender&& __sndr) const { + auto __domain = __get_sender_domain((_Sender&&) __sndr); + return transform_sender( + __domain, make_sender_expr((_Scheduler&&) __sched, (_Sender&&) __sndr)); } + }; - template - auto operator()(_Scheduler&& __sched, _Sender&& __sndr) const - -> __t<__sender>, stdexec::__id<__decay_t<_Sender>>>> { - return {(_Scheduler&&) __sched, (_Sender&&) __sndr}; + struct __lower_on { + template + using __sender = + __t<__sender>, stdexec::__id<__decay_t<_Sender>>>>; + + template + auto operator()(_Sender&& __sndr, const _Env&...) const { + return apply_sender( + (_Sender&&) __sndr, + [](__ignore, _Data&& __data, _Child&& __child) { + return __sender<_Data, _Child>{(_Data&&) __data, (_Child&&) __child}; + }); } }; } // namespace __on @@ -5871,12 +5884,8 @@ namespace stdexec { using __on::on_t; inline constexpr on_t on{}; - // BUGBUG this wouldn't be necessary if `on` returned a __sexpr - template - inline constexpr auto make_sender_expr = - [](_Scheduler&& __sched, _Sender&& __sndr) { - return on((_Scheduler&&) __sched, (_Sender&&) __sndr); - }; + template <> + inline constexpr __on::__lower_on __default_sender_transform{}; // BUGBUG this will also be unnecessary when `on` returns a __sexpr namespace __detail { From 2499ff4bf7e1098c76b4c2ec5b14bc4a44b06ad4 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Thu, 12 Oct 2023 13:48:10 -0700 Subject: [PATCH 033/146] implement stdexec::on in terms of transfer that way on() will benefit from any customization of transfer() --- include/stdexec/execution.hpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 0d62aed1b..e15b3c3c9 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -5770,6 +5770,7 @@ namespace stdexec { using __id = __operation; using __receiver_t = stdexec::__t<__receiver<_SchedulerId, _SenderId, _ReceiverId>>; using __receiver_ref_t = __on::__receiver_ref_t<_SchedulerId, _SenderId, _ReceiverId>; + using __schedule_sender_t = __result_of; friend void tag_invoke(start_t, __t& __self) noexcept { start(std::get<0>(__self.__data_)); @@ -5779,12 +5780,12 @@ namespace stdexec { __t(_Scheduler __sched, _Sender2&& __sndr, _Receiver2&& __rcvr) : _Base{{}, (_Scheduler&&) __sched, (_Sender2&&) __sndr, (_Receiver2&&) __rcvr} , __data_{std::in_place_index<0>, __conv{[this] { - return connect(schedule(this->__scheduler_), __receiver_t{{}, this}); + return connect(transfer_just(this->__scheduler_), __receiver_t{{}, this}); }}} { } std::variant< - connect_result_t, __receiver_t>, + connect_result_t<__schedule_sender_t, __receiver_t>, connect_result_t<_Sender, __receiver_ref_t>> __data_; }; @@ -5801,6 +5802,7 @@ namespace stdexec { using __id = __sender; using is_sender = void; + using __schedule_sender_t = __result_of; template using __receiver_ref_t = __on::__receiver_ref_t<_SchedulerId, _SenderId, _ReceiverId>; template @@ -5813,7 +5815,7 @@ namespace stdexec { template <__decays_to<__t> _Self, receiver _Receiver> requires constructible_from<_Sender, __copy_cvref_t<_Self, _Sender>> - && sender_to, __receiver_t>> + && sender_to<__schedule_sender_t, __receiver_t>> && sender_to<_Sender, __receiver_ref_t>> friend auto tag_invoke(connect_t, _Self&& __self, _Receiver __rcvr) -> __operation_t> { @@ -5828,7 +5830,7 @@ namespace stdexec { template <__decays_to<__t> _Self, class _Env> friend auto tag_invoke(get_completion_signatures_t, _Self&&, _Env&&) -> __try_make_completion_signatures< - schedule_result_t<_Scheduler>, + __schedule_sender_t, _Env, __try_make_completion_signatures< __copy_cvref_t<_Self, _Sender>, From 028b6acacbc6d8825f33666d796f1e0df5183412 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Thu, 12 Oct 2023 13:52:18 -0700 Subject: [PATCH 034/146] default_domain::transform_sender calls sender-tag::transform_sender the default implementation of `transform_sender`, `default_domain::transform_sender`, will dispatch to a member function named `transform_sender` on the input sender's tag type. --- include/stdexec/execution.hpp | 211 ++++++++++++++-------------------- 1 file changed, 87 insertions(+), 124 deletions(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 0d62aed1b..962137677 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -99,9 +99,6 @@ namespace stdexec { static inline constexpr Tag (*signature)(Sig) = nullptr; }; - template - inline constexpr __none_such __default_sender_transform{}; - namespace __domain { template using __legacy_c11n_for = typename _Tag::__legacy_customizations_t; @@ -119,17 +116,17 @@ namespace stdexec { } }; - template - concept __has_default_transform = - sender_expr<_Sender> && // - __callable<__mtypeof<__default_sender_transform<__tag_of<_Sender>>>, _Sender>; - - template + template concept __has_transform_member = - requires(_Domain __domain, _Sender&& __sender, _Env&&... __env) { - __domain.transform_sender((_Sender&&) __sender, (_Env&&) __env...); + requires(_Type __obj, _Sender&& __sender, const _Env&... __env) { + __obj.transform_sender((_Sender&&) __sender, __env...); }; + template + concept __has_default_transform = + sender_expr<_Sender> && // + __has_transform_member<__tag_of<_Sender>, _Sender, _Env...>; + template struct __default_domain { __default_domain() = default; @@ -149,7 +146,7 @@ namespace stdexec { if constexpr (__callable) { return apply_sender((_Sender&&) __sndr, __legacy_customization()); } else if constexpr (__has_default_transform<_Sender>) { - return __default_sender_transform<__tag_of<_Sender>>((_Sender&&) __sndr); + return __tag_of<_Sender>().transform_sender((_Sender&&) __sndr); } else { return _Sender((_Sender&&) __sndr); } @@ -157,14 +154,12 @@ namespace stdexec { // Called with an environment during lazy customization template - auto transform_sender(_Sender&& __sndr, const _Env&) const -> _Sender { - return (_Sender&&) __sndr; - } - - template - requires __callable<__mtypeof<__default_sender_transform<__tag_of<_Sender>>>, _Sender, _Env> - auto transform_sender(_Sender&& __sndr, const _Env& __env) const { - return __default_sender_transform<__tag_of<_Sender>>((_Sender&&) __sndr, __env); + decltype(auto) transform_sender(_Sender&& __sndr, const _Env& __env) const { + if constexpr (__has_default_transform<_Sender, _Env>) { + return __tag_of<_Sender>().transform_sender((_Sender&&) __sndr, __env); + } else { + return _Sender((_Sender&&) __sndr); + } } // BUGBUG remove me when we no longer support finding customizations via the @@ -3278,7 +3273,7 @@ namespace stdexec { template auto operator()(_Sender&& __sndr, _Fun __fun) const { auto __domain = __get_sender_domain((_Sender&&) __sndr); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr((_Fun&&) __fun, (_Sender&&) __sndr)); } @@ -3419,7 +3414,7 @@ namespace stdexec { template auto operator()(_Sender&& __sndr, _Fun __fun) const { auto __domain = __get_sender_domain((_Sender&&) __sndr, set_error); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr((_Fun&&) __fun, (_Sender&&) __sndr)); } @@ -3562,7 +3557,7 @@ namespace stdexec { requires __callable<_Fun> auto operator()(_Sender&& __sndr, _Fun __fun) const { auto __domain = __get_sender_domain((_Sender&&) __sndr, set_stopped); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr((_Fun&&) __fun, (_Sender&&) __sndr)); } @@ -3755,7 +3750,7 @@ namespace stdexec { STDEXEC_ATTRIBUTE((host, device)) auto operator()(_Sender&& __sndr, _Shape __shape, _Fun __fun) const { auto __domain = __get_sender_domain((_Sender&&) __sndr); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr(__data{__shape, (_Fun&&) __fun}, (_Sender&&) __sndr)); } @@ -4081,14 +4076,14 @@ namespace stdexec { requires sender_in<_Sender, empty_env> && __decay_copyable> auto operator()(_Sender&& __sndr) const { auto __domain = __get_sender_domain(__sndr); - return transform_sender(__domain, make_sender_expr(__(), (_Sender&&) __sndr)); + return stdexec::transform_sender(__domain, make_sender_expr(__(), (_Sender&&) __sndr)); } template requires sender_in<_Sender, _Env> && __decay_copyable> auto operator()(_Sender&& __sndr, _Env&& __env) const { auto __domain = __get_env_domain(__env); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr(__(), (_Sender&&) __sndr), (_Env&&) __env); } @@ -4101,11 +4096,9 @@ namespace stdexec { __types< tag_invoke_t(split_t, __get_sender_domain_t(const _Sender&), _Sender), tag_invoke_t(split_t, _Sender)>; - }; - struct __lower_split { template - auto operator()(_Sender&& __sndr, _Env... __env) const { + static auto transform_sender(_Sender&& __sndr, _Env... __env) { return apply_sender( (_Sender&&) __sndr, [&](__ignore, __ignore, _Child&& __child) { using __sh_state_t = __t<__sh_state<__cvref_id<_Child>, __id<_Env>...>>; @@ -4120,9 +4113,6 @@ namespace stdexec { using __split::split_t; inline constexpr split_t split{}; - template <> - inline constexpr __split::__lower_split __default_sender_transform{}; - ///////////////////////////////////////////////////////////////////////////// // [execution.senders.adaptors.ensure_started] namespace __ensure_started { @@ -4421,7 +4411,7 @@ namespace stdexec { return (_Sender&&) __sndr; } else { auto __domain = __get_sender_domain(__sndr); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr(__(), (_Sender&&) __sndr)); } STDEXEC_UNREACHABLE(); @@ -4434,7 +4424,7 @@ namespace stdexec { return (_Sender&&) __sndr; } else { auto __domain = __get_env_domain(__env); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr(__(), (_Sender&&) __sndr), (_Env&&) __env); } STDEXEC_UNREACHABLE(); @@ -4449,11 +4439,9 @@ namespace stdexec { __types< tag_invoke_t(ensure_started_t, __get_sender_domain_t(const _Sender&), _Sender), tag_invoke_t(ensure_started_t, _Sender)>; - }; - struct __lower_ensure_started { template - auto operator()(_Sender&& __sndr, _Env... __env) const { + static auto transform_sender(_Sender&& __sndr, _Env... __env) { return apply_sender( (_Sender&&) __sndr, [&](__ignore, __ignore, _Child&& __child) { using __sh_state_t = __t<__sh_state<__cvref_id<_Child>, __id<_Env>...>>; @@ -4468,10 +4456,6 @@ namespace stdexec { using __ensure_started::ensure_started_t; inline constexpr ensure_started_t ensure_started{}; - template <> - inline constexpr __ensure_started::__lower_ensure_started - __default_sender_transform< ensure_started_t>{}; - STDEXEC_PRAGMA_PUSH() STDEXEC_PRAGMA_IGNORE_EDG(not_used_in_partial_spec_arg_list) @@ -5446,7 +5430,7 @@ namespace stdexec { using _Env = __t<__environ<__id<__decay_t<_Scheduler>>>>; auto __env = __join_env(_Env{(_Scheduler&&) __sched}, stdexec::get_env(__sndr)); auto __domain = query_or(get_domain, __sched, __default_domain()); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr(std::move(__env), (_Sender&&) __sndr)); } @@ -5529,7 +5513,7 @@ namespace stdexec { auto operator()(_Sender&& __sndr, _Scheduler&& __sched) const { auto __domain = __get_sender_domain(__sndr); using _Env = __t<__environ<__id<__decay_t<_Scheduler>>>>; - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr( __join_env(_Env{(_Scheduler&&) __sched}, stdexec::get_env(__sndr)), @@ -5557,13 +5541,11 @@ namespace stdexec { static __data_of get_env(const _Sender& __sndr) noexcept { return apply_sender(__sndr, __detail::__get_data()); } - }; - struct __lower_transfer { template - auto operator()(_Sender&& __sndr, const _Env& __env) const { + static auto transform_sender(_Sender&& __sndr, const _Env& __env) { auto __domain = __get_env_domain(__env); - return transform_sender( + return stdexec::transform_sender( __domain, apply_sender( (_Sender&&) __sndr, @@ -5579,9 +5561,6 @@ namespace stdexec { using __transfer::transfer_t; inline constexpr transfer_t transfer{}; - template <> - inline constexpr __transfer::__lower_transfer __default_sender_transform{}; - ///////////////////////////////////////////////////////////////////////////// // [execution.senders.transfer_just] namespace __transfer_just { @@ -5649,7 +5628,7 @@ namespace stdexec { template auto operator()(_Sender&& __sndr, _Envs... __envs) const { auto __domain = __get_sender_domain(__sndr); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr<__write_t>(__join_env(std::move(__envs)...), (_Sender&&) __sndr)); } @@ -5860,22 +5839,20 @@ namespace stdexec { template auto operator()(_Scheduler&& __sched, _Sender&& __sndr) const { auto __domain = __get_sender_domain((_Sender&&) __sndr); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr((_Scheduler&&) __sched, (_Sender&&) __sndr)); } - }; - struct __lower_on { template - using __sender = + using __sender_t = __t<__sender>, stdexec::__id<__decay_t<_Sender>>>>; template - auto operator()(_Sender&& __sndr, const _Env&...) const { + static auto transform_sender(_Sender&& __sndr, const _Env&...) { return apply_sender( (_Sender&&) __sndr, [](__ignore, _Data&& __data, _Child&& __child) { - return __sender<_Data, _Child>{(_Data&&) __data, (_Child&&) __child}; + return __sender_t<_Data, _Child>{(_Data&&) __data, (_Child&&) __child}; }); } }; @@ -5884,9 +5861,6 @@ namespace stdexec { using __on::on_t; inline constexpr on_t on{}; - template <> - inline constexpr __on::__lower_on __default_sender_transform{}; - // BUGBUG this will also be unnecessary when `on` returns a __sexpr namespace __detail { template @@ -5963,7 +5937,7 @@ namespace stdexec { template auto operator()(_Sender&& __sndr) const { auto __domain = __get_sender_domain(__sndr); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr(__(), std::move(__sndr))); } @@ -6386,7 +6360,7 @@ namespace stdexec { template auto operator()(_Senders&&... __sndrs) const { auto __domain = when_all_t::__common_domain(__get_sender_domain((_Senders&&) __sndrs)...); - return transform_sender( + return stdexec::transform_sender( __domain, make_sender_expr(__(), (_Senders&&) __sndrs...)); } @@ -6719,52 +6693,13 @@ namespace stdexec { __mstring _Details = __no_scheduler_details> struct _CANNOT_RESTORE_EXECUTION_CONTEXT_AFTER_ON_ { }; - struct on_t : __default_get_env { - template - auto operator()(_Scheduler&& __sched, _Sender&& __sndr) const { - // BUGBUG __get_sender_domain, or get_domain(__sched), or ...? - auto __domain = __get_sender_domain(__sndr); - return transform_sender( - __domain, make_sender_expr((_Scheduler&&) __sched, (_Sender&&) __sndr)); - } - }; - - template - struct __continue_on_data { - _Scheduler __sched_; - _Closure __clsur_; - }; - template - __continue_on_data(_Scheduler, _Closure) -> __continue_on_data<_Scheduler, _Closure>; - - struct continue_on_t : __default_get_env { - template _Closure> - auto operator()(_Sender&& __sndr, _Scheduler&& __sched, _Closure&& __clsur) const { - auto __domain = __get_sender_domain(__sndr); - return transform_sender( - __domain, - make_sender_expr( - __continue_on_data{(_Scheduler&&) __sched, (_Closure&&) __clsur}, (_Sender&&) __sndr)); - } - - template - auto operator()(_Scheduler&& __sched, _Closure&& __clsur) const - -> __binder_back, __decay_t<_Closure>> { - return { - {}, - {}, - {(_Scheduler&&) __sched, (_Closure&&) __clsur} - }; - } - }; - STDEXEC_PRAGMA_PUSH() STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-local-typedefs") struct __no_scheduler_in_environment { // Issue a custom diagnostic if the environment doesn't provide a scheduler. template - auto operator()(_Sender&&, const _Env&) const { + static auto transform_sender(_Sender&&, const _Env&) { struct __no_scheduler_in_environment { using is_sender = void; @@ -6790,15 +6725,23 @@ namespace stdexec { } }; - struct __lower_on : __no_scheduler_in_environment { - using __no_scheduler_in_environment::operator(); + struct on_t : __default_get_env, __no_scheduler_in_environment { + template + auto operator()(_Scheduler&& __sched, _Sender&& __sndr) const { + // BUGBUG __get_sender_domain, or get_domain(__sched), or ...? + auto __domain = __get_sender_domain(__sndr); + return stdexec::transform_sender( + __domain, make_sender_expr((_Scheduler&&) __sched, (_Sender&&) __sndr)); + } + + using __no_scheduler_in_environment::transform_sender; template requires __callable - auto operator()(_Sender&& __sndr, const _Env& __env) const { + static auto transform_sender(_Sender&& __sndr, const _Env& __env) { auto __domain = __get_env_domain(__env); auto __old = get_scheduler(__env); - return transform_sender( + return stdexec::transform_sender( __domain, apply_sender( (_Sender&&) __sndr, @@ -6811,25 +6754,52 @@ namespace stdexec { } }; - template - auto __mkenv(_Scheduler __sched) { - auto __env = __join_env(__mkprop(get_scheduler, __sched), __mkprop(get_domain)); - using _Env = decltype(__env); + template + struct __continue_on_data { + _Scheduler __sched_; + _Closure __clsur_; + }; + template + __continue_on_data(_Scheduler, _Closure) -> __continue_on_data<_Scheduler, _Closure>; + + struct continue_on_t : __default_get_env, __no_scheduler_in_environment { + template _Closure> + auto operator()(_Sender&& __sndr, _Scheduler&& __sched, _Closure&& __clsur) const { + auto __domain = __get_sender_domain(__sndr); + return stdexec::transform_sender( + __domain, + make_sender_expr( + __continue_on_data{(_Scheduler&&) __sched, (_Closure&&) __clsur}, (_Sender&&) __sndr)); + } + + template + auto operator()(_Scheduler&& __sched, _Closure&& __clsur) const + -> __binder_back, __decay_t<_Closure>> { + return { + {}, + {}, + {(_Scheduler&&) __sched, (_Closure&&) __clsur} + }; + } + + template + static auto __mkenv(_Scheduler __sched) { + auto __env = __join_env(__mkprop(get_scheduler, __sched), __mkprop(get_domain)); + using _Env = decltype(__env); - struct __env_t : _Env { }; + struct __env_t : _Env { }; - return __env_t{__env}; - } + return __env_t{__env}; + } - struct __lower_continue_on : __no_scheduler_in_environment { - using __no_scheduler_in_environment::operator(); + using __no_scheduler_in_environment::transform_sender; template requires __callable - auto operator()(_Sender&& __sndr, const _Env& __env) const { + static auto transform_sender(_Sender&& __sndr, const _Env& __env) { auto __domain = __get_env_domain(__env); auto __old = get_scheduler(__env); - return transform_sender( + return stdexec::transform_sender( __domain, apply_sender( (_Sender&&) __sndr, @@ -6856,13 +6826,6 @@ namespace stdexec { inline constexpr continue_on_t continue_on{}; } - // v2::on() has a sender transform that lowers it to more primitive operations - template <> - inline constexpr __on_v2::__lower_on __default_sender_transform{}; - - template <> - inline constexpr __on_v2::__lower_continue_on __default_sender_transform{}; - ///////////////////////////////////////////////////////////////////////////// // [execution.senders.consumers.sync_wait] // [execution.senders.consumers.sync_wait_with_variant] From 2c42dc187c79de9150ee7e368edffcb352071133 Mon Sep 17 00:00:00 2001 From: Luke Elliott Date: Thu, 12 Oct 2023 19:46:11 +0100 Subject: [PATCH 035/146] Fix test_iterate; missing . --- test/exec/sequence/test_iterate.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/test/exec/sequence/test_iterate.cpp b/test/exec/sequence/test_iterate.cpp index bc867e4d2..26100736c 100644 --- a/test/exec/sequence/test_iterate.cpp +++ b/test/exec/sequence/test_iterate.cpp @@ -22,6 +22,7 @@ #include #include +#include template struct sum_item_rcvr { From 3c202158bd3ce42adafba3aecbed60cb23f21e0a Mon Sep 17 00:00:00 2001 From: Luke Elliott Date: Thu, 12 Oct 2023 21:16:07 +0100 Subject: [PATCH 036/146] [[msvc::no_unique_address]] is not implemented in clang-cl. https://reviews.llvm.org/D110485 --- include/stdexec/__detail/__config.hpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/include/stdexec/__detail/__config.hpp b/include/stdexec/__detail/__config.hpp index 887f9005b..09f4cd657 100644 --- a/include/stdexec/__detail/__config.hpp +++ b/include/stdexec/__detail/__config.hpp @@ -165,7 +165,8 @@ // MSVCBUG https://developercommunity.visualstudio.com/t/Incorrect-codegen-when-using-msvc::no_/10452874 #define STDEXEC_ATTR_WHICH_3(_ATTR) // [[msvc::no_unique_address]] #elif STDEXEC_CLANG_CL() -#define STDEXEC_ATTR_WHICH_3(_ATTR) [[msvc::no_unique_address]] +// clang-cl does not support: https://reviews.llvm.org/D110485 +#define STDEXEC_ATTR_WHICH_3(_ATTR) // [[msvc::no_unique_address]] #else #define STDEXEC_ATTR_WHICH_3(_ATTR) [[no_unique_address]] #endif From 2adec7083f51d446b15a11cd2dc845262df95b6f Mon Sep 17 00:00:00 2001 From: Luke Elliott Date: Thu, 12 Oct 2023 19:50:20 +0100 Subject: [PATCH 037/146] __allocator is #defined in Windows SDK's shared/specstrings.h Use __allocator_c instead. --- include/stdexec/execution.hpp | 4 ++-- test/stdexec/algos/other/test_execute.cpp | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 0d62aed1b..361c2fe58 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -270,7 +270,7 @@ namespace stdexec { // TODO: implement allocator concept template - concept __allocator = true; + concept __allocator_c = true; struct get_scheduler_t : __query { friend constexpr bool tag_invoke(forwarding_query_t, const get_scheduler_t&) noexcept { @@ -309,7 +309,7 @@ namespace stdexec { auto operator()(const _Env& __env) const noexcept -> tag_invoke_result_t { static_assert(nothrow_tag_invocable); - static_assert(__allocator>); + static_assert(__allocator_c>); return tag_invoke(get_allocator_t{}, __env); } diff --git a/test/stdexec/algos/other/test_execute.cpp b/test/stdexec/algos/other/test_execute.cpp index c0476230d..e31c0679b 100644 --- a/test/stdexec/algos/other/test_execute.cpp +++ b/test/stdexec/algos/other/test_execute.cpp @@ -14,6 +14,11 @@ * limitations under the License. */ +#ifdef _WIN32 +// __allocator is #defined in Windows SDK's shared/specstrings.h. +#include +#endif + #include #include #include From f8bc3ee7cd7c8427e5ebedc3a4dc6e37eed1a457 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Fri, 13 Oct 2023 16:42:15 -0700 Subject: [PATCH 038/146] port `let_[value|error|stopped]` to `transform_sender` also, simplify `default_domain` --- include/exec/__detail/__basic_sequence.hpp | 6 +- include/stdexec/__detail/__execution_fwd.hpp | 7 +- include/stdexec/execution.hpp | 199 +++++++++--------- test/stdexec/algos/adaptors/test_when_all.cpp | 35 +-- 4 files changed, 120 insertions(+), 127 deletions(-) diff --git a/include/exec/__detail/__basic_sequence.hpp b/include/exec/__detail/__basic_sequence.hpp index ed152afbf..1afcafd2a 100644 --- a/include/exec/__detail/__basic_sequence.hpp +++ b/include/exec/__detail/__basic_sequence.hpp @@ -111,7 +111,7 @@ namespace exec { #if STDEXEC_NVHPC() || (STDEXEC_GCC() && __GNUC__ < 13) namespace __detail { - template > + template struct make_sequence_expr_t { template constexpr auto operator()(_Data __data = {}, _Children... __children) const { @@ -122,7 +122,7 @@ namespace exec { } #else namespace __detail { - template > + template struct make_sequence_expr_t { template constexpr auto operator()(_Data __data = {}, _Children... __children) const { @@ -133,7 +133,7 @@ namespace exec { } #endif - template > + template inline constexpr __detail::make_sequence_expr_t<_Tag, _Domain> make_sequence_expr{}; template diff --git a/include/stdexec/__detail/__execution_fwd.hpp b/include/stdexec/__detail/__execution_fwd.hpp index 74f8f70c4..d95559fa6 100644 --- a/include/stdexec/__detail/__execution_fwd.hpp +++ b/include/stdexec/__detail/__execution_fwd.hpp @@ -23,12 +23,7 @@ namespace stdexec { struct __none_such; ////////////////////////////////////////////////////////////////////////////////////////////////// - namespace __domain { - template - struct __default_domain; - } - - using __domain::__default_domain; + struct default_domain; ////////////////////////////////////////////////////////////////////////////////////////////////// namespace __receivers { diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 08cdd478b..41e716d40 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -123,68 +123,36 @@ namespace stdexec { }; template - concept __has_default_transform = - sender_expr<_Sender> && // - __has_transform_member<__tag_of<_Sender>, _Sender, _Env...>; + concept __has_default_transform = sender_expr<_Sender> && // + __has_transform_member<__tag_of<_Sender>, _Sender, _Env...>; + } // namespace __domain - template - struct __default_domain { - __default_domain() = default; + struct default_domain { + default_domain() = default; - explicit __default_domain(_Base __base) - : __base_(static_cast<_Base&&>(__base)) { - } - - template - using __legacy_transform_result_t = - __call_result_t; - - // Called without the environment during eager customization - template - decltype(auto) transform_sender(_Sender&& __sndr) const { - // Look for a legacy customization for the given tag, and if found, apply it. - if constexpr (__callable) { - return apply_sender((_Sender&&) __sndr, __legacy_customization()); - } else if constexpr (__has_default_transform<_Sender>) { - return __tag_of<_Sender>().transform_sender((_Sender&&) __sndr); - } else { - return _Sender((_Sender&&) __sndr); - } - } - - // Called with an environment during lazy customization - template - decltype(auto) transform_sender(_Sender&& __sndr, const _Env& __env) const { - if constexpr (__has_default_transform<_Sender, _Env>) { - return __tag_of<_Sender>().transform_sender((_Sender&&) __sndr, __env); - } else { - return _Sender((_Sender&&) __sndr); - } - } - - // BUGBUG remove me when we no longer support finding customizations via the - // completion scheduler. - operator _Base&&() && noexcept { - return (_Base&&) __base_; - } - - operator const _Base &() const & noexcept { - return __base_; - } - - _Base&& base() && noexcept { - return __base_; + // Called without the environment during eager customization + template + decltype(auto) transform_sender(_Sender&& __sndr) const { + // Look for a legacy customization for the given tag, and if found, apply it. + if constexpr (__callable) { + return apply_sender((_Sender&&) __sndr, __domain::__legacy_customization()); + } else if constexpr (__domain::__has_default_transform<_Sender>) { + return __tag_of<_Sender>().transform_sender((_Sender&&) __sndr); + } else { + return _Sender((_Sender&&) __sndr); } + } - const _Base& base() const & noexcept { - return __base_; + // Called with an environment during lazy customization + template + decltype(auto) transform_sender(_Sender&& __sndr, const _Env& __env) const { + if constexpr (__domain::__has_default_transform<_Sender, _Env>) { + return __tag_of<_Sender>().transform_sender((_Sender&&) __sndr, __env); + } else { + return _Sender((_Sender&&) __sndr); } - - STDEXEC_ATTRIBUTE((no_unique_address)) _Base __base_; - }; - } - - using __domain::__default_domain; + } + }; ////////////////////////////////////////////////////////////////////////////////////////////////// // [exec.queries] @@ -684,7 +652,7 @@ namespace stdexec { // Find the domain by first asking the sender's env, then the completion scheduler, // and finally using the default domain (which wraps the completion scheduler) auto __env = __join_env(get_env(__sndr), __sched); - return query_or(get_domain, __env, __default_domain{__sched}); + return query_or(get_domain, __env, default_domain()); } } __get_sender_domain{}; @@ -701,7 +669,7 @@ namespace stdexec { // Find the domain by first asking the env, then the current scheduler, // and finally using the default domain if all else fails. auto __env2 = __join_env(__env, __sched); - return query_or(get_domain, __env2, __default_domain{}); + return query_or(get_domain, __env2, default_domain()); } } __get_env_domain{}; @@ -1218,13 +1186,6 @@ namespace stdexec { using __debug::__is_debug_env; using __debug::__debug_sender; - // [execution.general.queries], general queries - namespace __queries { - struct get_scheduler_t; - } // namespace __queries - - using __queries::get_scheduler_t; - ///////////////////////////////////////////////////////////////////////////// // [execution.transform_sender] inline constexpr struct transform_sender_t { @@ -1234,7 +1195,7 @@ namespace stdexec { if constexpr (__domain::__has_transform_member<_Domain, _Sender, const _Env&...>) { return __dom.transform_sender((_Sender&&) __sndr, __env...); } else { - return __default_domain().transform_sender((_Sender&&) __sndr, __env...); + return default_domain().transform_sender((_Sender&&) __sndr, __env...); } } } transform_sender{}; @@ -2602,6 +2563,11 @@ namespace stdexec { using __cust_sigs_sender = // __types< tag_invoke_t(start_detached_t, _Sender), + // For legacy reasons: + tag_invoke_t( + start_detached_t, + get_completion_scheduler_t(get_env_t(const _Sender&)), + _Sender), tag_invoke_t(start_detached_t, __get_sender_domain_t(const _Sender&), _Sender)>; using _Env = __1; @@ -4076,7 +4042,8 @@ namespace stdexec { requires sender_in<_Sender, empty_env> && __decay_copyable> auto operator()(_Sender&& __sndr) const { auto __domain = __get_sender_domain(__sndr); - return stdexec::transform_sender(__domain, make_sender_expr(__(), (_Sender&&) __sndr)); + return stdexec::transform_sender( + __domain, make_sender_expr(__(), (_Sender&&) __sndr)); } template @@ -4094,7 +4061,10 @@ namespace stdexec { using _Sender = __1; using __legacy_customizations_t = // __types< - tag_invoke_t(split_t, __get_sender_domain_t(const _Sender&), _Sender), + tag_invoke_t( + split_t, + get_completion_scheduler_t(get_env_t(const _Sender&)), + _Sender), tag_invoke_t(split_t, _Sender)>; template @@ -4125,7 +4095,7 @@ namespace stdexec { template struct __sh_state; - template + template > struct __receiver { using _CvrefSender = stdexec::__cvref_t<_CvrefSenderId>; using _Env = stdexec::__t<_EnvId>; @@ -4403,7 +4373,7 @@ namespace stdexec { } }; - struct ensure_started_t : __ensure_started_t { + struct ensure_started_t { template requires sender_in<_Sender, empty_env> && __decay_copyable> auto operator()(_Sender&& __sndr) const { @@ -4437,10 +4407,18 @@ namespace stdexec { using _Sender = __1; using __legacy_customizations_t = // __types< - tag_invoke_t(ensure_started_t, __get_sender_domain_t(const _Sender&), _Sender), + tag_invoke_t( + ensure_started_t, + get_completion_scheduler_t(get_env_t(const _Sender&)), + _Sender), tag_invoke_t(ensure_started_t, _Sender)>; + template + using __receiver_t = + stdexec::__t<__meval<__receiver, __cvref_id<_CvrefSender>, __id<_Env>...>>; + template + requires sender_to<__child_of<_Sender>, __receiver_t<__child_of<_Sender>, _Env...>> static auto transform_sender(_Sender&& __sndr, _Env... __env) { return apply_sender( (_Sender&&) __sndr, [&](__ignore, __ignore, _Child&& __child) { @@ -4768,39 +4746,44 @@ namespace stdexec { return {}; } + // BUGBUG better would be to port the `let_[value|error|stopped]` algorithms to __sexpr + template + static auto apply(_Self&& __self, _ApplyFun __fun) -> __call_result_t< + _ApplyFun, + _SetId, // Actually one of let_value_t, let_error_t, let_stopped_t + __copy_cvref_t<_Self, _Fun>, + __copy_cvref_t<_Self, _Sender>> { + return ((_ApplyFun&&) __fun)( + _SetId(), ((_Self&&) __self).__fun_, ((_Self&&) __self).__sndr_); + } + _Sender __sndr_; _Fun __fun_; }; }; template - struct __let_xxx_t { + struct __let_xxx_t : __default_get_env<_LetTag> { + using _Sender = __1; + using _Function = __0; + using __legacy_customizations_t = __types< + tag_invoke_t( + _LetTag, + get_completion_scheduler_t(get_env_t(const _Sender&)), + _Sender, + _Function), + tag_invoke_t(_LetTag, _Sender, _Function)>; + using __t = _SetTag; template using __sender = stdexec::__t<__let::__sender>, _Fun, _LetTag>>; template - requires __tag_invocable_with_domain<_LetTag, set_value_t, _Sender, _Fun> - sender auto operator()(_Sender&& __sndr, _Fun __fun) const - noexcept(nothrow_tag_invocable<_LetTag, __sender_domain_of_t<_Sender>, _Sender, _Fun>) { - auto __domain = __get_sender_domain(__sndr); - return tag_invoke(_LetTag{}, __domain, (_Sender&&) __sndr, (_Fun&&) __fun); - } - - template - requires(!__tag_invocable_with_domain<_LetTag, set_value_t, _Sender, _Fun>) - && tag_invocable<_LetTag, _Sender, _Fun> - sender auto operator()(_Sender&& __sndr, _Fun __fun) const - noexcept(nothrow_tag_invocable<_LetTag, _Sender, _Fun>) { - return tag_invoke(_LetTag{}, (_Sender&&) __sndr, (_Fun&&) __fun); - } - - template - requires(!__tag_invocable_with_domain<_LetTag, set_value_t, _Sender, _Fun>) - && (!tag_invocable<_LetTag, _Sender, _Fun>) && sender<__sender<_Sender, _Fun>> - __sender<_Sender, _Fun> operator()(_Sender&& __sndr, _Fun __fun) const { - return __sender<_Sender, _Fun>{(_Sender&&) __sndr, (_Fun&&) __fun}; + auto operator()(_Sender&& __sndr, _Fun __fun) const { + auto __domain = __get_sender_domain((_Sender&&) __sndr); + return stdexec::transform_sender( + __domain, __sender<_Sender, _Fun>{(_Sender&&) __sndr, (_Fun&&) __fun}); } template @@ -4823,6 +4806,13 @@ namespace stdexec { using __let::let_stopped_t; inline constexpr let_stopped_t let_stopped{}; + // BUGBUG this will also be unnecessary when `on` returns a __sexpr + namespace __detail { + template + extern __mconst<__let::__sender<__name_of<__t<_SenderId>>, _Fun, _SetId>> + __name_of_v<__let::__sender<_SenderId, _Fun, _SetId>>; + } + ///////////////////////////////////////////////////////////////////////////// // [execution.senders.adaptors.stopped_as_optional] // [execution.senders.adaptors.stopped_as_error] @@ -5429,7 +5419,7 @@ namespace stdexec { auto operator()(_Scheduler&& __sched, _Sender&& __sndr) const { using _Env = __t<__environ<__id<__decay_t<_Scheduler>>>>; auto __env = __join_env(_Env{(_Scheduler&&) __sched}, stdexec::get_env(__sndr)); - auto __domain = query_or(get_domain, __sched, __default_domain()); + auto __domain = query_or(get_domain, __sched, default_domain()); return stdexec::transform_sender( __domain, make_sender_expr(std::move(__env), (_Sender&&) __sndr)); } @@ -5532,7 +5522,7 @@ namespace stdexec { __types< tag_invoke_t( transfer_t, - __get_sender_domain_t(_Sender), + get_completion_scheduler_t(get_env_t(const _Sender&)), _Sender, get_completion_scheduler_t(_Env)), tag_invoke_t(transfer_t, _Sender, get_completion_scheduler_t(_Env))>; @@ -5835,8 +5825,7 @@ namespace stdexec { struct on_t : __default_get_env { using _Sender = __1; using _Scheduler = __0; - using __legacy_customizations_t = __types< - tag_invoke_t(on_t, _Scheduler, _Sender)>; + using __legacy_customizations_t = __types< tag_invoke_t(on_t, _Scheduler, _Sender)>; template auto operator()(_Scheduler&& __sched, _Sender&& __sndr) const { @@ -6383,7 +6372,7 @@ namespace stdexec { } template - static __default_domain __common_domain(_Domains...) noexcept { + static default_domain __common_domain(_Domains...) noexcept { return {}; } @@ -6396,7 +6385,7 @@ namespace stdexec { template _Self> static auto get_env(const _Self& __self) noexcept { using _Domain = __call_result_t; - if constexpr (same_as<_Domain, __default_domain>) { + if constexpr (same_as<_Domain, default_domain>) { return empty_env(); } else { return __env::__env_fn{ @@ -6727,7 +6716,9 @@ namespace stdexec { } }; - struct on_t : __default_get_env, __no_scheduler_in_environment { + struct on_t + : __default_get_env + , __no_scheduler_in_environment { template auto operator()(_Scheduler&& __sched, _Sender&& __sndr) const { // BUGBUG __get_sender_domain, or get_domain(__sched), or ...? @@ -6764,7 +6755,9 @@ namespace stdexec { template __continue_on_data(_Scheduler, _Closure) -> __continue_on_data<_Scheduler, _Closure>; - struct continue_on_t : __default_get_env, __no_scheduler_in_environment { + struct continue_on_t + : __default_get_env + , __no_scheduler_in_environment { template _Closure> auto operator()(_Sender&& __sndr, _Scheduler&& __sched, _Closure&& __clsur) const { auto __domain = __get_sender_domain(__sndr); @@ -6925,7 +6918,9 @@ namespace stdexec { using _Sender = __0; template using __cust_sigs = __types< - tag_invoke_t(_Tag, __get_sender_domain_t STDEXEC_MSVC((*))(const _Sender&), _Sender), + // For legacy reasons: + tag_invoke_t(_Tag, get_completion_scheduler_t(get_env_t(const _Sender&)), _Sender), + tag_invoke_t(_Tag, __get_sender_domain_t STDEXEC_MSVC((*) )(const _Sender&), _Sender), tag_invoke_t(_Tag, _Sender)>; template diff --git a/test/stdexec/algos/adaptors/test_when_all.cpp b/test/stdexec/algos/adaptors/test_when_all.cpp index f86525cdf..5fdb2a83a 100644 --- a/test/stdexec/algos/adaptors/test_when_all.cpp +++ b/test/stdexec/algos/adaptors/test_when_all.cpp @@ -361,22 +361,25 @@ TEST_CASE("when_all_with_variant can be customized", "[adaptors][when_all]") { using my_string_variant_sender_t = decltype(ex::into_variant(my_string_sender_t{std::string{}})); -auto tag_invoke(ex::when_all_t, my_string_variant_sender_t, my_string_variant_sender_t) { - // Return a different sender when we invoke this custom defined on implementation - return ex::just(std::string{"first program"}); -} - -TEST_CASE( - "when_all_with_variant take into account when_all cusomizations", - "[adaptors][when_all]") { - // when_all_with_variant must be using the `when_all` implementation that allows cusomizations - // The customization will return a different value - auto snd = ex::when_all_with_variant( // - my_string_sender_t{std::string{"hello,"}}, // - my_string_sender_t{std::string{" world!"}} // - ); - wait_for_value(std::move(snd), std::string{"first program"}); -} +// There is no way for ADL to find this overload. This test is broken and needs to be rewritten +// using a custom domain. +// +// auto tag_invoke(ex::when_all_t, my_string_variant_sender_t, my_string_variant_sender_t) { +// // Return a different sender when we invoke this custom defined on implementation +// return ex::just(std::string{"first program"}); +// } + +// TEST_CASE( +// "when_all_with_variant take into account when_all customizations", +// "[adaptors][when_all]") { +// // when_all_with_variant must be using the `when_all` implementation that allows customizations +// // The customization will return a different value +// auto snd = ex::when_all_with_variant( // +// my_string_sender_t{std::string{"hello,"}}, // +// my_string_sender_t{std::string{" world!"}} // +// ); +// wait_for_value(std::move(snd), std::string{"first program"}); +// } TEST_CASE("when_all returns empty env", "[adaptors][when_all]") { check_env_type(ex::when_all(ex::just(), ex::just())); From 9987d38e8c4e2f5c94c15b9bed79c148c5ee82e4 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sat, 14 Oct 2023 14:01:39 -0700 Subject: [PATCH 039/146] port all the when_all composite algos to transform_sender --- include/stdexec/execution.hpp | 207 ++++++++++++++++++++++------------ 1 file changed, 138 insertions(+), 69 deletions(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 7835a0235..663b61610 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -5760,6 +5760,9 @@ namespace stdexec { }; }; + template + struct __sender; + struct on_t; template @@ -5822,7 +5825,7 @@ namespace stdexec { }; }; - struct on_t : __default_get_env { + struct on_t { using _Sender = __1; using _Scheduler = __0; using __legacy_customizations_t = __types< tag_invoke_t(on_t, _Scheduler, _Sender)>; @@ -5838,13 +5841,24 @@ namespace stdexec { using __sender_t = __t<__sender>, stdexec::__id<__decay_t<_Sender>>>>; - template - static auto transform_sender(_Sender&& __sndr, const _Env&...) { - return apply_sender( - (_Sender&&) __sndr, - [](__ignore, _Data&& __data, _Child&& __child) { - return __sender_t<_Data, _Child>{(_Data&&) __data, (_Child&&) __child}; - }); + template _Sender> + static auto get_env(const _Sender& __sndr) noexcept + -> __call_result_t { + return apply_sender(__sndr, __get_env_fn()); + } + + template + requires __is_not_instance_of<__id<__decay_t<_Sender>>, __sender> + static auto transform_sender(_Sender&& __sndr, const _Env& __env) { + auto __domain = __get_env_domain(__env); + return stdexec::transform_sender( + __domain, + apply_sender( + (_Sender&&) __sndr, + [](__ignore, _Data&& __data, _Child&& __child) { + return __sender_t<_Data, _Child>{(_Data&&) __data, (_Child&&) __child}; + }), + __env); } }; } // namespace __on @@ -6345,43 +6359,56 @@ namespace stdexec { struct _INVALID_ARGUMENTS_TO_WHEN_ALL_ { }; + struct __common_domain_fn { + template + requires __all_of<_Domain, _OtherDomains...> + static _Domain __common_domain(_Domain __domain, _OtherDomains...) noexcept { + return (_Domain&&) __domain; + } + + template + static __none_such __common_domain(_Domains...) noexcept { + return {}; + } + + static default_domain __common_domain() noexcept { + return {}; + } + + auto operator()(__ignore, __ignore, const auto&... __sndrs) const noexcept { + return __common_domain(__get_sender_domain(__sndrs)...); + } + }; + + template + using __common_domain_t = // + __call_result_t<__common_domain_fn, int, int, _Senders...>; + + template + concept __has_common_domain = // + __none_of<__none_such, __common_domain_t<_Senders...>>; + struct when_all_t { - // BUGBUG: look for a customization? + // Used by the default_domain to find legacy customizations: + using _Sender = __1; + using __legacy_customizations_t = // + __types; + // TODO: improve diagnostic when senders have different domains template + requires __has_common_domain<_Senders...> auto operator()(_Senders&&... __sndrs) const { - auto __domain = when_all_t::__common_domain(__get_sender_domain((_Senders&&) __sndrs)...); + auto __domain = __common_domain_t<_Senders...>(); return stdexec::transform_sender( __domain, make_sender_expr(__(), (_Senders&&) __sndrs...)); } - using _Sender = __1; - using __legacy_customizations_t = // - __types; - #if STDEXEC_FRIENDSHIP_IS_LEXICAL() private: template friend struct stdexec::__sexpr; #endif - template - requires __all_of<_Domain, _OtherDomains...> - static _Domain __common_domain(_Domain __domain, _OtherDomains...) noexcept { - return (_Domain&&) __domain; - } - - template - static default_domain __common_domain(_Domains...) noexcept { - return {}; - } - - struct __common_domain_fn { - auto operator()(auto, auto&&, const auto&... __sndrs) const noexcept { - return when_all_t::__common_domain(__get_sender_domain(__sndrs)...); - } - }; - template _Self> static auto get_env(const _Self& __self) noexcept { using _Domain = __call_result_t; @@ -6446,59 +6473,101 @@ namespace stdexec { using __into_variant_result_t = __result_of; struct when_all_with_variant_t { - template - requires tag_invocable - auto operator()(_Senders&&... __sndrs) const - noexcept(nothrow_tag_invocable) - -> tag_invoke_result_t { - static_assert( - sender>, - "A customization of when_all_with_variant must return a sender"); - return tag_invoke(*this, (_Senders&&) __sndrs...); - } + using _Sender = __1; + using __legacy_customizations_t = // + __types; template - requires(!tag_invocable) + requires __has_common_domain<_Senders...> auto operator()(_Senders&&... __sndrs) const { - return when_all_t{}(into_variant((_Senders&&) __sndrs)...); + auto __domain = __common_domain_t<_Senders...>(); + return stdexec::transform_sender( + __domain, make_sender_expr(__(), (_Senders&&) __sndrs...)); + } + + template + static auto transform_sender(_Sender&& __sndr, const _Env& __env) { + // transform the when_all_with_variant into a regular when_all (looking for + // early when_all customizations), then transform it again to look for + // late customizations. + return apply_sender( + (_Sender&&) __sndr, [&](__ignore, __ignore, _Child&&... __child) { + auto __domain = __get_env_domain(__env); + return stdexec::transform_sender( + __domain, + when_all_t()( + stdexec::transform_sender(__domain, into_variant((_Child&&) __child), __env)...), + __env); + }); } }; struct transfer_when_all_t { - template - requires tag_invocable - auto operator()(_Sched&& __sched, _Senders&&... __sndrs) const - noexcept(nothrow_tag_invocable) - -> tag_invoke_result_t { - static_assert( - sender>, - "A customization of transfer_when_all must return a sender"); - return tag_invoke(*this, (_Sched&&) __sched, (_Senders&&) __sndrs...); + using _Scheduler = __0; + using _Sender = __1; + using __legacy_customizations_t = // + __types; + + template + requires __has_common_domain<_Senders...> + auto operator()(_Scheduler&& __sched, _Senders&&... __sndrs) const { + auto __domain = query_or(get_domain, __sched, default_domain()); + return stdexec::transform_sender( + __domain, + make_sender_expr((_Scheduler&&) __sched, (_Senders&&) __sndrs...)); } - template - requires(!tag_invocable) - auto operator()(_Sched&& __sched, _Senders&&... __sndrs) const { - return transfer(when_all_t{}((_Senders&&) __sndrs...), (_Sched&&) __sched); + template + static auto transform_sender(_Sender&& __sndr, const _Env& __env) { + // transform the transfer_when_all into a regular transform | when_all + // (looking for early customizations), then transform it again to look for + // late customizations. + return apply_sender( + (_Sender&&) __sndr, + [&](__ignore, _Data&& __data, _Child&&... __child) { + auto __domain = __get_env_domain(__env); + return stdexec::transform_sender( + __domain, + transfer( + stdexec::transform_sender(__domain, when_all_t()((_Child&&) __child...), __env), + (_Data&&) __data), + __env); + }); } }; struct transfer_when_all_with_variant_t { - template - requires tag_invocable - auto operator()(_Sched&& __sched, _Senders&&... __sndrs) const - noexcept(nothrow_tag_invocable) - -> tag_invoke_result_t { - static_assert( - sender>, - "A customization of transfer_when_all_with_variant must return a sender"); - return tag_invoke(*this, (_Sched&&) __sched, (_Senders&&) __sndrs...); + using _Scheduler = __0; + using _Sender = __1; + using __legacy_customizations_t = // + __types; + + template + requires __has_common_domain<_Senders...> + auto operator()(_Scheduler&& __sched, _Senders&&... __sndrs) const { + auto __domain = query_or(get_domain, __sched, default_domain()); + return stdexec::transform_sender( + __domain, + make_sender_expr( + (_Scheduler&&) __sched, (_Senders&&) __sndrs...)); } - template - requires(!tag_invocable) - auto operator()(_Sched&& __sched, _Senders&&... __sndrs) const { - return transfer_when_all_t{}((_Sched&&) __sched, into_variant((_Senders&&) __sndrs)...); + template + static auto transform_sender(_Sender&& __sndr, const _Env& __env) { + // transform the transfer_when_all into a regular transform | when_all + // (looking for early customizations), then transform it again to look for + // late customizations. + return apply_sender( + (_Sender&&) __sndr, + [&](__ignore, _Data&& __data, _Child&&... __child) { + auto __domain = __get_env_domain(__env); + return stdexec::transform_sender( + __domain, + transfer_when_all_t()( + (_Data&&) __data, + stdexec::transform_sender(__domain, into_variant((_Child&&) __child), __env)...), + __env); + }); } }; } // namespace __when_all From 8ddc7dfda971ec6f5722cd5da09961b352630852 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 15 Oct 2023 14:26:11 -0700 Subject: [PATCH 040/146] domain-based customizations of [transfer_]when_all[_with_variant] follows P2999 --- include/exec/any_sender_of.hpp | 2 +- include/exec/async_scope.hpp | 8 +- include/exec/env.hpp | 8 +- include/exec/when_any.hpp | 5 +- include/nvexec/stream/common.cuh | 2 +- include/nvexec/stream/when_all.cuh | 2 +- include/stdexec/execution.hpp | 159 ++++++++++-------- .../algos/adaptors/test_transfer_when_all.cpp | 159 ++++++++++++++++++ test/stdexec/algos/adaptors/test_when_all.cpp | 154 ++++++++++++++++- 9 files changed, 407 insertions(+), 92 deletions(-) diff --git a/include/exec/any_sender_of.hpp b/include/exec/any_sender_of.hpp index 31e853dd3..65ff4585f 100644 --- a/include/exec/any_sender_of.hpp +++ b/include/exec/any_sender_of.hpp @@ -829,7 +829,7 @@ namespace exec { friend __env_t> tag_invoke(_GetEnv, const _Self& __self) noexcept { return __make_env( get_env(__self.__op_->__rcvr_), - __with_(get_stop_token, __self.__op_->__stop_source_.get_token())); + __mkprop(__self.__op_->__stop_source_.get_token(), get_stop_token)); } }; }; diff --git a/include/exec/async_scope.hpp b/include/exec/async_scope.hpp index 4fb039ec1..24ca4dbd4 100644 --- a/include/exec/async_scope.hpp +++ b/include/exec/async_scope.hpp @@ -602,8 +602,8 @@ namespace exec { using __spawn_env_t = __result_of< __join_env, _Env, - __env::__prop, - __env::__prop>; + __env::__prop, + __env::__prop<__inln::__scheduler(get_scheduler_t)>>; template struct __spawn_op_base { @@ -647,8 +647,8 @@ namespace exec { template <__decays_to<_Sender> _Sndr> __spawn_op(_Sndr&& __sndr, _Env __env, const __impl* __scope) : __spawn_op_base<_EnvId>{__join_env((_Env&&) __env, - __mkprop(get_stop_token, __scope->__stop_source_.get_token()), - __mkprop(get_scheduler, __inln::__scheduler{})), + __mkprop(__scope->__stop_source_.get_token(), get_stop_token), + __mkprop(__inln::__scheduler{}, get_scheduler)), [](__spawn_op_base<_EnvId>* __op) { delete static_cast<__spawn_op*>(__op); }} diff --git a/include/exec/env.hpp b/include/exec/env.hpp index 6b95339b3..fc8d3a5c9 100644 --- a/include/exec/env.hpp +++ b/include/exec/env.hpp @@ -23,19 +23,19 @@ #endif namespace exec { - template + template using with_t = stdexec::__with<_Tag, _Value>; namespace __detail { struct __with_t { template - with_t<_Tag, _Value> operator()(_Tag, _Value&& __val) const { - return stdexec::__with_(_Tag(), (_Value&&) __val); + with_t<_Tag, stdexec::__decay_t<_Value>> operator()(_Tag, _Value&& __val) const { + return stdexec::__mkprop((_Value&&) __val, _Tag()); } template with_t<_Tag> operator()(_Tag) const { - return stdexec::__with_(_Tag()); + return stdexec::__mkprop(_Tag()); } }; } // namespace __detail diff --git a/include/exec/when_any.hpp b/include/exec/when_any.hpp index e5af82756..24ffaed5b 100644 --- a/include/exec/when_any.hpp +++ b/include/exec/when_any.hpp @@ -172,9 +172,8 @@ namespace exec { } friend __env_t> tag_invoke(get_env_t, const __t& __self) noexcept { - using __with_token = __with; - auto __token = __with_(get_stop_token, __self.__op_->__stop_source_.get_token()); - return __make_env(get_env(__self.__op_->__receiver_), (__with_token&&) __token); + auto __token = __mkprop(__self.__op_->__stop_source_.get_token(), get_stop_token); + return __make_env(get_env(__self.__op_->__receiver_), std::move(__token)); } }; }; diff --git a/include/nvexec/stream/common.cuh b/include/nvexec/stream/common.cuh index be383faee..94f4b1c7a 100644 --- a/include/nvexec/stream/common.cuh +++ b/include/nvexec/stream/common.cuh @@ -326,7 +326,7 @@ namespace nvexec { template auto make_stream_env(BaseEnv&& base_env, stream_provider_t* stream_provider) noexcept { - return __join_env(__mkprop(get_stream_provider, stream_provider), (BaseEnv&&) base_env); + return __join_env(__mkprop(stream_provider, get_stream_provider), (BaseEnv&&) base_env); } template diff --git a/include/nvexec/stream/when_all.cuh b/include/nvexec/stream/when_all.cuh index a671e83de..e129d4068 100644 --- a/include/nvexec/stream/when_all.cuh +++ b/include/nvexec/stream/when_all.cuh @@ -200,7 +200,7 @@ namespace nvexec::STDEXEC_STREAM_DETAIL_NS { auto env = make_terminal_stream_env( exec::make_env( stdexec::get_env(base()), - __with_(get_stop_token, op_state_->stop_source_.get_token())), + __mkprop(op_state_->stop_source_.get_token(), get_stop_token)), &const_cast(op_state_->stream_providers_[Index])); return env; diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 663b61610..fd8cf2a31 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -395,38 +395,41 @@ namespace stdexec { using __id = empty_env; }; - template - struct __prop { + template + struct __prop; + + template + struct __prop<_Value(_Tags...)> { using __t = __prop; using __id = __prop; _Value __value_; - template _Key> + template <__one_of<_Tags...> _Key> friend auto tag_invoke(_Key, const __prop& __self) // noexcept(__nothrow_decay_copyable<_Value>) -> _Value { return __self.__value_; } }; - template - struct __prop<_Tag, __none_such> { + template + struct __prop { using __t = __prop; using __id = __prop; - template _Key, class _Self> + template <__one_of<_Tags...> _Key, class _Self> requires(std::is_base_of_v<__prop, __decay_t<_Self>>) friend auto tag_invoke(_Key, _Self&&) noexcept = delete; }; struct __mkprop_t { - template - auto operator()(_Tag, _Value&& __value) const noexcept(__nothrow_decay_copyable<_Value>) - -> __prop<_Tag, __decay_t<_Value>> { + template + auto operator()(_Value&& __value, _Tag, _Tags...) const + noexcept(__nothrow_decay_copyable<_Value>) -> __prop<__decay_t<_Value>(_Tag, _Tags...)> { return {(_Value&&) __value}; } template - auto operator()(_Tag) const -> __prop<_Tag> { + auto operator()(_Tag) const -> __prop { return {}; } }; @@ -488,10 +491,10 @@ namespace stdexec { }; template - struct __joined_env<__prop<_Tag>, _Base> : __env_fwd<_Base> { + struct __joined_env<__prop, _Base> : __env_fwd<_Base> { using __t = __joined_env; using __id = __joined_env; - STDEXEC_ATTRIBUTE((no_unique_address)) __prop<_Tag> __env_; + STDEXEC_ATTRIBUTE((no_unique_address)) __prop __env_; friend void tag_invoke(_Tag, const __joined_env&) noexcept = delete; }; @@ -549,22 +552,6 @@ namespace stdexec { friend auto tag_invoke(get_env_t, const __env_promise&) noexcept -> const _Env&; }; - template - constexpr auto __with_(_Tag, _Value __val) noexcept { - return __env_fn{ - [__val = std::move(__val)](_Tag) noexcept(__nothrow_copy_constructible<_Value>) { - return __val; - }}; - } - - template - __prop<_Tag> __with_(_Tag) noexcept { - return {}; - } - - template - using __with = decltype(__env::__with_(__declval<_Ts>()...)); - // For making an environment from key/value pairs and optionally // another environment. struct __make_env_t { @@ -619,10 +606,9 @@ namespace stdexec { // for making an environment from a single key/value pair inline constexpr __env::__mkprop_t __mkprop{}; - inline constexpr __env::__mkprop_t __with_{}; - template - using __with = __call_result_t<__env::__mkprop_t, _Tag, _Value>; + template + using __with = __env::__prop<_Value(_Tag)>; template using __make_env_t = __call_result_t<__env::__make_env_t, _Ts...>; @@ -2487,7 +2473,7 @@ namespace stdexec { struct __schedule_t { static auto get_env(__ignore) noexcept - -> __env::__prop, __scheduler>; + -> __env::__prop<__scheduler(get_completion_scheduler_t)>; using __compl_sigs = stdexec::completion_signatures; static __compl_sigs get_completion_signatures(__ignore, __ignore); @@ -2516,8 +2502,8 @@ namespace stdexec { }; inline auto __schedule_t::get_env(__ignore) noexcept - -> __env::__prop, __scheduler> { - return __mkprop(get_completion_scheduler, __scheduler{}); + -> __env::__prop<__scheduler(get_completion_scheduler_t)> { + return __mkprop(__scheduler{}, get_completion_scheduler); } } @@ -3866,7 +3852,7 @@ namespace stdexec { connect_result_t<_CvrefSender, __receiver_> __op_state2_; explicit __t(_CvrefSender&& __sndr, _Env __env = {}) - : __env_(__make_env((_Env&&) __env, __with_(get_stop_token, __stop_source_.get_token()))) + : __env_(__make_env((_Env&&) __env, __mkprop(__stop_source_.get_token(), get_stop_token))) , __op_state2_(connect((_CvrefSender&&) __sndr, __receiver_{*this})) { } @@ -4177,7 +4163,7 @@ namespace stdexec { connect_result_t<_CvrefSender, __receiver_t> __op_state2_; explicit __t(_CvrefSender&& __sndr, _Env __env = {}) - : __env_(__make_env((_Env&&) __env, __with_(get_stop_token, __stop_source_.get_token()))) + : __env_(__make_env((_Env&&) __env, __mkprop(__stop_source_.get_token(), get_stop_token))) , __op_state2_(connect((_CvrefSender&&) __sndr, __receiver_t{*this})) { start(__op_state2_); } @@ -4502,7 +4488,7 @@ namespace stdexec { }; inline constexpr auto __get_scheduler_prop = [](auto* __op) noexcept { - return __mkprop(get_scheduler, __op->__sched_); + return __mkprop(__op->__sched_, get_scheduler); }; inline constexpr auto __get_domain_prop = [](auto*) noexcept { @@ -4538,8 +4524,10 @@ namespace stdexec { // then we can use that instead of hard-coding `__inln::__scheduler` here. __one_of<_Scheduler, __none_such, __inln::__scheduler>, _Env, - __env:: - __env_join_t< __env::__prop, __env::__prop, _Env>>; + __env::__env_join_t< + __env::__prop<_Scheduler(get_scheduler_t)>, + __env::__prop, + _Env>>; // A metafunction that computes the type of the resulting operation state for a // given set of argument types. @@ -5371,16 +5359,15 @@ namespace stdexec { template struct __environ { - using _Scheduler = stdexec::__t<_SchedulerId>; - - struct __t { + struct __t + : __env::__prop( + get_completion_scheduler_t, + get_completion_scheduler_t)> { using __id = __environ; - _Scheduler __sched_; - - template <__one_of _Tag> - friend _Scheduler tag_invoke(get_completion_scheduler_t<_Tag>, const __t& __self) noexcept { - return __self.__sched_; + template _Key> + friend auto tag_invoke(_Key, const __t& __self) noexcept { + return query_or(get_domain, __self.__value_, default_domain()); } }; }; @@ -5682,7 +5669,7 @@ namespace stdexec { }; inline constexpr auto __sched_prop = [](auto* __op) noexcept { - return __mkprop(get_scheduler, __op->__scheduler_); + return __mkprop(__op->__scheduler_, get_scheduler); }; inline constexpr auto __domain_prop = [](auto*) noexcept { return __mkprop(get_domain); @@ -6388,7 +6375,21 @@ namespace stdexec { concept __has_common_domain = // __none_of<__none_such, __common_domain_t<_Senders...>>; - struct when_all_t { + template + struct __get_env_common_domain { + template _Self> + static auto get_env(const _Self& __self) noexcept { + using _Domain = __call_result_t; + if constexpr (same_as<_Domain, default_domain>) { + return empty_env(); + } else { + return __mkprop(apply_sender(__self, __common_domain_fn()), get_domain); + } + STDEXEC_UNREACHABLE(); + } + }; + + struct when_all_t : __get_env_common_domain { // Used by the default_domain to find legacy customizations: using _Sender = __1; using __legacy_customizations_t = // @@ -6409,18 +6410,6 @@ namespace stdexec { friend struct stdexec::__sexpr; #endif - template _Self> - static auto get_env(const _Self& __self) noexcept { - using _Domain = __call_result_t; - if constexpr (same_as<_Domain, default_domain>) { - return empty_env(); - } else { - return __env::__env_fn{ - __mkfield(apply_sender(__self, __common_domain_fn()))}; - } - STDEXEC_UNREACHABLE(); - } - template using __error = __mexception< _INVALID_ARGUMENTS_TO_WHEN_ALL_, @@ -6472,7 +6461,7 @@ namespace stdexec { template using __into_variant_result_t = __result_of; - struct when_all_with_variant_t { + struct when_all_with_variant_t : __get_env_common_domain { using _Sender = __1; using __legacy_customizations_t = // __types; @@ -6503,18 +6492,28 @@ namespace stdexec { }; struct transfer_when_all_t { - using _Scheduler = __0; + using _Env = __0; using _Sender = __1; using __legacy_customizations_t = // - __types; + __types(const _Env&), + _Sender...)>; template requires __has_common_domain<_Senders...> auto operator()(_Scheduler&& __sched, _Senders&&... __sndrs) const { + using _Env = __t<__schedule_from::__environ<__id<__decay_t<_Scheduler>>>>; auto __domain = query_or(get_domain, __sched, default_domain()); return stdexec::transform_sender( __domain, - make_sender_expr((_Scheduler&&) __sched, (_Senders&&) __sndrs...)); + make_sender_expr( + _Env{(_Scheduler&&) __sched}, (_Senders&&) __sndrs...)); + } + + template _Sender> + static __data_of get_env(const _Sender& __self) noexcept { + return apply_sender(__self, __detail::__get_data()); } template @@ -6530,33 +6529,42 @@ namespace stdexec { __domain, transfer( stdexec::transform_sender(__domain, when_all_t()((_Child&&) __child...), __env), - (_Data&&) __data), + get_completion_scheduler(__data)), __env); }); } }; struct transfer_when_all_with_variant_t { - using _Scheduler = __0; + using _Env = __0; using _Sender = __1; using __legacy_customizations_t = // - __types; + __types(const _Env&), + _Sender...)>; template requires __has_common_domain<_Senders...> auto operator()(_Scheduler&& __sched, _Senders&&... __sndrs) const { + using _Env = __t<__schedule_from::__environ<__id<__decay_t<_Scheduler>>>>; auto __domain = query_or(get_domain, __sched, default_domain()); return stdexec::transform_sender( __domain, make_sender_expr( - (_Scheduler&&) __sched, (_Senders&&) __sndrs...)); + _Env{(_Scheduler&&) __sched}, (_Senders&&) __sndrs...)); + } + + template _Sender> + static __data_of get_env(const _Sender& __self) noexcept { + return apply_sender(__self, __detail::__get_data()); } template static auto transform_sender(_Sender&& __sndr, const _Env& __env) { - // transform the transfer_when_all into a regular transform | when_all - // (looking for early customizations), then transform it again to look for - // late customizations. + // transform the transfer_when_allwith_variant into regular transform_when_all + // and into_variant calls/ (looking for early customizations), then transform it + // again to look for late customizations. return apply_sender( (_Sender&&) __sndr, [&](__ignore, _Data&& __data, _Child&&... __child) { @@ -6564,7 +6572,7 @@ namespace stdexec { return stdexec::transform_sender( __domain, transfer_when_all_t()( - (_Data&&) __data, + get_completion_scheduler((_Data&&) __data), stdexec::transform_sender(__domain, into_variant((_Child&&) __child), __env)...), __env); }); @@ -6574,10 +6582,13 @@ namespace stdexec { using __when_all::when_all_t; inline constexpr when_all_t when_all{}; + using __when_all::when_all_with_variant_t; inline constexpr when_all_with_variant_t when_all_with_variant{}; + using __when_all::transfer_when_all_t; inline constexpr transfer_when_all_t transfer_when_all{}; + using __when_all::transfer_when_all_with_variant_t; inline constexpr transfer_when_all_with_variant_t transfer_when_all_with_variant{}; @@ -6848,7 +6859,7 @@ namespace stdexec { template static auto __mkenv(_Scheduler __sched) { - auto __env = __join_env(__mkprop(get_scheduler, __sched), __mkprop(get_domain)); + auto __env = __join_env(__mkprop(__sched, get_scheduler), __mkprop(get_domain)); using _Env = decltype(__env); struct __env_t : _Env { }; diff --git a/test/stdexec/algos/adaptors/test_transfer_when_all.cpp b/test/stdexec/algos/adaptors/test_transfer_when_all.cpp index c52ba3dcb..3378c9a26 100644 --- a/test/stdexec/algos/adaptors/test_transfer_when_all.cpp +++ b/test/stdexec/algos/adaptors/test_transfer_when_all.cpp @@ -140,3 +140,162 @@ TEST_CASE("transfer_when_all_with_variant can be customized", "[adaptors][transf ); wait_for_value(std::move(snd), std::string{"first program"}); } + +namespace { + enum customize : std::size_t { + early, + late, + none + }; + + template + struct basic_domain { + template Sender, class... Env> + requires(sizeof...(Env) == C) + auto transform_sender(Sender&& sender, const Env&...) const { + return Fun(); + } + }; +} // anonymous namespace + +TEST_CASE("transfer_when_all works with custom domain", "[adaptors][transfer_when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all( // + scheduler(), + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } + + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all( // + scheduler(), // + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } + + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::transfer_when_all( // + inline_scheduler(), // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } +} + +TEST_CASE( + "transfer_when_all_with_variant works with custom domain", + "[adaptors][transfer_when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all_with_variant( // + scheduler(), // + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } + + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all_with_variant( // + scheduler(), + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } + + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::transfer_when_all_with_variant( // + inline_scheduler(), // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } +} + +TEST_CASE( + "transfer_when_all_with_variant finds transfer_when_all customizations", + "[adaptors][transfer_when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all_with_variant( // + scheduler(), // + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } + + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all_with_variant( // + scheduler(), // + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } + + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::transfer_when_all_with_variant( // + inline_scheduler(), // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } +} diff --git a/test/stdexec/algos/adaptors/test_when_all.cpp b/test/stdexec/algos/adaptors/test_when_all.cpp index 5fdb2a83a..5edd31585 100644 --- a/test/stdexec/algos/adaptors/test_when_all.cpp +++ b/test/stdexec/algos/adaptors/test_when_all.cpp @@ -359,10 +359,10 @@ TEST_CASE("when_all_with_variant can be customized", "[adaptors][when_all]") { wait_for_value(std::move(snd), std::string{"first program"}); } -using my_string_variant_sender_t = decltype(ex::into_variant(my_string_sender_t{std::string{}})); - -// There is no way for ADL to find this overload. This test is broken and needs to be rewritten -// using a custom domain. +// There is no way for ADL to find the following overload. This test is broken and needs to be +// rewritten using a custom domain. +// +// using my_string_variant_sender_t = decltype(ex::into_variant(my_string_sender_t{std::string{}})); // // auto tag_invoke(ex::when_all_t, my_string_variant_sender_t, my_string_variant_sender_t) { // // Return a different sender when we invoke this custom defined on implementation @@ -384,3 +384,149 @@ using my_string_variant_sender_t = decltype(ex::into_variant(my_string_sender_t{ TEST_CASE("when_all returns empty env", "[adaptors][when_all]") { check_env_type(ex::when_all(ex::just(), ex::just())); } + +namespace { + enum customize : std::size_t { + early, + late, + none + }; + + template + struct basic_domain { + template Sender, class... Env> + requires(sizeof...(Env) == C) + auto transform_sender(Sender&& sender, const Env&...) const { + return Fun(); + } + }; +} // anonymous namespace + +TEST_CASE("when_all works with custom domain", "[adaptors][when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::when_all( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } + + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::when_all( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } + + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::when_all( // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } +} + +TEST_CASE("when_all_with_variant works with custom domain", "[adaptors][when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::when_all_with_variant( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } + + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::when_all_with_variant( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } + + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::when_all_with_variant( // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } +} + +TEST_CASE("when_all_with_variant finds when_all customizations", "[adaptors][when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::when_all_with_variant( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } + + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::when_all_with_variant( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } + + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::when_all_with_variant( // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } +} From b4f6661d82656ee12aae3b3e318470d9db8a7877 Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Sun, 15 Oct 2023 14:32:29 -0700 Subject: [PATCH 041/146] remove unused concept --- include/stdexec/execution.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index fd8cf2a31..121399ca5 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -2090,10 +2090,6 @@ namespace stdexec { __qf<__tag_of_sig_t<_SetSig>>, __q<__types>>>; - template - concept __tag_invocable_with_domain = // - tag_invocable<_Fun, __sender_domain_of_t<_Sender, _CPO>, _Sender, _As...>; - #if !STDEXEC_STD_NO_COROUTINES_ ///////////////////////////////////////////////////////////////////////////// // stdexec::as_awaitable [execution.coro_utils.as_awaitable] From ffbf23d328fff0b814761d58b7823415e90f651e Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Wed, 18 Oct 2023 14:19:34 -0700 Subject: [PATCH 042/146] express __get_sender_domain in terms of __get_env_domain --- include/stdexec/execution.hpp | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/include/stdexec/execution.hpp b/include/stdexec/execution.hpp index 121399ca5..af1031009 100644 --- a/include/stdexec/execution.hpp +++ b/include/stdexec/execution.hpp @@ -628,29 +628,12 @@ namespace stdexec { { get_env(std::as_const(__ep)) } -> __none_of; }; - ///////////////////////////////////////////////////////////////////////////// - inline constexpr struct __get_sender_domain_t { - template - auto operator()(const _Sender& __sndr, _Tag = {}) const noexcept { - // Find the sender's completion scheduler, or empty_env if it doesn't have one. - auto __sched = query_or(get_completion_scheduler<_Tag>, get_env(__sndr), empty_env()); - - // Find the domain by first asking the sender's env, then the completion scheduler, - // and finally using the default domain (which wraps the completion scheduler) - auto __env = __join_env(get_env(__sndr), __sched); - return query_or(get_domain, __env, default_domain()); - } - } __get_sender_domain{}; - - template - using __sender_domain_of_t = __call_result_t<__get_sender_domain_t, _Sender, _Tag>; - ///////////////////////////////////////////////////////////////////////////// inline constexpr struct __get_env_domain_t { - template - auto operator()(const _Env& __env) const noexcept { + template + auto operator()(const _Env& __env, _Tag __tag = {}) const noexcept { // Find the current scheduler, or empty_env if there isn't one. - auto __sched = query_or(get_scheduler, __env, empty_env()); + auto __sched = query_or(__tag, __env, empty_env()); // Find the domain by first asking the env, then the current scheduler, // and finally using the default domain if all else fails. @@ -662,6 +645,17 @@ namespace stdexec { template using __env_domain_of_t = __call_result_t<__get_env_domain_t, _Env>; + ///////////////////////////////////////////////////////////////////////////// + inline constexpr struct __get_sender_domain_t { + template + auto operator()(const _Sender& __sndr, _Tag = {}) const noexcept { + return __get_env_domain(get_env(__sndr), get_completion_scheduler<_Tag>); + } + } __get_sender_domain{}; + + template + using __sender_domain_of_t = __call_result_t<__get_sender_domain_t, _Sender, _Tag>; + ///////////////////////////////////////////////////////////////////////////// // [execution.receivers] namespace __receivers { From 5949e66db5bfe80970f5bedf1f1a55260c4f9e6a Mon Sep 17 00:00:00 2001 From: Maikel Nadolski Date: Thu, 19 Oct 2023 14:49:11 +0200 Subject: [PATCH 043/146] Update io_uring example for the new interface --- examples/CMakeLists.txt | 2 +- examples/io_uring.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 4076cf8a8..c3cc8852f 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -47,7 +47,7 @@ set(stdexec_examples ) if (LINUX) - set(stdexect_examples ${stdexec_examples} + set(stdexec_examples ${stdexec_examples} "example.io_uring : io_uring.cpp" ) endif (LINUX) diff --git a/examples/io_uring.cpp b/examples/io_uring.cpp index eb837b6cb..085f29e7f 100644 --- a/examples/io_uring.cpp +++ b/examples/io_uring.cpp @@ -31,10 +31,10 @@ int main() { exec::io_uring_context context; exec::io_uring_context context2; std::thread io_thread{[&] { - context.run(); + context.run_until_stopped(); }}; std::thread io_thread2{[&] { - context2.run(); + context2.run_until_stopped(); }}; auto scheduler = context.get_scheduler(); auto scheduler2 = context2.get_scheduler(); @@ -87,8 +87,9 @@ int main() { | stdexec::then([] { std::cout << "This should not print, because the context is stopped.\n"; }) | stdexec::upon_stopped([] { std::cout << "The context is stopped!\n"; })); + context.reset(); io_thread = std::thread{[&] { - context.run(); + context.run_until_stopped(); }}; while (!context.is_running()) From 1782121c7d295a4e3db857271a4afb6d26734527 Mon Sep 17 00:00:00 2001 From: Maikel Nadolski Date: Thu, 19 Oct 2023 15:01:04 +0200 Subject: [PATCH 044/146] Retry __io_uring_enter on EINTR --- include/exec/linux/io_uring_context.hpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/include/exec/linux/io_uring_context.hpp b/include/exec/linux/io_uring_context.hpp index cc50cd673..6e0b95824 100644 --- a/include/exec/linux/io_uring_context.hpp +++ b/include/exec/linux/io_uring_context.hpp @@ -264,8 +264,7 @@ namespace exec { // This function first completes all tasks that are ready in the completion queue of the io_uring. // Then it completes all tasks that are ready in the given queue of ready tasks. // The function returns the number of previously submitted completed tasks. - int - complete(stdexec::__intrusive_queue<& __task::__next_> __ready = __task_queue{}) noexcept { + int complete(stdexec::__intrusive_queue<&__task::__next_> __ready = __task_queue{}) noexcept { __u32 __head = __head_.load(std::memory_order_relaxed); __u32 __tail = __tail_.load(std::memory_order_acquire); int __count = 0; @@ -481,9 +480,11 @@ namespace exec { && __n_total_submitted_ <= static_cast(__params_.cq_entries)); int rc = __io_uring_enter( __ring_fd_, __n_newly_submitted_, __min_complete, IORING_ENTER_GETEVENTS); - __throw_error_code_if(rc < 0, -rc); - STDEXEC_ASSERT(rc <= __n_newly_submitted_); - __n_newly_submitted_ -= rc; + __throw_error_code_if(rc < 0 && rc != -EINTR, -rc); + if (rc != -EINTR) { + STDEXEC_ASSERT(rc <= __n_newly_submitted_); + __n_newly_submitted_ -= rc; + } __n_total_submitted_ -= __completion_queue_.complete(); STDEXEC_ASSERT(0 <= __n_total_submitted_); __pending_.append(__requests_.pop_all()); From 7d122146b3d90bee1ee2d8f9313123afaec93f03 Mon Sep 17 00:00:00 2001 From: Maikel Nadolski Date: Thu, 19 Oct 2023 17:39:13 +0200 Subject: [PATCH 045/146] Fix hanging example --- examples/io_uring.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples/io_uring.cpp b/examples/io_uring.cpp index 085f29e7f..286290dc6 100644 --- a/examples/io_uring.cpp +++ b/examples/io_uring.cpp @@ -56,10 +56,14 @@ int main() { exec::schedule_after(scheduler, 1s) | stdexec::then([] { std::cout << "Hello, 1!\n"; }), exec::schedule_after(scheduler2, 2s) | stdexec::then([] { std::cout << "Hello, 2!\n"; }), exec::schedule_after(scheduler, 3s) | stdexec::then([] { std::cout << "Stop it!\n"; }), - exec::schedule_after(scheduler2, 4s) | stdexec::then([&] { context.request_stop(); }), + exec::finally(exec::schedule_after(scheduler2, 4s), + stdexec::just() | stdexec::then([&] { + context.request_stop(); })), exec::finally( exec::schedule_after(scheduler, 4s), - stdexec::just() | stdexec::then([&] { context2.request_stop(); })), + stdexec::just() | stdexec::then([&] { + context2.request_stop(); + })), exec::schedule_after(scheduler, 10s) // | stdexec::then([] { // std::cout << "Hello, world!\n"; // From 5d6b265c3b7ebcf49a725ce17c399b1e9666118d Mon Sep 17 00:00:00 2001 From: Eric Niebler Date: Thu, 19 Oct 2023 18:28:20 -0700 Subject: [PATCH 046/146] put all tests in anonymous namespaces to avoid ODR violations --- test/exec/async_scope/test_dtor.cpp | 35 +- test/exec/async_scope/test_empty.cpp | 313 ++--- test/exec/async_scope/test_spawn.cpp | 383 +++--- test/exec/async_scope/test_spawn_future.cpp | 786 +++++------ test/exec/async_scope/test_stop.cpp | 85 +- test/exec/sequence/test_any_sequence_of.cpp | 204 +-- test/exec/sequence/test_empty_sequence.cpp | 76 +- test/exec/sequence/test_ignore_all_values.cpp | 150 +- test/exec/sequence/test_iterate.cpp | 200 +-- test/exec/sequence/test_transform_each.cpp | 159 +-- test/exec/test_any_sender.cpp | 1222 +++++++++-------- test/exec/test_at_coroutine_exit.cpp | 331 ++--- test/exec/test_create.cpp | 72 +- test/exec/test_env.cpp | 26 +- test/exec/test_finally.cpp | 111 +- test/exec/test_io_uring_context.cpp | 527 +++---- test/exec/test_materialize.cpp | 104 +- test/exec/test_on.cpp | 344 ++--- test/exec/test_on2.cpp | 695 +++++----- test/exec/test_on3.cpp | 67 +- test/exec/test_repeat_effect_until.cpp | 224 +-- test/exec/test_sequence_senders.cpp | 201 +-- test/exec/test_task.cpp | 299 ++-- test/exec/test_trampoline_scheduler.cpp | 89 +- test/exec/test_type_async_scope.cpp | 112 +- test/exec/test_variant_sender.cpp | 77 +- test/exec/test_when_any.cpp | 379 ++--- test/nvexec/bulk.cpp | 253 ++-- test/nvexec/common.cuh | 517 +++---- test/nvexec/ensure_started.cpp | 145 +- test/nvexec/launch.cpp | 4 +- test/nvexec/let_error.cpp | 149 +- test/nvexec/let_stopped.cpp | 145 +- test/nvexec/let_value.cpp | 243 ++-- test/nvexec/monotonic_buffer.cpp | 135 +- test/nvexec/reduce.cpp | 119 +- test/nvexec/split.cpp | 125 +- test/nvexec/start_detached.cpp | 73 +- test/nvexec/synchronized_pool.cpp | 115 +- test/nvexec/then.cpp | 427 +++--- test/nvexec/tracer_resource.h | 65 +- test/nvexec/transfer.cpp | 249 ++-- test/nvexec/transfer_when_all.cpp | 182 +-- test/nvexec/upon_error.cpp | 139 +- test/nvexec/upon_stopped.cpp | 41 +- test/nvexec/variant.cpp | 229 +-- test/nvexec/when_all.cpp | 149 +- test/stdexec/algos/adaptors/test_bulk.cpp | 465 +++---- .../algos/adaptors/test_ensure_started.cpp | 513 +++---- .../algos/adaptors/test_into_variant.cpp | 237 ++-- .../stdexec/algos/adaptors/test_let_error.cpp | 583 ++++---- .../algos/adaptors/test_let_stopped.cpp | 401 +++--- .../stdexec/algos/adaptors/test_let_value.cpp | 513 +++---- test/stdexec/algos/adaptors/test_on.cpp | 413 +++--- .../algos/adaptors/test_schedule_from.cpp | 443 +++--- test/stdexec/algos/adaptors/test_split.cpp | 824 +++++------ .../algos/adaptors/test_stopped_as_error.cpp | 247 ++-- .../adaptors/test_stopped_as_optional.cpp | 193 +-- test/stdexec/algos/adaptors/test_then.cpp | 326 ++--- test/stdexec/algos/adaptors/test_transfer.cpp | 464 +++---- .../algos/adaptors/test_transfer_when_all.cpp | 488 +++---- .../algos/adaptors/test_upon_error.cpp | 223 +-- .../algos/adaptors/test_upon_stopped.cpp | 47 +- test/stdexec/algos/adaptors/test_when_all.cpp | 895 ++++++------ .../algos/consumers/test_start_detached.cpp | 212 +-- .../algos/consumers/test_sync_wait.cpp | 512 +++---- test/stdexec/algos/factories/test_just.cpp | 197 +-- .../algos/factories/test_just_error.cpp | 69 +- .../algos/factories/test_just_stopped.cpp | 49 +- test/stdexec/algos/factories/test_read.cpp | 7 +- .../algos/factories/test_transfer_just.cpp | 345 ++--- test/stdexec/algos/other/test_execute.cpp | 65 +- test/stdexec/concepts/test_awaitables.cpp | 371 ++--- .../concepts/test_concept_operation_state.cpp | 60 +- .../concepts/test_concept_scheduler.cpp | 266 ++-- .../stdexec/concepts/test_concepts_sender.cpp | 440 +++--- test/stdexec/cpos/cpo_helpers.cuh | 135 +- test/stdexec/cpos/test_cpo_bulk.cpp | 49 +- test/stdexec/cpos/test_cpo_connect.cpp | 170 +-- test/stdexec/cpos/test_cpo_ensure_started.cpp | 23 +- test/stdexec/cpos/test_cpo_receiver.cpp | 399 +++--- test/stdexec/cpos/test_cpo_schedule.cpp | 59 +- test/stdexec/cpos/test_cpo_split.cpp | 43 +- test/stdexec/cpos/test_cpo_start.cpp | 132 +- test/stdexec/cpos/test_cpo_upon_error.cpp | 49 +- test/stdexec/cpos/test_cpo_upon_stopped.cpp | 49 +- .../detail/test_completion_signatures.cpp | 586 ++++---- test/stdexec/detail/test_utility.cpp | 63 +- .../queries/test_forwarding_queries.cpp | 31 +- .../test_get_forward_progress_guarantee.cpp | 38 +- test/tbbexec/test_tbb_thread_pool.cpp | 183 +-- test/test_common/receivers.hpp | 843 ++++++------ test/test_common/retry.hpp | 211 +-- test/test_common/schedulers.hpp | 463 +++---- test/test_common/senders.hpp | 235 ++-- test/test_common/type_helpers.hpp | 149 +- 96 files changed, 12484 insertions(+), 12069 deletions(-) diff --git a/test/exec/async_scope/test_dtor.cpp b/test/exec/async_scope/test_dtor.cpp index a36ede392..283481702 100644 --- a/test/exec/async_scope/test_dtor.cpp +++ b/test/exec/async_scope/test_dtor.cpp @@ -6,25 +6,28 @@ namespace ex = stdexec; using exec::async_scope; using stdexec::sync_wait; -TEST_CASE("async_scope can be created and them immediately destructed", "[async_scope][dtor]") { - async_scope scope; - (void) scope; -} +namespace { -TEST_CASE("async_scope destruction after spawning work into it", "[async_scope][dtor]") { - exec::static_thread_pool pool{4}; - ex::scheduler auto sch = pool.get_scheduler(); - std::atomic counter{0}; - { + TEST_CASE("async_scope can be created and them immediately destructed", "[async_scope][dtor]") { async_scope scope; + (void) scope; + } + + TEST_CASE("async_scope destruction after spawning work into it", "[async_scope][dtor]") { + exec::static_thread_pool pool{4}; + ex::scheduler auto sch = pool.get_scheduler(); + std::atomic counter{0}; + { + async_scope scope; - // Add some work into the scope - for (int i = 0; i < 10; i++) - scope.spawn(ex::on(sch, ex::just() | ex::then([&] { counter++; }))); + // Add some work into the scope + for (int i = 0; i < 10; i++) + scope.spawn(ex::on(sch, ex::just() | ex::then([&] { counter++; }))); - // Wait on the work, before calling destructor - sync_wait(scope.on_empty()); + // Wait on the work, before calling destructor + sync_wait(scope.on_empty()); + } + // We should have all the work executed + REQUIRE(counter == 10); } - // We should have all the work executed - REQUIRE(counter == 10); } diff --git a/test/exec/async_scope/test_empty.cpp b/test/exec/async_scope/test_empty.cpp index 525156bdb..ab36a7db4 100644 --- a/test/exec/async_scope/test_empty.cpp +++ b/test/exec/async_scope/test_empty.cpp @@ -7,166 +7,169 @@ namespace ex = stdexec; using exec::async_scope; using stdexec::sync_wait; -TEST_CASE("empty will complete immediately on an empty async_scope", "[async_scope][empty]") { - async_scope scope; - bool is_empty{false}; - - ex::sender auto snd = scope.on_empty() | ex::then([&] { is_empty = true; }); - sync_wait(std::move(snd)); - REQUIRE(is_empty); -} - -TEST_CASE("empty sender can properly connect a void receiver", "[async_scope][empty]") { - async_scope scope; - bool is_empty{false}; - - scope.spawn(ex::just()); - - ex::sender auto snd = scope.on_empty() | ex::then([&] { is_empty = true; }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); - REQUIRE(is_empty); -} - -TEST_CASE("empty will complete after the work is done", "[async_scope][empty]") { - impulse_scheduler sch; - async_scope scope; - - // Add some work - scope.spawn(ex::on(sch, ex::just())); - - // The on_empty() sender cannot notify now - bool is_empty{false}; - ex::sender auto snd = ex::on(sch, scope.on_empty()) | ex::then([&] { is_empty = true; }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); - REQUIRE_FALSE(is_empty); - - // TODO: refactor this test - sch.start_next(); - sch.start_next(); - // We should be notified now - REQUIRE(is_empty); -} - -TEST_CASE("TODO: empty can be used multiple times", "[async_scope][empty]") { - impulse_scheduler sch; - async_scope scope; - - // Add some work - scope.spawn(ex::on(sch, ex::just())); - - // The on_empty() sender cannot notify now - bool is_empty{false}; - ex::sender auto snd = ex::on(sch, scope.on_empty()) | ex::then([&] { is_empty = true; }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); - REQUIRE_FALSE(is_empty); - - // TODO: refactor this test - sch.start_next(); - sch.start_next(); - // We should be notified now - REQUIRE(is_empty); - - // Add some work - scope.spawn(ex::on(sch, ex::just())); - - // The on_empty() sender cannot notify now - bool is_empty2{false}; - ex::sender auto snd2 = ex::on(sch, scope.on_empty()) | ex::then([&] { is_empty2 = true; }); - auto op2 = ex::connect(std::move(snd2), expect_void_receiver{}); - ex::start(op2); - REQUIRE_FALSE(is_empty2); - - // TODO: refactor this test - sch.start_next(); - sch.start_next(); - // We should be notified now - REQUIRE(is_empty2); -} +namespace { + + TEST_CASE("empty will complete immediately on an empty async_scope", "[async_scope][empty]") { + async_scope scope; + bool is_empty{false}; + + ex::sender auto snd = scope.on_empty() | ex::then([&] { is_empty = true; }); + sync_wait(std::move(snd)); + REQUIRE(is_empty); + } + + TEST_CASE("empty sender can properly connect a void receiver", "[async_scope][empty]") { + async_scope scope; + bool is_empty{false}; + + scope.spawn(ex::just()); + + ex::sender auto snd = scope.on_empty() | ex::then([&] { is_empty = true; }); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + REQUIRE(is_empty); + } + + TEST_CASE("empty will complete after the work is done", "[async_scope][empty]") { + impulse_scheduler sch; + async_scope scope; + + // Add some work + scope.spawn(ex::on(sch, ex::just())); + + // The on_empty() sender cannot notify now + bool is_empty{false}; + ex::sender auto snd = ex::on(sch, scope.on_empty()) | ex::then([&] { is_empty = true; }); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + REQUIRE_FALSE(is_empty); + + // TODO: refactor this test + sch.start_next(); + sch.start_next(); + // We should be notified now + REQUIRE(is_empty); + } + + TEST_CASE("TODO: empty can be used multiple times", "[async_scope][empty]") { + impulse_scheduler sch; + async_scope scope; + + // Add some work + scope.spawn(ex::on(sch, ex::just())); + + // The on_empty() sender cannot notify now + bool is_empty{false}; + ex::sender auto snd = ex::on(sch, scope.on_empty()) | ex::then([&] { is_empty = true; }); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + REQUIRE_FALSE(is_empty); + + // TODO: refactor this test + sch.start_next(); + sch.start_next(); + // We should be notified now + REQUIRE(is_empty); + + // Add some work + scope.spawn(ex::on(sch, ex::just())); + + // The on_empty() sender cannot notify now + bool is_empty2{false}; + ex::sender auto snd2 = ex::on(sch, scope.on_empty()) | ex::then([&] { is_empty2 = true; }); + auto op2 = ex::connect(std::move(snd2), expect_void_receiver{}); + ex::start(op2); + REQUIRE_FALSE(is_empty2); + + // TODO: refactor this test + sch.start_next(); + sch.start_next(); + // We should be notified now + REQUIRE(is_empty2); + } // TODO: GCC-11 generates warnings (treated as errors) for the following test #if defined(__clang__) || !defined(__GNUC__) -TEST_CASE("waiting on work that spawns more work", "[async_scope][empty]") { - impulse_scheduler sch; - async_scope scope; - - bool work1_done{false}; - auto work1 = [&] { - work1_done = true; - }; - bool work2_done{false}; - auto work2 = [&] { - // Spawn work - scope.spawn(ex::on(sch, ex::just() | ex::then(work1))); - // We are done - work2_done = true; - }; - - // Spawn work 2 - // No work is executed until the impulse scheduler dictates - scope.spawn(ex::on(sch, ex::just() | ex::then(work2))); - - // start an on_empty() sender - bool is_empty{false}; - ex::sender auto snd = ex::on(inline_scheduler{}, scope.on_empty()) // - | ex::then([&] { is_empty = true; }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); - REQUIRE_FALSE(work1_done); - REQUIRE_FALSE(work2_done); - REQUIRE_FALSE(is_empty); - - // Trigger the execution of work2 - // When work2 is done, work1 is not yet started - sch.start_next(); - REQUIRE_FALSE(work1_done); - REQUIRE(work2_done); - REQUIRE_FALSE(is_empty); - - // Trigger the execution of work1 - // This will complete the on_empty() sender - sch.start_next(); - REQUIRE(work1_done); - REQUIRE(work2_done); - REQUIRE(is_empty); -} + TEST_CASE("waiting on work that spawns more work", "[async_scope][empty]") { + impulse_scheduler sch; + async_scope scope; + + bool work1_done{false}; + auto work1 = [&] { + work1_done = true; + }; + bool work2_done{false}; + auto work2 = [&] { + // Spawn work + scope.spawn(ex::on(sch, ex::just() | ex::then(work1))); + // We are done + work2_done = true; + }; + + // Spawn work 2 + // No work is executed until the impulse scheduler dictates + scope.spawn(ex::on(sch, ex::just() | ex::then(work2))); + + // start an on_empty() sender + bool is_empty{false}; + ex::sender auto snd = ex::on(inline_scheduler{}, scope.on_empty()) // + | ex::then([&] { is_empty = true; }); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + REQUIRE_FALSE(work1_done); + REQUIRE_FALSE(work2_done); + REQUIRE_FALSE(is_empty); + + // Trigger the execution of work2 + // When work2 is done, work1 is not yet started + sch.start_next(); + REQUIRE_FALSE(work1_done); + REQUIRE(work2_done); + REQUIRE_FALSE(is_empty); + + // Trigger the execution of work1 + // This will complete the on_empty() sender + sch.start_next(); + REQUIRE(work1_done); + REQUIRE(work2_done); + REQUIRE(is_empty); + } #endif // TODO: GCC-11 generates warnings (treated as errors) for the following test #if defined(__clang__) || !defined(__GNUC__) -TEST_CASE( - "async_scope is empty after adding work when in cancelled state", - "[async_scope][empty]") { - impulse_scheduler sch; - async_scope scope; - - bool is_empty1{false}; - ex::sender auto snd = ex::on(inline_scheduler{}, scope.on_empty()) // - | ex::then([&] { is_empty1 = true; }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); - REQUIRE(is_empty1); - - // cancel & add work - scope.request_stop(); - bool work_executed{false}; - scope.spawn( - ex::on(sch, ex::just()) // - | ex::upon_stopped([&] { work_executed = true; })); - // note that we don't tell impulse sender to start the work - - bool is_empty2{false}; - ex::sender auto snd2 = ex::on(inline_scheduler{}, scope.on_empty()) // - | ex::then([&] { is_empty2 = true; }); - auto op2 = ex::connect(std::move(snd2), expect_void_receiver{}); - ex::start(op2); - REQUIRE_FALSE(is_empty2); - - REQUIRE_FALSE(work_executed); - sch.start_next(); - REQUIRE(work_executed); - REQUIRE(is_empty2); -} + TEST_CASE( + "async_scope is empty after adding work when in cancelled state", + "[async_scope][empty]") { + impulse_scheduler sch; + async_scope scope; + + bool is_empty1{false}; + ex::sender auto snd = ex::on(inline_scheduler{}, scope.on_empty()) // + | ex::then([&] { is_empty1 = true; }); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + REQUIRE(is_empty1); + + // cancel & add work + scope.request_stop(); + bool work_executed{false}; + scope.spawn( + ex::on(sch, ex::just()) // + | ex::upon_stopped([&] { work_executed = true; })); + // note that we don't tell impulse sender to start the work + + bool is_empty2{false}; + ex::sender auto snd2 = ex::on(inline_scheduler{}, scope.on_empty()) // + | ex::then([&] { is_empty2 = true; }); + auto op2 = ex::connect(std::move(snd2), expect_void_receiver{}); + ex::start(op2); + REQUIRE_FALSE(is_empty2); + + REQUIRE_FALSE(work_executed); + sch.start_next(); + REQUIRE(work_executed); + REQUIRE(is_empty2); + } #endif +} diff --git a/test/exec/async_scope/test_spawn.cpp b/test/exec/async_scope/test_spawn.cpp index 77ee56bf9..00c0d98f9 100644 --- a/test/exec/async_scope/test_spawn.cpp +++ b/test/exec/async_scope/test_spawn.cpp @@ -8,226 +8,229 @@ namespace ex = stdexec; using exec::async_scope; using stdexec::sync_wait; -//! Sender that throws exception when connected -struct throwing_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures; - - template - struct operation { - Receiver rcvr_; +namespace { + + //! Sender that throws exception when connected + struct throwing_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures; + + template + struct operation { + Receiver rcvr_; + + friend void tag_invoke(ex::start_t, operation& self) noexcept { + ex::set_value(std::move(self.rcvr_)); + } + }; + + template + friend auto tag_invoke(ex::connect_t, throwing_sender&& self, Receiver&& rcvr) + -> operation> { + throw std::logic_error("cannot connect"); + return {std::forward(rcvr)}; + } - friend void tag_invoke(ex::start_t, operation& self) noexcept { - ex::set_value(std::move(self.rcvr_)); + friend empty_env tag_invoke(stdexec::get_env_t, const throwing_sender&) noexcept { + return {}; } }; - template - friend auto tag_invoke(ex::connect_t, throwing_sender&& self, Receiver&& rcvr) - -> operation> { - throw std::logic_error("cannot connect"); - return {std::forward(rcvr)}; - } + TEST_CASE("spawn will execute its work", "[async_scope][spawn]") { + impulse_scheduler sch; + bool executed{false}; + async_scope scope; - friend empty_env tag_invoke(stdexec::get_env_t, const throwing_sender&) noexcept { - return {}; + // Non-blocking call + scope.spawn(ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); + REQUIRE_FALSE(executed); + // Run the operation on the scheduler + sch.start_next(); + // Now the spawn work should be completed + REQUIRE(executed); } -}; - -TEST_CASE("spawn will execute its work", "[async_scope][spawn]") { - impulse_scheduler sch; - bool executed{false}; - async_scope scope; - - // Non-blocking call - scope.spawn(ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); - REQUIRE_FALSE(executed); - // Run the operation on the scheduler - sch.start_next(); - // Now the spawn work should be completed - REQUIRE(executed); -} -TEST_CASE("spawn will start sender before returning", "[async_scope][spawn]") { - bool executed{false}; - async_scope scope; + TEST_CASE("spawn will start sender before returning", "[async_scope][spawn]") { + bool executed{false}; + async_scope scope; - // This will be a blocking call - scope.spawn(ex::just() | ex::then([&] { executed = true; })); - REQUIRE(executed); -} + // This will be a blocking call + scope.spawn(ex::just() | ex::then([&] { executed = true; })); + REQUIRE(executed); + } #if !NO_TESTS_WITH_EXCEPTIONS -TEST_CASE( - "spawn will propagate exceptions encountered during op creation", - "[async_scope][spawn]") { - async_scope scope; - try { - scope.spawn(throwing_sender{} | ex::then([&] { FAIL("work should not be executed"); })); - FAIL("Exceptions should have been thrown"); - } catch (const std::logic_error& e) { - SUCCEED("correct exception caught"); - } catch (...) { - FAIL("invalid exception caught"); + TEST_CASE( + "spawn will propagate exceptions encountered during op creation", + "[async_scope][spawn]") { + async_scope scope; + try { + scope.spawn(throwing_sender{} | ex::then([&] { FAIL("work should not be executed"); })); + FAIL("Exceptions should have been thrown"); + } catch (const std::logic_error& e) { + SUCCEED("correct exception caught"); + } catch (...) { + FAIL("invalid exception caught"); + } } -} #endif -TEST_CASE( - "TODO: spawn will keep the scope non-empty until the work is executed", - "[async_scope][spawn]") { - impulse_scheduler sch; - bool executed{false}; - async_scope scope; - - // Before adding any operations, the scope is empty - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); - - // Non-blocking call - scope.spawn(ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); - REQUIRE_FALSE(executed); - - // The scope is now non-empty - // TODO: reenable this - // REQUIRE_FALSE(P2519::__scope::empty(scope)); - // REQUIRE(P2519::__scope::op_count(scope) == 1); - - // Run the operation on the scheduler; blocking call - sch.start_next(); - - // Now the scope should again be empty - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); - REQUIRE(executed); -} + TEST_CASE( + "TODO: spawn will keep the scope non-empty until the work is executed", + "[async_scope][spawn]") { + impulse_scheduler sch; + bool executed{false}; + async_scope scope; -TEST_CASE( - "TODO: spawn will keep track on how many operations are in flight", - "[async_scope][spawn]") { - impulse_scheduler sch; - std::size_t num_executed{0}; - async_scope scope; - - // Before adding any operations, the scope is empty - // TODO: reenable this - // REQUIRE(P2519::__scope::op_count(scope) == 0); - // REQUIRE(P2519::__scope::empty(scope)); - - constexpr std::size_t num_oper = 10; - for (std::size_t i = 0; i < num_oper; i++) { - scope.spawn(ex::on(sch, ex::just() | ex::then([&] { num_executed++; }))); - size_t num_expected_ops = i + 1; + // Before adding any operations, the scope is empty // TODO: reenable this - // REQUIRE(P2519::__scope::op_count(scope) == num_expected_ops); - (void) num_expected_ops; - } + // REQUIRE(P2519::__scope::empty(scope)); + + // Non-blocking call + scope.spawn(ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); + REQUIRE_FALSE(executed); - // Now execute the operations - for (std::size_t i = 0; i < num_oper; i++) { + // The scope is now non-empty + // TODO: reenable this + // REQUIRE_FALSE(P2519::__scope::empty(scope)); + // REQUIRE(P2519::__scope::op_count(scope) == 1); + + // Run the operation on the scheduler; blocking call sch.start_next(); - size_t num_expected_ops = num_oper - i - 1; + + // Now the scope should again be empty // TODO: reenable this - // REQUIRE(P2519::__scope::op_count(scope) == num_expected_ops); - (void) num_expected_ops; + // REQUIRE(P2519::__scope::empty(scope)); + REQUIRE(executed); } - // The scope is empty after all the operations are executed - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); - REQUIRE(num_executed == num_oper); -} + TEST_CASE( + "TODO: spawn will keep track on how many operations are in flight", + "[async_scope][spawn]") { + impulse_scheduler sch; + std::size_t num_executed{0}; + async_scope scope; -TEST_CASE("TODO: spawn work can be cancelled by cancelling the scope", "[async_scope][spawn]") { - impulse_scheduler sch; - async_scope scope; - - bool cancelled1{false}; - bool cancelled2{false}; - - scope.spawn(ex::on( - sch, - ex::just() // - | ex::let_stopped([&] { - cancelled1 = true; - return ex::just(); - }))); - scope.spawn(ex::on( - sch, - ex::just() // - | ex::let_stopped([&] { - cancelled2 = true; - return ex::just(); - }))); - - // TODO: reenable this - // REQUIRE(P2519::__scope::op_count(scope) == 2); - - // Execute the first operation, before cancelling - sch.start_next(); - REQUIRE_FALSE(cancelled1); - REQUIRE_FALSE(cancelled2); - - // Cancel the async_scope object - scope.request_stop(); - // TODO: reenable this - // REQUIRE(P2519::__scope::op_count(scope) == 1); - - // Execute the first operation, after cancelling - sch.start_next(); - REQUIRE_FALSE(cancelled1); - // TODO: second operation should be cancelled - // REQUIRE(cancelled2); - REQUIRE_FALSE(cancelled2); - - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); -} + // Before adding any operations, the scope is empty + // TODO: reenable this + // REQUIRE(P2519::__scope::op_count(scope) == 0); + // REQUIRE(P2519::__scope::empty(scope)); + + constexpr std::size_t num_oper = 10; + for (std::size_t i = 0; i < num_oper; i++) { + scope.spawn(ex::on(sch, ex::just() | ex::then([&] { num_executed++; }))); + size_t num_expected_ops = i + 1; + // TODO: reenable this + // REQUIRE(P2519::__scope::op_count(scope) == num_expected_ops); + (void) num_expected_ops; + } -template -concept is_spawn_worthy = requires(async_scope& scope, S&& snd) { scope.spawn(std::move(snd)); }; + // Now execute the operations + for (std::size_t i = 0; i < num_oper; i++) { + sch.start_next(); + size_t num_expected_ops = num_oper - i - 1; + // TODO: reenable this + // REQUIRE(P2519::__scope::op_count(scope) == num_expected_ops); + (void) num_expected_ops; + } -TEST_CASE("spawn accepts void senders", "[async_scope][spawn]") { - static_assert(is_spawn_worthy); -} + // The scope is empty after all the operations are executed + // TODO: reenable this + // REQUIRE(P2519::__scope::empty(scope)); + REQUIRE(num_executed == num_oper); + } -TEST_CASE("spawn doesn't accept non-void senders", "[async_scope][spawn]") { - static_assert(!is_spawn_worthy); - static_assert(!is_spawn_worthy); - static_assert(!is_spawn_worthy); -} + TEST_CASE("TODO: spawn work can be cancelled by cancelling the scope", "[async_scope][spawn]") { + impulse_scheduler sch; + async_scope scope; + + bool cancelled1{false}; + bool cancelled2{false}; + + scope.spawn(ex::on( + sch, + ex::just() // + | ex::let_stopped([&] { + cancelled1 = true; + return ex::just(); + }))); + scope.spawn(ex::on( + sch, + ex::just() // + | ex::let_stopped([&] { + cancelled2 = true; + return ex::just(); + }))); -TEST_CASE("TODO: spawn doesn't accept senders of errors", "[async_scope][spawn]") { - // TODO: check if just_error(exception_ptr) should be allowed - static_assert(is_spawn_worthy); - static_assert(!is_spawn_worthy); - static_assert(!is_spawn_worthy); -} + // TODO: reenable this + // REQUIRE(P2519::__scope::op_count(scope) == 2); -TEST_CASE("spawn should accept senders that send stopped signal", "[async_scope][spawn]") { - static_assert(is_spawn_worthy); -} + // Execute the first operation, before cancelling + sch.start_next(); + REQUIRE_FALSE(cancelled1); + REQUIRE_FALSE(cancelled2); + + // Cancel the async_scope object + scope.request_stop(); + // TODO: reenable this + // REQUIRE(P2519::__scope::op_count(scope) == 1); + + // Execute the first operation, after cancelling + sch.start_next(); + REQUIRE_FALSE(cancelled1); + // TODO: second operation should be cancelled + // REQUIRE(cancelled2); + REQUIRE_FALSE(cancelled2); + + // TODO: reenable this + // REQUIRE(P2519::__scope::empty(scope)); + } + + template + concept is_spawn_worthy = requires(async_scope& scope, S&& snd) { scope.spawn(std::move(snd)); }; + + TEST_CASE("spawn accepts void senders", "[async_scope][spawn]") { + static_assert(is_spawn_worthy); + } + + TEST_CASE("spawn doesn't accept non-void senders", "[async_scope][spawn]") { + static_assert(!is_spawn_worthy); + static_assert(!is_spawn_worthy); + static_assert(!is_spawn_worthy); + } + + TEST_CASE("TODO: spawn doesn't accept senders of errors", "[async_scope][spawn]") { + // TODO: check if just_error(exception_ptr) should be allowed + static_assert(is_spawn_worthy); + static_assert(!is_spawn_worthy); + static_assert(!is_spawn_worthy); + } + + TEST_CASE("spawn should accept senders that send stopped signal", "[async_scope][spawn]") { + static_assert(is_spawn_worthy); + } -TEST_CASE( - "TODO: spawn works with senders that complete with stopped signal", - "[async_scope][spawn]") { - impulse_scheduler sch; - async_scope scope; + TEST_CASE( + "TODO: spawn works with senders that complete with stopped signal", + "[async_scope][spawn]") { + impulse_scheduler sch; + async_scope scope; - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); + // TODO: reenable this + // REQUIRE(P2519::__scope::empty(scope)); - scope.spawn(ex::on(sch, ex::just_stopped())); + scope.spawn(ex::on(sch, ex::just_stopped())); - // The scope is now non-empty - // TODO: reenable this - // REQUIRE_FALSE(P2519::__scope::empty(scope)); - // REQUIRE(P2519::__scope::op_count(scope) == 1); + // The scope is now non-empty + // TODO: reenable this + // REQUIRE_FALSE(P2519::__scope::empty(scope)); + // REQUIRE(P2519::__scope::op_count(scope) == 1); - // Run the operation on the scheduler; blocking call - sch.start_next(); + // Run the operation on the scheduler; blocking call + sch.start_next(); - // Now the scope should again be empty - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); + // Now the scope should again be empty + // TODO: reenable this + // REQUIRE(P2519::__scope::empty(scope)); + } } diff --git a/test/exec/async_scope/test_spawn_future.cpp b/test/exec/async_scope/test_spawn_future.cpp index 9ec9f842b..5aed25b84 100644 --- a/test/exec/async_scope/test_spawn_future.cpp +++ b/test/exec/async_scope/test_spawn_future.cpp @@ -20,423 +20,423 @@ namespace { ex::start(op); loop.run(); } -} // namespace - -//! Sender that throws exception when connected -struct throwing_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures; - template - struct operation { - Receiver rcvr_; + //! Sender that throws exception when connected + struct throwing_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures; + + template + struct operation { + Receiver rcvr_; + + friend void tag_invoke(ex::start_t, operation& self) noexcept { + ex::set_value(std::move(self.rcvr_)); + } + }; + + template + friend auto tag_invoke(ex::connect_t, throwing_sender&& self, Receiver&& rcvr) + -> operation> { + throw std::logic_error("cannot connect"); + return {std::forward(rcvr)}; + } - friend void tag_invoke(ex::start_t, operation& self) noexcept { - ex::set_value(std::move(self.rcvr_)); + friend empty_env tag_invoke(stdexec::get_env_t, const throwing_sender&) noexcept { + return {}; } }; - template - friend auto tag_invoke(ex::connect_t, throwing_sender&& self, Receiver&& rcvr) - -> operation> { - throw std::logic_error("cannot connect"); - return {std::forward(rcvr)}; + TEST_CASE("spawn_future will execute its work", "[async_scope][spawn_future]") { + impulse_scheduler sch; + bool executed{false}; + async_scope scope; + + // Non-blocking call + { + ex::sender auto snd = scope.spawn_future( + ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); + (void) snd; + } + REQUIRE_FALSE(executed); + // Run the operation on the scheduler + sch.start_next(); + // Now the spawn work should be completed + REQUIRE(executed); + expect_empty(scope); } - friend empty_env tag_invoke(stdexec::get_env_t, const throwing_sender&) noexcept { - return {}; + TEST_CASE("spawn_future sender will complete", "[async_scope][spawn_future]") { + impulse_scheduler sch; + bool executed1{false}; + bool executed2{false}; + async_scope scope; + + // Non-blocking call + ex::sender auto snd = scope.spawn_future( + ex::on(sch, ex::just() | ex::then([&] { executed1 = true; }))); + auto op = ex::connect(std::move(snd), expect_void_receiver_ex{executed2}); + ex::start(op); + REQUIRE_FALSE(executed1); + REQUIRE_FALSE(executed2); + // Run the operation on the scheduler + sch.start_next(); + // Now the work from `snd` should be completed + REQUIRE(executed1); + REQUIRE(executed2); + expect_empty(scope); } -}; -TEST_CASE("spawn_future will execute its work", "[async_scope][spawn_future]") { - impulse_scheduler sch; - bool executed{false}; - async_scope scope; + TEST_CASE( + "spawn_future sender will complete after the given sender completes", + "[async_scope][spawn_future]") { + impulse_scheduler sch; + bool executed{false}; + async_scope scope; - // Non-blocking call - { + // Non-blocking call ex::sender auto snd = scope.spawn_future( ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); - (void) snd; + ex::sender auto snd2 = std::move(snd) | ex::then([&] { REQUIRE(executed); }); + // Execute the given work + sch.start_next(); + // Ensure `snd2` is complete + wait_for_value(std::move(snd2)); + expect_empty(scope); + } + + TEST_CASE("spawn_future returned sender can be dropped", "[async_scope][spawn_future]") { + impulse_scheduler sch; + std::atomic_bool executed{false}; + async_scope scope; + + // Non-blocking call; simply ignore the returned sender + (void) scope.spawn_future(ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); + REQUIRE_FALSE(executed.load()); + // Execute the given work + sch.start_next(); + REQUIRE(executed.load()); + expect_empty(scope); } - REQUIRE_FALSE(executed); - // Run the operation on the scheduler - sch.start_next(); - // Now the spawn work should be completed - REQUIRE(executed); - expect_empty(scope); -} - -TEST_CASE("spawn_future sender will complete", "[async_scope][spawn_future]") { - impulse_scheduler sch; - bool executed1{false}; - bool executed2{false}; - async_scope scope; - - // Non-blocking call - ex::sender auto snd = scope.spawn_future( - ex::on(sch, ex::just() | ex::then([&] { executed1 = true; }))); - auto op = ex::connect(std::move(snd), expect_void_receiver_ex{executed2}); - ex::start(op); - REQUIRE_FALSE(executed1); - REQUIRE_FALSE(executed2); - // Run the operation on the scheduler - sch.start_next(); - // Now the work from `snd` should be completed - REQUIRE(executed1); - REQUIRE(executed2); - expect_empty(scope); -} - -TEST_CASE( - "spawn_future sender will complete after the given sender completes", - "[async_scope][spawn_future]") { - impulse_scheduler sch; - bool executed{false}; - async_scope scope; - - // Non-blocking call - ex::sender auto snd = scope.spawn_future( - ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); - ex::sender auto snd2 = std::move(snd) | ex::then([&] { REQUIRE(executed); }); - // Execute the given work - sch.start_next(); - // Ensure `snd2` is complete - wait_for_value(std::move(snd2)); - expect_empty(scope); -} - -TEST_CASE("spawn_future returned sender can be dropped", "[async_scope][spawn_future]") { - impulse_scheduler sch; - std::atomic_bool executed{false}; - async_scope scope; - - // Non-blocking call; simply ignore the returned sender - (void) scope.spawn_future(ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); - REQUIRE_FALSE(executed.load()); - // Execute the given work - sch.start_next(); - REQUIRE(executed.load()); - expect_empty(scope); -} - -TEST_CASE( - "spawn_future returned sender can be captured and dropped", - "[async_scope][spawn_future]") { - impulse_scheduler sch; - bool executed{false}; - async_scope scope; - - // Non-blocking call; simply ignore the returned sender - { + + TEST_CASE( + "spawn_future returned sender can be captured and dropped", + "[async_scope][spawn_future]") { + impulse_scheduler sch; + bool executed{false}; + async_scope scope; + + // Non-blocking call; simply ignore the returned sender + { + ex::sender auto snd = scope.spawn_future( + ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); + (void) snd; + } + REQUIRE_FALSE(executed); + // Execute the given work + sch.start_next(); + REQUIRE(executed); + expect_empty(scope); + } + + TEST_CASE( + "spawn_future returned sender can be connected but not started", + "[async_scope][spawn_future]") { + impulse_scheduler sch; + bool executed{false}; + bool executed2{false}; + async_scope scope; + + // Non-blocking call; simply ignore the returned sender ex::sender auto snd = scope.spawn_future( ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); - (void) snd; + auto op = ex::connect(std::move(snd), expect_void_receiver_ex{executed2}); + REQUIRE_FALSE(executed); + REQUIRE_FALSE(executed2); + // Execute the given work + sch.start_next(); + REQUIRE(executed); + // Our final receiver will not be notified (as `op` was not started) + REQUIRE_FALSE(executed2); + expect_empty(scope); } - REQUIRE_FALSE(executed); - // Execute the given work - sch.start_next(); - REQUIRE(executed); - expect_empty(scope); -} - -TEST_CASE( - "spawn_future returned sender can be connected but not started", - "[async_scope][spawn_future]") { - impulse_scheduler sch; - bool executed{false}; - bool executed2{false}; - async_scope scope; - - // Non-blocking call; simply ignore the returned sender - ex::sender auto snd = scope.spawn_future( - ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); - auto op = ex::connect(std::move(snd), expect_void_receiver_ex{executed2}); - REQUIRE_FALSE(executed); - REQUIRE_FALSE(executed2); - // Execute the given work - sch.start_next(); - REQUIRE(executed); - // Our final receiver will not be notified (as `op` was not started) - REQUIRE_FALSE(executed2); - expect_empty(scope); -} - -TEST_CASE("spawn_future will start sender before returning", "[async_scope][spawn_future]") { - bool executed{false}; - async_scope scope; - - // This will be a blocking call - { - ex::sender auto snd = scope.spawn_future(ex::just() | ex::then([&] { executed = true; })); - (void) snd; + + TEST_CASE("spawn_future will start sender before returning", "[async_scope][spawn_future]") { + bool executed{false}; + async_scope scope; + + // This will be a blocking call + { + ex::sender auto snd = scope.spawn_future(ex::just() | ex::then([&] { executed = true; })); + (void) snd; + } + REQUIRE(executed); + expect_empty(scope); } - REQUIRE(executed); - expect_empty(scope); -} - -TEST_CASE( - "spawn_future returned sender can be started after given sender completed", - "[async_scope][spawn_future]") { - impulse_scheduler sch; - bool executed{false}; - bool executed2{false}; - async_scope scope; - - ex::sender auto snd = scope.spawn_future( - ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); - REQUIRE_FALSE(executed); - // Execute the work given to spawn_future - sch.start_next(); - REQUIRE_FALSE(executed2); - // Now connect the returned sender - auto op = ex::connect(std::move(snd), expect_void_receiver_ex{executed2}); - ex::start(op); - REQUIRE(executed2); - expect_empty(scope); -} -#if !NO_TESTS_WITH_EXCEPTIONS -TEST_CASE( - "spawn_future will propagate exceptions encountered during op creation", - "[async_scope][spawn_future]") { - async_scope scope; - try { + TEST_CASE( + "spawn_future returned sender can be started after given sender completed", + "[async_scope][spawn_future]") { + impulse_scheduler sch; + bool executed{false}; + bool executed2{false}; + async_scope scope; + ex::sender auto snd = scope.spawn_future( - throwing_sender{} | ex::then([&] { FAIL("work should not be executed"); })); - (void) snd; - FAIL("Exceptions should have been thrown"); - } catch (const std::logic_error& e) { - SUCCEED("correct exception caught"); - } catch (...) { - FAIL("invalid exception caught"); + ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); + REQUIRE_FALSE(executed); + // Execute the work given to spawn_future + sch.start_next(); + REQUIRE_FALSE(executed2); + // Now connect the returned sender + auto op = ex::connect(std::move(snd), expect_void_receiver_ex{executed2}); + ex::start(op); + REQUIRE(executed2); + expect_empty(scope); + } + +#if !NO_TESTS_WITH_EXCEPTIONS + TEST_CASE( + "spawn_future will propagate exceptions encountered during op creation", + "[async_scope][spawn_future]") { + async_scope scope; + try { + ex::sender auto snd = scope.spawn_future( + throwing_sender{} | ex::then([&] { FAIL("work should not be executed"); })); + (void) snd; + FAIL("Exceptions should have been thrown"); + } catch (const std::logic_error& e) { + SUCCEED("correct exception caught"); + } catch (...) { + FAIL("invalid exception caught"); + } + expect_empty(scope); } - expect_empty(scope); -} #endif -TEST_CASE( - "TODO: spawn_future will keep the scope non-empty until the work is executed", - "[async_scope][spawn_future]") { - impulse_scheduler sch; - bool executed{false}; - async_scope scope; + TEST_CASE( + "TODO: spawn_future will keep the scope non-empty until the work is executed", + "[async_scope][spawn_future]") { + impulse_scheduler sch; + bool executed{false}; + async_scope scope; + + // Before adding any operations, the scope is empty + // TODO: reenable this + // REQUIRE(P2519::__scope::empty(scope)); + + // Non-blocking call + { + ex::sender auto snd = scope.spawn_future( + ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); + (void) snd; + } + REQUIRE_FALSE(executed); + + // The scope is now non-empty + // TODO: reenable this + // REQUIRE_FALSE(P2519::__scope::empty(scope)); + // REQUIRE(P2519::__scope::op_count(scope) == 1); - // Before adding any operations, the scope is empty - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); + // Run the operation on the scheduler; blocking call + sch.start_next(); - // Non-blocking call - { - ex::sender auto snd = scope.spawn_future( - ex::on(sch, ex::just() | ex::then([&] { executed = true; }))); - (void) snd; + // Now the scope should again be empty + // TODO: reenable this + // REQUIRE(P2519::__scope::empty(scope)); + REQUIRE(executed); + } + + TEST_CASE( + "TODO: spawn_future will keep track on how many operations are in flight", + "[async_scope][spawn_future]") { + impulse_scheduler sch; + // std::size_t num_executed{0}; + async_scope scope; + + // Before adding any operations, the scope is empty + // TODO: reenable this + // REQUIRE(P2519::__scope::op_count(scope) == 0); + // REQUIRE(P2519::__scope::empty(scope)); + + // TODO: this will fail when running multiple iterations + // constexpr std::size_t num_oper = 10; + // for (std::size_t i = 0; i < num_oper; i++) { + // ex::sender auto snd = + // scope.spawn_future(ex::on(sch, ex::just() | ex::then([&] { num_executed++; }))); + // (void)snd; + // size_t num_expected_ops = i + 1; + // REQUIRE(P2519::__scope::op_count(scope) == num_expected_ops); + // } + // + // // Now execute the operations + // for (std::size_t i = 0; i < num_oper; i++) { + // sch.start_next(); + // size_t num_expected_ops = num_oper - i - 1; + // REQUIRE(P2519::__scope::op_count(scope) == num_expected_ops); + // } + // + // // The scope is empty after all the operations are executed + // REQUIRE(P2519::__scope::empty(scope)); + // REQUIRE(num_executed == num_oper); + + expect_empty(scope); + } + + TEST_CASE( + "TODO: spawn_future work can be cancelled by cancelling the scope", + "[async_scope][spawn_future]") { + impulse_scheduler sch; + async_scope scope; + + bool cancelled1{false}; + bool cancelled2{false}; + + { + ex::sender auto snd1 = scope.spawn_future(ex::on( + sch, + ex::just() // + | ex::let_stopped([&] { + cancelled1 = true; + return ex::just(); + }))); + ex::sender auto snd2 = scope.spawn_future(ex::on( + sch, + ex::just() // + | ex::let_stopped([&] { + cancelled2 = true; + return ex::just(); + }))); + (void) snd1; + (void) snd2; + } + + // TODO: reenable this + // REQUIRE(P2519::__scope::op_count(scope) == 2); + + // Execute the first operation, before cancelling + sch.start_next(); + REQUIRE_FALSE(cancelled1); + REQUIRE_FALSE(cancelled2); + + // Cancel the async_scope object + scope.request_stop(); + // TODO: reenable this + // REQUIRE(P2519::__scope::op_count(scope) == 1); + + // Execute the first operation, after cancelling + sch.start_next(); + REQUIRE_FALSE(cancelled1); + // TODO: second operation should be cancelled + // REQUIRE(cancelled2); + REQUIRE_FALSE(cancelled2); + + // TODO: reenable this + // REQUIRE(P2519::__scope::empty(scope)); + expect_empty(scope); } - REQUIRE_FALSE(executed); - - // The scope is now non-empty - // TODO: reenable this - // REQUIRE_FALSE(P2519::__scope::empty(scope)); - // REQUIRE(P2519::__scope::op_count(scope) == 1); - - // Run the operation on the scheduler; blocking call - sch.start_next(); - - // Now the scope should again be empty - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); - REQUIRE(executed); -} - -TEST_CASE( - "TODO: spawn_future will keep track on how many operations are in flight", - "[async_scope][spawn_future]") { - impulse_scheduler sch; - // std::size_t num_executed{0}; - async_scope scope; - - // Before adding any operations, the scope is empty - // TODO: reenable this - // REQUIRE(P2519::__scope::op_count(scope) == 0); - // REQUIRE(P2519::__scope::empty(scope)); - - // TODO: this will fail when running multiple iterations - // constexpr std::size_t num_oper = 10; - // for (std::size_t i = 0; i < num_oper; i++) { - // ex::sender auto snd = - // scope.spawn_future(ex::on(sch, ex::just() | ex::then([&] { num_executed++; }))); - // (void)snd; - // size_t num_expected_ops = i + 1; - // REQUIRE(P2519::__scope::op_count(scope) == num_expected_ops); - // } - // - // // Now execute the operations - // for (std::size_t i = 0; i < num_oper; i++) { - // sch.start_next(); - // size_t num_expected_ops = num_oper - i - 1; - // REQUIRE(P2519::__scope::op_count(scope) == num_expected_ops); - // } - // - // // The scope is empty after all the operations are executed - // REQUIRE(P2519::__scope::empty(scope)); - // REQUIRE(num_executed == num_oper); - - expect_empty(scope); -} - -TEST_CASE( - "TODO: spawn_future work can be cancelled by cancelling the scope", - "[async_scope][spawn_future]") { - impulse_scheduler sch; - async_scope scope; - - bool cancelled1{false}; - bool cancelled2{false}; - - { - ex::sender auto snd1 = scope.spawn_future(ex::on( - sch, - ex::just() // - | ex::let_stopped([&] { - cancelled1 = true; - return ex::just(); - }))); - ex::sender auto snd2 = scope.spawn_future(ex::on( - sch, - ex::just() // - | ex::let_stopped([&] { - cancelled2 = true; - return ex::just(); - }))); - (void) snd1; - (void) snd2; + + template + concept is_spawn_future_worthy = // + requires(async_scope& scope, S&& snd) { scope.spawn_future(std::move(snd)); }; + + TEST_CASE("spawn_future accepts void senders", "[async_scope][spawn_future]") { + static_assert(is_spawn_future_worthy); } - // TODO: reenable this - // REQUIRE(P2519::__scope::op_count(scope) == 2); - - // Execute the first operation, before cancelling - sch.start_next(); - REQUIRE_FALSE(cancelled1); - REQUIRE_FALSE(cancelled2); - - // Cancel the async_scope object - scope.request_stop(); - // TODO: reenable this - // REQUIRE(P2519::__scope::op_count(scope) == 1); - - // Execute the first operation, after cancelling - sch.start_next(); - REQUIRE_FALSE(cancelled1); - // TODO: second operation should be cancelled - // REQUIRE(cancelled2); - REQUIRE_FALSE(cancelled2); - - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); - expect_empty(scope); -} - -template -concept is_spawn_future_worthy = // - requires(async_scope& scope, S&& snd) { scope.spawn_future(std::move(snd)); }; - -TEST_CASE("spawn_future accepts void senders", "[async_scope][spawn_future]") { - static_assert(is_spawn_future_worthy); -} - -TEST_CASE("spawn_future accepts non-void senders", "[async_scope][spawn_future]") { - static_assert(is_spawn_future_worthy); - static_assert(is_spawn_future_worthy); - static_assert(is_spawn_future_worthy); -} - -TEST_CASE("spawn_future accepts senders of errors", "[async_scope][spawn_future]") { - static_assert(is_spawn_future_worthy); - static_assert(is_spawn_future_worthy); - static_assert(is_spawn_future_worthy); -} - -TEST_CASE( - "spawn_future should accept senders that send stopped signal", - "[async_scope][spawn_future]") { - static_assert(is_spawn_future_worthy); -} - -TEST_CASE( - "TODO: spawn_future works with senders that complete with stopped signal", - "[async_scope][spawn_future]") { - impulse_scheduler sch; - async_scope scope; - - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); - - // TODO: make this work - // ex::sender auto snd = scope.spawn_future(ex::on(sch, ex::just_stopped())); - // (void)snd; - // - // // The scope is now non-empty - // REQUIRE_FALSE(P2519::__scope::empty(scope)); - // REQUIRE(P2519::__scope::op_count(scope) == 1); - // - // // Run the operation on the scheduler; blocking call - // sch.start_next(); - // - // // Now the scope should again be empty - // REQUIRE(P2519::__scope::empty(scope)); - - expect_empty(scope); -} - -TEST_CASE("spawn_future forwards value to returned sender", "[async_scope][spawn_future]") { - impulse_scheduler sch; - async_scope scope; - - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); - - ex::sender auto snd = scope.spawn_future(ex::on(sch, ex::just(13))); - sch.start_next(); - wait_for_value(std::move(snd), 13); - expect_empty(scope); -} - -TEST_CASE("TODO: spawn_future forwards error to returned sender", "[async_scope][spawn_future]") { - impulse_scheduler sch; - async_scope scope; - - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); - - // TODO: fix this - // ex::sender auto snd = scope.spawn_future(ex::on(sch, ex::just_error(-1))); - // sch.start_next(); - // try - // { - // sync_wait(std::move(snd)); - // FAIL("Should not reach this point"); - // } - // catch(int error) - // { - // REQUIRE(error == -1); - // } - expect_empty(scope); -} - -TEST_CASE( - "TODO: spawn_future forwards stopped signal to returned sender", - "[async_scope][spawn_future]") { - // impulse_scheduler sch; - async_scope scope; - - // TODO: reenable this - // REQUIRE(P2519::__scope::empty(scope)); - - // TODO: fix this - // ex::sender auto snd = scope.spawn_future(ex::on(sch, ex::just_stopped())); - // sch.start_next(); - // auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - // ex::start(op); - expect_empty(scope); -} + TEST_CASE("spawn_future accepts non-void senders", "[async_scope][spawn_future]") { + static_assert(is_spawn_future_worthy); + static_assert(is_spawn_future_worthy); + static_assert(is_spawn_future_worthy); + } + + TEST_CASE("spawn_future accepts senders of errors", "[async_scope][spawn_future]") { + static_assert(is_spawn_future_worthy); + static_assert(is_spawn_future_worthy); + static_assert(is_spawn_future_worthy); + } + + TEST_CASE( + "spawn_future should accept senders that send stopped signal", + "[async_scope][spawn_future]") { + static_assert(is_spawn_future_worthy); + } + + TEST_CASE( + "TODO: spawn_future works with senders that complete with stopped signal", + "[async_scope][spawn_future]") { + impulse_scheduler sch; + async_scope scope; + + // TODO: reenable this + // REQUIRE(P2519::__scope::empty(scope)); + + // TODO: make this work + // ex::sender auto snd = scope.spawn_future(ex::on(sch, ex::just_stopped())); + // (void)snd; + // + // // The scope is now non-empty + // REQUIRE_FALSE(P2519::__scope::empty(scope)); + // REQUIRE(P2519::__scope::op_count(scope) == 1); + // + // // Run the operation on the scheduler; blocking call + // sch.start_next(); + // + // // Now the scope should again be empty + // REQUIRE(P2519::__scope::empty(scope)); + + expect_empty(scope); + } + + TEST_CASE("spawn_future forwards value to returned sender", "[async_scope][spawn_future]") { + impulse_scheduler sch; + async_scope scope; + + // TODO: reenable this + // REQUIRE(P2519::__scope::empty(scope)); + + ex::sender auto snd = scope.spawn_future(ex::on(sch, ex::just(13))); + sch.start_next(); + wait_for_value(std::move(snd), 13); + expect_empty(scope); + } + + TEST_CASE("TODO: spawn_future forwards error to returned sender", "[async_scope][spawn_future]") { + impulse_scheduler sch; + async_scope scope; + + // TODO: reenable this + // REQUIRE(P2519::__scope::empty(scope)); + + // TODO: fix this + // ex::sender auto snd = scope.spawn_future(ex::on(sch, ex::just_error(-1))); + // sch.start_next(); + // try + // { + // sync_wait(std::move(snd)); + // FAIL("Should not reach this point"); + // } + // catch(int error) + // { + // REQUIRE(error == -1); + // } + expect_empty(scope); + } + + TEST_CASE( + "TODO: spawn_future forwards stopped signal to returned sender", + "[async_scope][spawn_future]") { + // impulse_scheduler sch; + async_scope scope; + + // TODO: reenable this + // REQUIRE(P2519::__scope::empty(scope)); + + // TODO: fix this + // ex::sender auto snd = scope.spawn_future(ex::on(sch, ex::just_stopped())); + // sch.start_next(); + // auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + // ex::start(op); + expect_empty(scope); + } +} // namespace diff --git a/test/exec/async_scope/test_stop.cpp b/test/exec/async_scope/test_stop.cpp index 9d58ecfdd..604acfea8 100644 --- a/test/exec/async_scope/test_stop.cpp +++ b/test/exec/async_scope/test_stop.cpp @@ -8,58 +8,61 @@ namespace ex = stdexec; using exec::async_scope; using stdexec::sync_wait; -TEST_CASE("calling request_stop will be visible in stop_source", "[async_scope][stop]") { - async_scope scope; +namespace { - scope.request_stop(); - REQUIRE(scope.get_stop_source().stop_requested()); -} + TEST_CASE("calling request_stop will be visible in stop_source", "[async_scope][stop]") { + async_scope scope; -TEST_CASE("calling request_stop will be visible in stop_token", "[async_scope][stop]") { - async_scope scope; + scope.request_stop(); + REQUIRE(scope.get_stop_source().stop_requested()); + } - scope.request_stop(); - REQUIRE(scope.get_stop_token().stop_requested()); -} + TEST_CASE("calling request_stop will be visible in stop_token", "[async_scope][stop]") { + async_scope scope; -TEST_CASE( - "cancelling the associated stop_source will cancel the async_scope object", - "[async_scope][stop]") { - bool empty = false; + scope.request_stop(); + REQUIRE(scope.get_stop_token().stop_requested()); + } - { - impulse_scheduler sch; - async_scope scope; - bool called = false; + TEST_CASE( + "cancelling the associated stop_source will cancel the async_scope object", + "[async_scope][stop]") { + bool empty = false; - // put work in the scope - scope.spawn(ex::on(sch, ex::just()) | ex::upon_stopped([&] { called = true; })); - REQUIRE_FALSE(called); + { + impulse_scheduler sch; + async_scope scope; + bool called = false; - // start a thread waiting on when the scope is empty: - exec::single_thread_context thread; - auto thread_sch = thread.get_scheduler(); - ex::start_detached(ex::on(thread_sch, scope.on_empty()) | ex::then([&] { empty = true; })); - REQUIRE_FALSE(empty); + // put work in the scope + scope.spawn(ex::on(sch, ex::just()) | ex::upon_stopped([&] { called = true; })); + REQUIRE_FALSE(called); - // request the scope stop - scope.get_stop_source().request_stop(); + // start a thread waiting on when the scope is empty: + exec::single_thread_context thread; + auto thread_sch = thread.get_scheduler(); + ex::start_detached(ex::on(thread_sch, scope.on_empty()) | ex::then([&] { empty = true; })); + REQUIRE_FALSE(empty); - // execute the work in the scope - sch.start_next(); + // request the scope stop + scope.get_stop_source().request_stop(); - // Should have completed with a stopped signal - REQUIRE(called); - } // blocks until the separate thread is joined + // execute the work in the scope + sch.start_next(); - REQUIRE(empty); -} + // Should have completed with a stopped signal + REQUIRE(called); + } // blocks until the separate thread is joined -TEST_CASE( - "cancelling the associated stop_source will be visible in stop_token", - "[async_scope][stop]") { - async_scope scope; + REQUIRE(empty); + } - scope.get_stop_source().request_stop(); - REQUIRE(scope.get_stop_token().stop_requested()); + TEST_CASE( + "cancelling the associated stop_source will be visible in stop_token", + "[async_scope][stop]") { + async_scope scope; + + scope.get_stop_source().request_stop(); + REQUIRE(scope.get_stop_token().stop_requested()); + } } diff --git a/test/exec/sequence/test_any_sequence_of.cpp b/test/exec/sequence/test_any_sequence_of.cpp index 30afdd2aa..5c9b86be7 100644 --- a/test/exec/sequence/test_any_sequence_of.cpp +++ b/test/exec/sequence/test_any_sequence_of.cpp @@ -21,114 +21,126 @@ #include -template -struct ignore_all_item_rcvr { - using is_receiver = void; - Receiver rcvr; - - friend stdexec::env_of_t - tag_invoke(stdexec::get_env_t, const ignore_all_item_rcvr& self) noexcept { - return stdexec::get_env(self.rcvr); - } +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-function") - template - friend void tag_invoke(stdexec::set_value_t, ignore_all_item_rcvr&& self, As&&...) noexcept { - stdexec::set_value(static_cast(self.rcvr)); - } +namespace { - friend void tag_invoke(stdexec::set_stopped_t, ignore_all_item_rcvr&& self) noexcept { - stdexec::set_value(static_cast(self.rcvr)); - } + template + struct ignore_all_item_rcvr { + using is_receiver = void; + Receiver rcvr; - template - friend void tag_invoke(stdexec::set_error_t, ignore_all_item_rcvr&& self, E&&) noexcept { - stdexec::set_value(static_cast(self.rcvr)); - } -}; - -template -struct ignore_all_sender { - using is_sender = void; - using completion_signatures = stdexec::completion_signatures; - - Item item_; - - template < - stdexec::__decays_to Self, - stdexec::receiver_of Receiver> - friend auto tag_invoke(stdexec::connect_t, Self&& self, Receiver rcvr) noexcept { - return stdexec::connect( - static_cast(self).item_, - ignore_all_item_rcvr{static_cast(rcvr)}); - } -}; + friend stdexec::env_of_t + tag_invoke(stdexec::get_env_t, const ignore_all_item_rcvr& self) noexcept { + return stdexec::get_env(self.rcvr); + } -struct ignore_all_receiver { - using is_receiver = void; + template + friend void tag_invoke(stdexec::set_value_t, ignore_all_item_rcvr&& self, As&&...) noexcept { + stdexec::set_value(static_cast(self.rcvr)); + } - template - friend ignore_all_sender> - tag_invoke(exec::set_next_t, ignore_all_receiver&, Item&& item) noexcept { - return {static_cast(item)}; - } + friend void tag_invoke(stdexec::set_stopped_t, ignore_all_item_rcvr&& self) noexcept { + stdexec::set_value(static_cast(self.rcvr)); + } - friend void tag_invoke(stdexec::set_value_t, ignore_all_receiver&&) noexcept { - } + template + friend void tag_invoke(stdexec::set_error_t, ignore_all_item_rcvr&& self, E&&) noexcept { + stdexec::set_value(static_cast(self.rcvr)); + } + }; - friend void tag_invoke(stdexec::set_stopped_t, ignore_all_receiver&&) noexcept { + template + struct ignore_all_sender { + using is_sender = void; + using completion_signatures = stdexec::completion_signatures; + + Item item_; + + template < + stdexec::__decays_to Self, + stdexec::receiver_of Receiver> + friend auto tag_invoke(stdexec::connect_t, Self&& self, Receiver rcvr) noexcept { + return stdexec::connect( + static_cast(self).item_, + ignore_all_item_rcvr{static_cast(rcvr)}); + } + }; + + struct ignore_all_receiver { + using is_receiver = void; + + template + friend ignore_all_sender> + tag_invoke(exec::set_next_t, ignore_all_receiver&, Item&& item) noexcept { + return {static_cast(item)}; + } + + friend void tag_invoke(stdexec::set_value_t, ignore_all_receiver&&) noexcept { + } + + friend void tag_invoke(stdexec::set_stopped_t, ignore_all_receiver&&) noexcept { + } + + friend void + tag_invoke(stdexec::set_error_t, ignore_all_receiver&&, std::exception_ptr) noexcept { + } + + friend stdexec::empty_env tag_invoke(stdexec::get_env_t, const ignore_all_receiver&) noexcept { + return {}; + } + }; + + TEST_CASE( + "any_sequence_of - works with empty_sequence", + "[sequence_senders][any_sequence_of][empty_sequence]") { + using Completions = stdexec::completion_signatures; + STATIC_REQUIRE(stdexec::constructible_from< + exec::any_sequence_receiver_ref::any_sender<>, + decltype(exec::empty_sequence())>); + exec::any_sequence_receiver_ref::any_sender<> any_sequence = + exec::empty_sequence(); + auto op = exec::subscribe(std::move(any_sequence), ignore_all_receiver{}); + stdexec::start(op); } - friend void tag_invoke(stdexec::set_error_t, ignore_all_receiver&&, std::exception_ptr) noexcept { + TEST_CASE("any_sequence_of - works with just(42)", "[sequence_senders][any_sequence_of]") { + using Completions = stdexec::completion_signatures; + STATIC_REQUIRE(stdexec::constructible_from< + exec::any_sequence_receiver_ref::any_sender<>, + decltype(stdexec::just(42))>); + exec::any_sequence_receiver_ref::any_sender<> any_sequence = stdexec::just(42); + auto op = exec::subscribe(std::move(any_sequence), ignore_all_receiver{}); + stdexec::start(op); } - friend stdexec::empty_env tag_invoke(stdexec::get_env_t, const ignore_all_receiver&) noexcept { - return {}; + TEST_CASE("any_sequence_of - works with just()", "[sequence_senders][any_sequence_of]") { + using CompletionsFalse = stdexec::completion_signatures; + using Completions = stdexec::completion_signatures; + STATIC_REQUIRE_FALSE(stdexec::constructible_from< + exec::any_sequence_receiver_ref::any_sender<>, + decltype(stdexec::just())>); + STATIC_REQUIRE(stdexec::constructible_from< + exec::any_sequence_receiver_ref::any_sender<>, + decltype(stdexec::just())>); + exec::any_sequence_receiver_ref::any_sender<> any_sequence = stdexec::just(); + auto op = exec::subscribe(std::move(any_sequence), ignore_all_receiver{}); + stdexec::start(op); } -}; - -TEST_CASE( - "any_sequence_of - works with empty_sequence", - "[sequence_senders][any_sequence_of][empty_sequence]") { - using Completions = stdexec::completion_signatures; - STATIC_REQUIRE(stdexec::constructible_from< - exec::any_sequence_receiver_ref::any_sender<>, - decltype(exec::empty_sequence())>); - exec::any_sequence_receiver_ref::any_sender<> any_sequence = exec::empty_sequence(); - auto op = exec::subscribe(std::move(any_sequence), ignore_all_receiver{}); - stdexec::start(op); -} -TEST_CASE("any_sequence_of - works with just(42)", "[sequence_senders][any_sequence_of]") { - using Completions = stdexec::completion_signatures; - STATIC_REQUIRE(stdexec::constructible_from< - exec::any_sequence_receiver_ref::any_sender<>, - decltype(stdexec::just(42))>); - exec::any_sequence_receiver_ref::any_sender<> any_sequence = stdexec::just(42); - auto op = exec::subscribe(std::move(any_sequence), ignore_all_receiver{}); - stdexec::start(op); -} - -TEST_CASE("any_sequence_of - works with just()", "[sequence_senders][any_sequence_of]") { - using CompletionsFalse = stdexec::completion_signatures; - using Completions = stdexec::completion_signatures; - STATIC_REQUIRE_FALSE(stdexec::constructible_from< - exec::any_sequence_receiver_ref::any_sender<>, - decltype(stdexec::just())>); - STATIC_REQUIRE(stdexec::constructible_from< - exec::any_sequence_receiver_ref::any_sender<>, - decltype(stdexec::just())>); - exec::any_sequence_receiver_ref::any_sender<> any_sequence = stdexec::just(); - auto op = exec::subscribe(std::move(any_sequence), ignore_all_receiver{}); - stdexec::start(op); + TEST_CASE("any_sequence_of - has an environment", "[sequence_senders][any_sequence_of]") { + using Completions = stdexec::completion_signatures; + exec::any_sequence_receiver_ref::any_sender<> any_sequence = stdexec::just(); + auto env = stdexec::get_env(any_sequence); + using env_t = decltype(env); + STATIC_REQUIRE( + stdexec::same_as< + env_t, + stdexec::__t< + exec::__any::__sender_env, stdexec::__types<>>>>); + } } -TEST_CASE("any_sequence_of - has an environment", "[sequence_senders][any_sequence_of]") { - using Completions = stdexec::completion_signatures; - exec::any_sequence_receiver_ref::any_sender<> any_sequence = stdexec::just(); - auto env = stdexec::get_env(any_sequence); - using env_t = decltype(env); - STATIC_REQUIRE( - stdexec::same_as< - env_t, - stdexec::__t, stdexec::__types<>>>>); -} \ No newline at end of file +STDEXEC_PRAGMA_POP() diff --git a/test/exec/sequence/test_empty_sequence.cpp b/test/exec/sequence/test_empty_sequence.cpp index 50953ef19..fcfc7764d 100644 --- a/test/exec/sequence/test_empty_sequence.cpp +++ b/test/exec/sequence/test_empty_sequence.cpp @@ -22,45 +22,49 @@ using namespace stdexec; using namespace exec; -TEST_CASE( - "sequence_senders - empty_sequence is a sequence sender", - "[sequence_senders][empty_sequence]") { - using empty_t = decltype(empty_sequence()); - STATIC_REQUIRE(sequence_sender); - STATIC_REQUIRE(same_as< - __sequence_completion_signatures_of_t, - completion_signatures>); - STATIC_REQUIRE( - same_as, completion_signatures>); - STATIC_REQUIRE(same_as, item_types<>>); -} - -struct count_set_next_receiver_t { - using is_receiver = void; - int& count_invocations_; +namespace { - friend auto tag_invoke(set_next_t, count_set_next_receiver_t& __self, auto /* item */) noexcept { - ++__self.count_invocations_; - return just(); + TEST_CASE( + "sequence_senders - empty_sequence is a sequence sender", + "[sequence_senders][empty_sequence]") { + using empty_t = decltype(empty_sequence()); + STATIC_REQUIRE(sequence_sender); + STATIC_REQUIRE(same_as< + __sequence_completion_signatures_of_t, + completion_signatures>); + STATIC_REQUIRE( + same_as, completion_signatures>); + STATIC_REQUIRE(same_as, item_types<>>); } - friend void tag_invoke(set_value_t, count_set_next_receiver_t&&) noexcept { - } + struct count_set_next_receiver_t { + using is_receiver = void; + int& count_invocations_; - friend empty_env tag_invoke(get_env_t, const count_set_next_receiver_t&) noexcept { - return {}; - } -}; + friend auto + tag_invoke(set_next_t, count_set_next_receiver_t& __self, auto /* item */) noexcept { + ++__self.count_invocations_; + return just(); + } -TEST_CASE( - "sequence_senders - empty_sequence is a sequence sender to a minimal receiver of set_value_t()", - "[sequence_senders][empty_sequence]") { - using empty_t = decltype(empty_sequence()); - STATIC_REQUIRE(receiver_of>); - STATIC_REQUIRE(sequence_sender_to); + friend void tag_invoke(set_value_t, count_set_next_receiver_t&&) noexcept { + } - int count{0}; - auto op = subscribe(empty_sequence(), count_set_next_receiver_t{count}); - start(op); - CHECK(count == 0); -} \ No newline at end of file + friend empty_env tag_invoke(get_env_t, const count_set_next_receiver_t&) noexcept { + return {}; + } + }; + + TEST_CASE( + "sequence_senders - empty_sequence is a sequence sender to a minimal receiver of set_value_t()", + "[sequence_senders][empty_sequence]") { + using empty_t = decltype(empty_sequence()); + STATIC_REQUIRE(receiver_of>); + STATIC_REQUIRE(sequence_sender_to); + + int count{0}; + auto op = subscribe(empty_sequence(), count_set_next_receiver_t{count}); + start(op); + CHECK(count == 0); + } +} diff --git a/test/exec/sequence/test_ignore_all_values.cpp b/test/exec/sequence/test_ignore_all_values.cpp index 2716ffac9..d2ba99085 100644 --- a/test/exec/sequence/test_ignore_all_values.cpp +++ b/test/exec/sequence/test_ignore_all_values.cpp @@ -20,87 +20,91 @@ #include -TEST_CASE("ignore_all_values - ignore empty sequence", "[sequence_senders][ignore_all_values]") { - auto sndr = exec::ignore_all_values(exec::empty_sequence()); - using Sender = decltype(sndr); - STATIC_REQUIRE(stdexec::sender_in); - STATIC_REQUIRE(stdexec::same_as< - stdexec::completion_signatures, - stdexec::completion_signatures_of_t>); - STATIC_REQUIRE(stdexec::sender_expr_for); - CHECK(stdexec::sync_wait(sndr)); -} +namespace { -TEST_CASE("ignore_all_values - ignore just(42)", "[sequence_senders][ignore_all_values]") { - auto sndr = exec::ignore_all_values(stdexec::just(42)); - using Sender = decltype(sndr); - STATIC_REQUIRE(stdexec::sender_in); - STATIC_REQUIRE(stdexec::same_as< - stdexec::completion_signatures, - stdexec::completion_signatures_of_t>); - CHECK(stdexec::sync_wait(sndr)); -} + TEST_CASE("ignore_all_values - ignore empty sequence", "[sequence_senders][ignore_all_values]") { + auto sndr = exec::ignore_all_values(exec::empty_sequence()); + using Sender = decltype(sndr); + STATIC_REQUIRE(stdexec::sender_in); + STATIC_REQUIRE(stdexec::same_as< + stdexec::completion_signatures, + stdexec::completion_signatures_of_t>); + STATIC_REQUIRE(stdexec::sender_expr_for); + CHECK(stdexec::sync_wait(sndr)); + } -TEST_CASE("ignore_all_values - ignore just()", "[sequence_senders][ignore_all_values]") { - auto sndr = exec::ignore_all_values(stdexec::just()); - using Sender = decltype(sndr); - STATIC_REQUIRE(stdexec::sender_in); - STATIC_REQUIRE(stdexec::same_as< - stdexec::completion_signatures, - stdexec::completion_signatures_of_t>); - CHECK(stdexec::sync_wait(sndr)); -} + TEST_CASE("ignore_all_values - ignore just(42)", "[sequence_senders][ignore_all_values]") { + auto sndr = exec::ignore_all_values(stdexec::just(42)); + using Sender = decltype(sndr); + STATIC_REQUIRE(stdexec::sender_in); + STATIC_REQUIRE(stdexec::same_as< + stdexec::completion_signatures, + stdexec::completion_signatures_of_t>); + CHECK(stdexec::sync_wait(sndr)); + } -TEST_CASE("ignore_all_values - ignore just_stopped()", "[sequence_senders][ignore_all_values]") { - auto sndr = exec::ignore_all_values(stdexec::just_stopped()); - using Sender = decltype(sndr); - STATIC_REQUIRE(stdexec::sender_in); - STATIC_REQUIRE(stdexec::same_as< - stdexec::completion_signatures, - stdexec::completion_signatures_of_t>); - CHECK_FALSE(stdexec::sync_wait(sndr)); -} + TEST_CASE("ignore_all_values - ignore just()", "[sequence_senders][ignore_all_values]") { + auto sndr = exec::ignore_all_values(stdexec::just()); + using Sender = decltype(sndr); + STATIC_REQUIRE(stdexec::sender_in); + STATIC_REQUIRE(stdexec::same_as< + stdexec::completion_signatures, + stdexec::completion_signatures_of_t>); + CHECK(stdexec::sync_wait(sndr)); + } -TEST_CASE("ignore_all_values - ignore just_error()", "[sequence_senders][ignore_all_values]") { - auto sndr = exec::ignore_all_values( - stdexec::just_error(std::make_exception_ptr(std::runtime_error("test")))); - using Sender = decltype(sndr); - STATIC_REQUIRE(stdexec::sender_in); - STATIC_REQUIRE( - stdexec::same_as< - stdexec:: - completion_signatures, - stdexec::completion_signatures_of_t>); - CHECK_THROWS(stdexec::sync_wait(sndr)); -} + TEST_CASE("ignore_all_values - ignore just_stopped()", "[sequence_senders][ignore_all_values]") { + auto sndr = exec::ignore_all_values(stdexec::just_stopped()); + using Sender = decltype(sndr); + STATIC_REQUIRE(stdexec::sender_in); + STATIC_REQUIRE(stdexec::same_as< + stdexec::completion_signatures, + stdexec::completion_signatures_of_t>); + CHECK_FALSE(stdexec::sync_wait(sndr)); + } -struct sequence_op { - friend void tag_invoke(stdexec::start_t, sequence_op&) noexcept { + TEST_CASE("ignore_all_values - ignore just_error()", "[sequence_senders][ignore_all_values]") { + auto sndr = exec::ignore_all_values( + stdexec::just_error(std::make_exception_ptr(std::runtime_error("test")))); + using Sender = decltype(sndr); + STATIC_REQUIRE(stdexec::sender_in); + STATIC_REQUIRE( + stdexec::same_as< + stdexec:: + completion_signatures, + stdexec::completion_signatures_of_t>); + CHECK_THROWS(stdexec::sync_wait(sndr)); } -}; -template -struct sequence { - using is_sender = exec::sequence_tag; + struct sequence_op { + friend void tag_invoke(stdexec::start_t, sequence_op&) noexcept { + } + }; - using completion_signatures = - stdexec::completion_signatures; + template + struct sequence { + using is_sender = exec::sequence_tag; - using item_types = exec::item_types; + using completion_signatures = + stdexec::completion_signatures; - friend sequence_op tag_invoke(exec::subscribe_t, sequence, stdexec::__ignore) noexcept { - return sequence_op{}; - } -}; + using item_types = exec::item_types; -TEST_CASE("ignore_all_values - Merge error and stop signatures from sequence and items") { - using just_t = decltype(stdexec::just_error(std::make_exception_ptr(std::runtime_error("test")))); - sequence seq; - auto ignore = exec::ignore_all_values(seq); - using Sigs = stdexec::completion_signatures_of_t; - using ExpectedSigs = stdexec::completion_signatures< - stdexec::set_value_t(), - stdexec::set_error_t(int), - stdexec::set_error_t(std::exception_ptr)>; - STATIC_REQUIRE(std::same_as); -} \ No newline at end of file + friend sequence_op tag_invoke(exec::subscribe_t, sequence, stdexec::__ignore) noexcept { + return sequence_op{}; + } + }; + + TEST_CASE("ignore_all_values - Merge error and stop signatures from sequence and items") { + using just_t = + decltype(stdexec::just_error(std::make_exception_ptr(std::runtime_error("test")))); + sequence seq; + auto ignore = exec::ignore_all_values(seq); + using Sigs = stdexec::completion_signatures_of_t; + using ExpectedSigs = stdexec::completion_signatures< + stdexec::set_value_t(), + stdexec::set_error_t(int), + stdexec::set_error_t(std::exception_ptr)>; + STATIC_REQUIRE(std::same_as); + } +} diff --git a/test/exec/sequence/test_iterate.cpp b/test/exec/sequence/test_iterate.cpp index 26100736c..70c333dc3 100644 --- a/test/exec/sequence/test_iterate.cpp +++ b/test/exec/sequence/test_iterate.cpp @@ -24,110 +24,114 @@ #include #include -template -struct sum_item_rcvr { - using is_receiver = void; - Receiver rcvr; - int* sum_; - - friend stdexec::env_of_t - tag_invoke(stdexec::get_env_t, const sum_item_rcvr& self) noexcept { - return stdexec::get_env(self.rcvr); - } - - template - friend void tag_invoke(stdexec::set_value_t, sum_item_rcvr&& self, int x) noexcept { - *self.sum_ += x; - stdexec::set_value(static_cast(self.rcvr)); - } - - friend void tag_invoke(stdexec::set_stopped_t, sum_item_rcvr&& self) noexcept { - stdexec::set_value(static_cast(self.rcvr)); - } - - template - friend void tag_invoke(stdexec::set_error_t, sum_item_rcvr&& self, E&&) noexcept { - stdexec::set_value(static_cast(self.rcvr)); - } -}; - -template -struct sum_sender { - using is_sender = void; - using completion_signatures = stdexec::completion_signatures; - - Item item_; - int* sum_; - - template < - stdexec::__decays_to Self, - stdexec::receiver_of Receiver> - friend auto tag_invoke(stdexec::connect_t, Self&& self, Receiver rcvr) noexcept { - return stdexec::connect( - static_cast(self).item_, - sum_item_rcvr{static_cast(rcvr), self.sum_}); - } -}; - -template -struct sum_receiver { - using is_receiver = void; - - int& sum_; - Env env_{}; +namespace { + + template + struct sum_item_rcvr { + using is_receiver = void; + Receiver rcvr; + int* sum_; + + friend stdexec::env_of_t + tag_invoke(stdexec::get_env_t, const sum_item_rcvr& self) noexcept { + return stdexec::get_env(self.rcvr); + } + + template + friend void tag_invoke(stdexec::set_value_t, sum_item_rcvr&& self, int x) noexcept { + *self.sum_ += x; + stdexec::set_value(static_cast(self.rcvr)); + } + + friend void tag_invoke(stdexec::set_stopped_t, sum_item_rcvr&& self) noexcept { + stdexec::set_value(static_cast(self.rcvr)); + } + + template + friend void tag_invoke(stdexec::set_error_t, sum_item_rcvr&& self, E&&) noexcept { + stdexec::set_value(static_cast(self.rcvr)); + } + }; template - friend sum_sender> - tag_invoke(exec::set_next_t, sum_receiver& self, Item&& item) noexcept { - return {static_cast(item), &self.sum_}; - } - - friend void tag_invoke(stdexec::set_value_t, sum_receiver&&) noexcept { - } - - friend void tag_invoke(stdexec::set_stopped_t, sum_receiver&&) noexcept { + struct sum_sender { + using is_sender = void; + using completion_signatures = stdexec::completion_signatures; + + Item item_; + int* sum_; + + template < + stdexec::__decays_to Self, + stdexec::receiver_of Receiver> + friend auto tag_invoke(stdexec::connect_t, Self&& self, Receiver rcvr) noexcept { + return stdexec::connect( + static_cast(self).item_, + sum_item_rcvr{static_cast(rcvr), self.sum_}); + } + }; + + template + struct sum_receiver { + using is_receiver = void; + + int& sum_; + Env env_{}; + + template + friend sum_sender> + tag_invoke(exec::set_next_t, sum_receiver& self, Item&& item) noexcept { + return {static_cast(item), &self.sum_}; + } + + friend void tag_invoke(stdexec::set_value_t, sum_receiver&&) noexcept { + } + + friend void tag_invoke(stdexec::set_stopped_t, sum_receiver&&) noexcept { + } + + friend void tag_invoke(stdexec::set_error_t, sum_receiver&&, std::exception_ptr) noexcept { + } + + friend Env tag_invoke(stdexec::get_env_t, const sum_receiver& self) noexcept { + return self.env_; + } + }; + + TEST_CASE("iterate - sum up an array ", "[sequence_senders][iterate]") { + std::array array{42, 43, 44}; + int sum = 0; + auto iterate = exec::iterate(std::views::all(array)); + STATIC_REQUIRE(exec::sequence_sender_in); + STATIC_REQUIRE(stdexec::sender_expr_for); + auto op = exec::subscribe(iterate, sum_receiver<>{sum}); + stdexec::start(op); + CHECK(sum == (42 + 43 + 44)); } - friend void tag_invoke(stdexec::set_error_t, sum_receiver&&, std::exception_ptr) noexcept { + struct my_domain { + template Sender, class _Env> + auto transform_sender(Sender&& sender, _Env&&) const noexcept { + auto range = stdexec::apply_sender( + std::forward(sender), stdexec::__detail::__get_data{}); + auto sum = std::accumulate(std::ranges::begin(range), std::ranges::end(range), 0); + return stdexec::just(sum + 1); + } + }; + + TEST_CASE("iterate - sum up an array with custom domain", "[sequence_senders][iterate]") { + std::array array{42, 43, 44}; + auto iterate = exec::iterate(std::views::all(array)); + STATIC_REQUIRE(exec::sequence_sender_in); + STATIC_REQUIRE(stdexec::sender_expr_for); + auto env = exec::make_env(exec::with(stdexec::get_domain, my_domain{})); + using Env = decltype(env); + int sum = 0; + auto op = exec::subscribe(iterate, sum_receiver{sum, env}); + stdexec::start(op); + CHECK(sum == (42 + 43 + 44 + 1)); } - friend Env tag_invoke(stdexec::get_env_t, const sum_receiver& self) noexcept { - return self.env_; - } -}; - -TEST_CASE("iterate - sum up an array ", "[sequence_senders][iterate]") { - std::array array{42, 43, 44}; - int sum = 0; - auto iterate = exec::iterate(std::views::all(array)); - STATIC_REQUIRE(exec::sequence_sender_in); - STATIC_REQUIRE(stdexec::sender_expr_for); - auto op = exec::subscribe(iterate, sum_receiver<>{sum}); - stdexec::start(op); - CHECK(sum == (42 + 43 + 44)); -} - -struct my_domain { - template Sender, class _Env> - auto transform_sender(Sender&& sender, _Env&&) const noexcept { - auto range = stdexec::apply_sender( - std::forward(sender), stdexec::__detail::__get_data{}); - auto sum = std::accumulate(std::ranges::begin(range), std::ranges::end(range), 0); - return stdexec::just(sum + 1); - } -}; - -TEST_CASE("iterate - sum up an array with custom domain", "[sequence_senders][iterate]") { - std::array array{42, 43, 44}; - auto iterate = exec::iterate(std::views::all(array)); - STATIC_REQUIRE(exec::sequence_sender_in); - STATIC_REQUIRE(stdexec::sender_expr_for); - auto env = exec::make_env(exec::with(stdexec::get_domain, my_domain{})); - using Env = decltype(env); - int sum = 0; - auto op = exec::subscribe(iterate, sum_receiver{sum, env}); - stdexec::start(op); - CHECK(sum == (42 + 43 + 44 + 1)); } #endif // STDEXEC_HAS_STD_RANGES() diff --git a/test/exec/sequence/test_transform_each.cpp b/test/exec/sequence/test_transform_each.cpp index d0f2933c0..80e268ad9 100644 --- a/test/exec/sequence/test_transform_each.cpp +++ b/test/exec/sequence/test_transform_each.cpp @@ -30,93 +30,96 @@ #include #include -struct next_rcvr { - using __id = next_rcvr; - using __t = next_rcvr; - using is_receiver = void; +namespace { - friend auto tag_invoke(exec::set_next_t, next_rcvr, auto item) { - return item; - } + struct next_rcvr { + using __id = next_rcvr; + using __t = next_rcvr; + using is_receiver = void; - friend void tag_invoke(stdexec::set_value_t, next_rcvr) noexcept { - } + friend auto tag_invoke(exec::set_next_t, next_rcvr, auto item) { + return item; + } - friend stdexec::empty_env tag_invoke(stdexec::get_env_t, next_rcvr) noexcept { - return {}; - } -}; + friend void tag_invoke(stdexec::set_value_t, next_rcvr) noexcept { + } -TEST_CASE( - "transform_each - transform sender applies adaptor to no elements", - "[sequence_senders][transform_each][empty_sequence]") { - int counter = 0; - auto transformed = exec::transform_each( - exec::empty_sequence(), stdexec::then([&counter]() noexcept { ++counter; })); - auto op = exec::subscribe(transformed, next_rcvr{}); - stdexec::start(op); - CHECK(counter == 0); -} + friend stdexec::empty_env tag_invoke(stdexec::get_env_t, next_rcvr) noexcept { + return {}; + } + }; -TEST_CASE( - "transform_each - transform sender applies adaptor to a sender", - "[sequence_senders][transform_each]") { - int value = 0; - auto transformed = exec::transform_each( - stdexec::just(42), stdexec::then([&value](int x) noexcept { value = x; })); - auto op = exec::subscribe(transformed, next_rcvr{}); - stdexec::start(op); - CHECK(value == 42); -} + TEST_CASE( + "transform_each - transform sender applies adaptor to no elements", + "[sequence_senders][transform_each][empty_sequence]") { + int counter = 0; + auto transformed = exec::transform_each( + exec::empty_sequence(), stdexec::then([&counter]() noexcept { ++counter; })); + auto op = exec::subscribe(transformed, next_rcvr{}); + stdexec::start(op); + CHECK(counter == 0); + } -TEST_CASE( - "transform_each - transform sender applies adaptor to a sender and ignores all values", - "[sequence_senders][transform_each][ignore_all_values]") { - int value = 0; - auto transformed = - exec::transform_each(stdexec::just(42), stdexec::then([&value](int x) { value = x; })) - | exec::ignore_all_values(); - stdexec::sync_wait(transformed); - CHECK(value == 42); -} + TEST_CASE( + "transform_each - transform sender applies adaptor to a sender", + "[sequence_senders][transform_each]") { + int value = 0; + auto transformed = exec::transform_each( + stdexec::just(42), stdexec::then([&value](int x) noexcept { value = x; })); + auto op = exec::subscribe(transformed, next_rcvr{}); + stdexec::start(op); + CHECK(value == 42); + } + + TEST_CASE( + "transform_each - transform sender applies adaptor to a sender and ignores all values", + "[sequence_senders][transform_each][ignore_all_values]") { + int value = 0; + auto transformed = + exec::transform_each(stdexec::just(42), stdexec::then([&value](int x) { value = x; })) + | exec::ignore_all_values(); + stdexec::sync_wait(transformed); + CHECK(value == 42); + } #if STDEXEC_HAS_STD_RANGES() -TEST_CASE( - "transform_each - transform sender applies adaptor to each item", - "[sequence_senders][transform_each][iterate]") { - auto range = [](auto from, auto to) { - return exec::iterate(std::views::iota(from, to)); - }; - auto then_each = [](auto f) { - return exec::transform_each(stdexec::then(f)); - }; - int total = 0; - auto sum = range(0, 10) // - | then_each([&total](int x) noexcept { total += x; }); - stdexec::sync_wait(exec::ignore_all_values(sum)); - CHECK(total == 45); -} + TEST_CASE( + "transform_each - transform sender applies adaptor to each item", + "[sequence_senders][transform_each][iterate]") { + auto range = [](auto from, auto to) { + return exec::iterate(std::views::iota(from, to)); + }; + auto then_each = [](auto f) { + return exec::transform_each(stdexec::then(f)); + }; + int total = 0; + auto sum = range(0, 10) // + | then_each([&total](int x) noexcept { total += x; }); + stdexec::sync_wait(exec::ignore_all_values(sum)); + CHECK(total == 45); + } #endif -struct my_domain { - template Sender, class Env> - static auto transform_sender(Sender&&, const Env&) { - return ex::just(int{42}); - } -}; + struct my_domain { + template Sender, class Env> + static auto transform_sender(Sender&&, const Env&) { + return ex::just(int{42}); + } + }; -TEST_CASE("transform_each - can be customized late", "[transform_each][ignore_all_values]") { - // The customization will return a different value - basic_inline_scheduler sched; - int result = 0; - auto start = ex::just(std::string{"hello"}); - auto with_scheduler = exec::write(exec::with(ex::get_scheduler, inline_scheduler())); - auto adaptor = exec::on(sched, ex::then([](std::string x) { return x + ", world"; })) - | with_scheduler; - auto snd = start // - | exec::transform_each(adaptor) // - | exec::transform_each(ex::then([&](int x) { result = x; })) // - | exec::ignore_all_values(); - stdexec::sync_wait(snd); - CHECK(result == 42); + TEST_CASE("transform_each - can be customized late", "[transform_each][ignore_all_values]") { + // The customization will return a different value + basic_inline_scheduler sched; + int result = 0; + auto start = ex::just(std::string{"hello"}); + auto with_scheduler = exec::write(exec::with(ex::get_scheduler, inline_scheduler())); + auto adaptor = exec::on(sched, ex::then([](std::string x) { return x + ", world"; })) + | with_scheduler; + auto snd = start // + | exec::transform_each(adaptor) // + | exec::transform_each(ex::then([&](int x) { result = x; })) // + | exec::ignore_all_values(); + stdexec::sync_wait(snd); + CHECK(result == 42); + } } diff --git a/test/exec/test_any_sender.cpp b/test/exec/test_any_sender.cpp index 345b1bc90..2ab3b532c 100644 --- a/test/exec/test_any_sender.cpp +++ b/test/exec/test_any_sender.cpp @@ -28,691 +28,697 @@ using namespace stdexec; using namespace exec; -/////////////////////////////////////////////////////////////////////////////// -// any_receiver_ref - -struct tag_t : stdexec::__query<::tag_t> { - template - // BUGBUG ambiguous! - requires stdexec::tag_invocable - auto operator()(T&& t) const noexcept(stdexec::nothrow_tag_invocable) - -> stdexec::tag_invoke_result_t { - return stdexec::tag_invoke(*this, (T&&) t); - } -}; - -inline constexpr ::tag_t get_address; - -struct env { - const void* object_{nullptr}; - in_place_stop_token token_; - - friend const void* tag_invoke(::tag_t, env e) noexcept { - return e.object_; - } - - friend in_place_stop_token tag_invoke(get_stop_token_t, const env& e) noexcept { - return e.token_; - } -}; - -struct sink_receiver { - std::variant value_{}; - - friend void tag_invoke(set_value_t, sink_receiver&& r, int value) noexcept { - r.value_ = value; - } - - friend void tag_invoke(set_error_t, sink_receiver&& r, std::exception_ptr e) noexcept { - r.value_ = e; - } - - friend void tag_invoke(set_stopped_t, sink_receiver&& r) noexcept { - r.value_ = set_stopped; - } - - friend env tag_invoke(get_env_t, const sink_receiver& r) noexcept { - return {static_cast(&r)}; - } -}; - -TEST_CASE("any_receiver_ref is constructible from receivers", "[types][any_sender]") { - using Sigs = completion_signatures; - sink_receiver rcvr; - any_receiver_ref ref{rcvr}; - CHECK(receiver); - CHECK(receiver_of); - CHECK(!receiver_of>); - CHECK(std::is_copy_assignable_v>); - CHECK(std::is_constructible_v, const sink_receiver&>); - CHECK(!std::is_constructible_v, sink_receiver&&>); - CHECK(!std::is_constructible_v< - any_receiver_ref>, - const sink_receiver&>); -} - -TEST_CASE("any_receiver_ref is queryable", "[types][any_sender]") { - using Sigs = completion_signatures; - using receiver_ref = any_receiver_ref>; - sink_receiver rcvr1{}; - sink_receiver rcvr2{}; - receiver_ref ref1{rcvr1}; - receiver_ref ref2{rcvr2}; - CHECK(get_address(get_env(ref1)) == &rcvr1); - CHECK(get_address(get_env(ref2)) == &rcvr2); - { - receiver_ref copied_ref = ref2; - CHECK(get_address(get_env(copied_ref)) != &ref2); - CHECK(get_address(get_env(copied_ref)) == &rcvr2); - ref1 = copied_ref; - CHECK(get_address(get_env(ref1)) != &rcvr1); - CHECK(get_address(get_env(ref1)) != &copied_ref); - CHECK(get_address(get_env(ref1)) == &rcvr2); - copied_ref = rcvr1; - CHECK(get_address(get_env(ref1)) == &rcvr2); - CHECK(get_address(get_env(copied_ref)) == &rcvr1); - } - CHECK(get_address(get_env(ref1)) == &rcvr2); -} - -TEST_CASE("any_receiver_ref calls receiver methods", "[types][any_sender]") { - using Sigs = - completion_signatures; - using receiver_ref = any_receiver_ref; - REQUIRE(receiver_of); - sink_receiver value{}; - sink_receiver error{}; - sink_receiver stopped{}; - - // Check set value - CHECK(value.value_.index() == 0); - receiver_ref ref = value; - set_value((receiver_ref&&) ref, 42); - CHECK(value.value_.index() == 1); - CHECK(std::get<1>(value.value_) == 42); - // Check set error - CHECK(error.value_.index() == 0); - ref = error; - set_error((receiver_ref&&) ref, std::make_exception_ptr(42)); - CHECK(error.value_.index() == 2); - CHECK_THROWS_AS(std::rethrow_exception(std::get<2>(error.value_)), int); - // Check set stopped - CHECK(stopped.value_.index() == 0); - ref = stopped; - set_stopped((receiver_ref&&) ref); - CHECK(stopped.value_.index() == 3); -} - -TEST_CASE("any_receiver_ref is connectable with when_any", "[types][any_sender]") { - using Sigs = completion_signatures; - using receiver_ref = any_receiver_ref; - REQUIRE(receiver_of); - sink_receiver rcvr{}; - receiver_ref ref = rcvr; - - auto sndr = when_any(just(42)); - CHECK(rcvr.value_.index() == 0); - auto op = connect(std::move(sndr), std::move(ref)); - start(op); - CHECK(rcvr.value_.index() == 1); - CHECK(std::get<1>(rcvr.value_) == 42); -} - -/////////////////////////////////////////////////////////////////////////////// -// any.storage +namespace { + + /////////////////////////////////////////////////////////////////////////////// + // any_receiver_ref + + struct tag_t : stdexec::__query { + template + // BUGBUG ambiguous! + requires stdexec::tag_invocable + auto operator()(T&& t) const noexcept(stdexec::nothrow_tag_invocable) + -> stdexec::tag_invoke_result_t { + return stdexec::tag_invoke(*this, (T&&) t); + } + }; -struct empty_vtable_t { - private: - template - friend empty_vtable_t* - tag_invoke(__any::__create_vtable_t, __mtype, __mtype) noexcept { - static empty_vtable_t vtable{}; - return &vtable; - } -}; - -TEST_CASE("empty storage is movable", "[types][any_sender]") { - struct foo { }; - - using any_unique = __any::__unique_storage_t; - any_unique s1{}; - any_unique s2 = foo{}; - static_assert(std::is_move_assignable_v); - static_assert(!std::is_copy_assignable_v); - - CHECK(s2.__get_vtable()); - CHECK(s1.__get_vtable() != s2.__get_vtable()); - CHECK(s1.__get_object_pointer() == nullptr); - CHECK(s2.__get_object_pointer() != nullptr); - // Test SBO - std::intptr_t obj_ptr = reinterpret_cast(s2.__get_object_pointer()); - std::intptr_t s2_ptr = reinterpret_cast(&s2); - CHECK(std::abs(s2_ptr - obj_ptr) < std::intptr_t(sizeof(any_unique))); - - s1 = std::move(s2); - CHECK(s2.__get_vtable()); - CHECK(s1.__get_vtable() != s2.__get_vtable()); - CHECK(s1.__get_object_pointer() != nullptr); - CHECK(s2.__get_object_pointer() == nullptr); - - s1 = std::move(s2); - CHECK(s1.__get_object_pointer() == nullptr); - CHECK(s2.__get_object_pointer() == nullptr); -} + inline constexpr tag_t get_address; -TEST_CASE("empty storage is movable, throwing moves will allocate", "[types][any_sender]") { - struct move_throws { - move_throws() = default; + struct env { + const void* object_{nullptr}; + in_place_stop_token token_; - move_throws(move_throws&&) noexcept(false) { + friend const void* tag_invoke(tag_t, env e) noexcept { + return e.object_; } - move_throws& operator=(move_throws&&) noexcept(false) { - return *this; + friend in_place_stop_token tag_invoke(get_stop_token_t, const env& e) noexcept { + return e.token_; } }; - using any_unique = __any::__unique_storage_t; - any_unique s1{}; - any_unique s2 = move_throws{}; - static_assert(std::is_move_assignable_v); - static_assert(!std::is_copy_assignable_v); - - CHECK(s2.__get_vtable()); - CHECK(s1.__get_vtable() != s2.__get_vtable()); - CHECK(s1.__get_object_pointer() == nullptr); - CHECK(s2.__get_object_pointer() != nullptr); - // Test SBO - std::intptr_t obj_ptr = reinterpret_cast(s2.__get_object_pointer()); - std::intptr_t s2_ptr = reinterpret_cast(&s2); - CHECK(std::abs(s2_ptr - obj_ptr) >= std::intptr_t(sizeof(any_unique))); - - s1 = std::move(s2); - CHECK(s2.__get_vtable()); - CHECK(s1.__get_vtable() != s2.__get_vtable()); - CHECK(s1.__get_object_pointer() != nullptr); - CHECK(s2.__get_object_pointer() == nullptr); - - s1 = std::move(s2); - CHECK(s1.__get_object_pointer() == nullptr); - CHECK(s2.__get_object_pointer() == nullptr); -} - -/////////////////////////////////////////////////////////////////////////////// -// any_sender - -template -using any_sender_of = - typename any_receiver_ref>::template any_sender<>; - -TEST_CASE("any sender is a sender", "[types][any_sender]") { - CHECK(stdexec::sender>); - CHECK(std::is_move_assignable_v>); - CHECK(std::is_nothrow_move_assignable_v>); - CHECK(!std::is_copy_assignable_v>); - any_sender_of sender = just(); -} + struct sink_receiver { + std::variant value_{}; -TEST_CASE("sync_wait works on any_sender_of", "[types][any_sender]") { - int value = 0; - any_sender_of sender = just(42) | then([&](int v) noexcept { value = v; }); - CHECK(std::same_as< - completion_signatures_of_t>, - completion_signatures>); - sync_wait(std::move(sender)); - CHECK(value == 42); -} - -TEST_CASE("construct any_sender_of recursively from when_all", "[types][any_sender]") { - any_sender_of sender = just(); - using sender_t = any_sender_of; - using when_all_t = decltype(when_all(std::move(sender))); - static_assert(std::is_constructible_v); -} - -TEST_CASE("sync_wait returns value", "[types][any_sender]") { - any_sender_of sender = just(21) | then([&](int v) noexcept { return 2 * v; }); - CHECK(std::same_as< - completion_signatures_of_t>, - completion_signatures>); - auto [value1] = *sync_wait(std::move(sender)); - CHECK(value1 == 42); -} + friend void tag_invoke(set_value_t, sink_receiver&& r, int value) noexcept { + r.value_ = value; + } -template -using my_sender_of = any_sender_of; + friend void tag_invoke(set_error_t, sink_receiver&& r, std::exception_ptr e) noexcept { + r.value_ = e; + } -TEST_CASE("sync_wait returns value and exception", "[types][any_sender]") { - my_sender_of sender = just(21) | then([&](int v) { return 2 * v; }); - auto [value] = *sync_wait(std::move(sender)); - CHECK(value == 42); + friend void tag_invoke(set_stopped_t, sink_receiver&& r) noexcept { + r.value_ = set_stopped; + } - sender = just(21) | then([&](int v) { - throw 420; - return 2 * v; - }); - CHECK_THROWS_AS(sync_wait(std::move(sender)), int); -} + friend env tag_invoke(get_env_t, const sink_receiver& r) noexcept { + return {static_cast(&r)}; + } + }; -TEST_CASE("any_sender is connectable with any_receiver_ref", "[types][any_sender]") { - using Sigs = completion_signatures; - using receiver_ref = any_receiver_ref; - using sender = receiver_ref::any_sender<>; - REQUIRE(receiver_of); - sender sndr = just_stopped(); - { - sink_receiver rcvr{}; - receiver_ref ref = rcvr; - auto op = connect(std::move(sndr), std::move(ref)); - CHECK(rcvr.value_.index() == 0); - start(op); - CHECK(rcvr.value_.index() == 3); + TEST_CASE("any_receiver_ref is constructible from receivers", "[types][any_sender]") { + using Sigs = completion_signatures; + sink_receiver rcvr; + any_receiver_ref ref{rcvr}; + CHECK(receiver); + CHECK(receiver_of); + CHECK(!receiver_of>); + CHECK(std::is_copy_assignable_v>); + CHECK(std::is_constructible_v, const sink_receiver&>); + CHECK(!std::is_constructible_v, sink_receiver&&>); + CHECK(!std::is_constructible_v< + any_receiver_ref>, + const sink_receiver&>); + } + + TEST_CASE("any_receiver_ref is queryable", "[types][any_sender]") { + using Sigs = completion_signatures; + using receiver_ref = any_receiver_ref>; + sink_receiver rcvr1{}; + sink_receiver rcvr2{}; + receiver_ref ref1{rcvr1}; + receiver_ref ref2{rcvr2}; + CHECK(get_address(get_env(ref1)) == &rcvr1); + CHECK(get_address(get_env(ref2)) == &rcvr2); + { + receiver_ref copied_ref = ref2; + CHECK(get_address(get_env(copied_ref)) != &ref2); + CHECK(get_address(get_env(copied_ref)) == &rcvr2); + ref1 = copied_ref; + CHECK(get_address(get_env(ref1)) != &rcvr1); + CHECK(get_address(get_env(ref1)) != &copied_ref); + CHECK(get_address(get_env(ref1)) == &rcvr2); + copied_ref = rcvr1; + CHECK(get_address(get_env(ref1)) == &rcvr2); + CHECK(get_address(get_env(copied_ref)) == &rcvr1); + } + CHECK(get_address(get_env(ref1)) == &rcvr2); } - sndr = just(42); - { + + TEST_CASE("any_receiver_ref calls receiver methods", "[types][any_sender]") { + using Sigs = + completion_signatures; + using receiver_ref = any_receiver_ref; + REQUIRE(receiver_of); + sink_receiver value{}; + sink_receiver error{}; + sink_receiver stopped{}; + + // Check set value + CHECK(value.value_.index() == 0); + receiver_ref ref = value; + set_value((receiver_ref&&) ref, 42); + CHECK(value.value_.index() == 1); + CHECK(std::get<1>(value.value_) == 42); + // Check set error + CHECK(error.value_.index() == 0); + ref = error; + set_error((receiver_ref&&) ref, std::make_exception_ptr(42)); + CHECK(error.value_.index() == 2); + CHECK_THROWS_AS(std::rethrow_exception(std::get<2>(error.value_)), int); + // Check set stopped + CHECK(stopped.value_.index() == 0); + ref = stopped; + set_stopped((receiver_ref&&) ref); + CHECK(stopped.value_.index() == 3); + } + + TEST_CASE("any_receiver_ref is connectable with when_any", "[types][any_sender]") { + using Sigs = completion_signatures; + using receiver_ref = any_receiver_ref; + REQUIRE(receiver_of); sink_receiver rcvr{}; receiver_ref ref = rcvr; - auto op = connect(std::move(sndr), std::move(ref)); + + auto sndr = when_any(just(42)); CHECK(rcvr.value_.index() == 0); - start(op); - CHECK(rcvr.value_.index() == 1); - } - sndr = when_any(just(42)); - { - sink_receiver rcvr{}; - receiver_ref ref = rcvr; auto op = connect(std::move(sndr), std::move(ref)); - CHECK(rcvr.value_.index() == 0); start(op); CHECK(rcvr.value_.index() == 1); + CHECK(std::get<1>(rcvr.value_) == 42); } -} -class stopped_token { - private: - bool stopped_{true}; + /////////////////////////////////////////////////////////////////////////////// + // any.storage - struct __callback_type { - template - explicit __callback_type(stopped_token t, Fn&& f) noexcept { - if (t.stopped_) { - static_cast(f)(); - } + struct empty_vtable_t { + private: + template + friend empty_vtable_t* + tag_invoke(__any::__create_vtable_t, __mtype, __mtype) noexcept { + static empty_vtable_t vtable{}; + return &vtable; } }; - public: - constexpr stopped_token() noexcept = default; - - explicit constexpr stopped_token(bool stopped) noexcept - : stopped_{stopped} { - } - - template - using callback_type = __callback_type; - - static std::true_type stop_requested() noexcept { - return {}; - } - - static std::true_type stop_possible() noexcept { - return {}; - } - bool operator==(const stopped_token&) const noexcept = default; -}; + TEST_CASE("empty storage is movable", "[types][any_sender]") { + struct foo { }; -template -struct stopped_receiver_base { - using is_receiver = void; - Token stop_token_{}; -}; - -template -struct stopped_receiver_env { - const stopped_receiver_base* receiver_; - - friend Token tag_invoke(get_stop_token_t, const stopped_receiver_env& env) noexcept { - return env.receiver_->stop_token_; - } -}; + using any_unique = __any::__unique_storage_t; + any_unique s1{}; + any_unique s2 = foo{}; + static_assert(std::is_move_assignable_v); + static_assert(!std::is_copy_assignable_v); -template -struct stopped_receiver : stopped_receiver_base { - stopped_receiver(Token token, bool expect_stop) - : stopped_receiver_base{token} - , expect_stop_{expect_stop} { - } + CHECK(s2.__get_vtable()); + CHECK(s1.__get_vtable() != s2.__get_vtable()); + CHECK(s1.__get_object_pointer() == nullptr); + CHECK(s2.__get_object_pointer() != nullptr); + // Test SBO + std::intptr_t obj_ptr = reinterpret_cast(s2.__get_object_pointer()); + std::intptr_t s2_ptr = reinterpret_cast(&s2); + CHECK(std::abs(s2_ptr - obj_ptr) < std::intptr_t(sizeof(any_unique))); - bool expect_stop_{false}; + s1 = std::move(s2); + CHECK(s2.__get_vtable()); + CHECK(s1.__get_vtable() != s2.__get_vtable()); + CHECK(s1.__get_object_pointer() != nullptr); + CHECK(s2.__get_object_pointer() == nullptr); - template - friend void tag_invoke(set_value_t, const stopped_receiver& r, Args&&...) noexcept { - CHECK(!r.expect_stop_); + s1 = std::move(s2); + CHECK(s1.__get_object_pointer() == nullptr); + CHECK(s2.__get_object_pointer() == nullptr); } - friend void tag_invoke(set_stopped_t, const stopped_receiver& r) noexcept { - CHECK(r.expect_stop_); - } + TEST_CASE("empty storage is movable, throwing moves will allocate", "[types][any_sender]") { + struct move_throws { + move_throws() = default; - friend stopped_receiver_env tag_invoke(get_env_t, const stopped_receiver& r) noexcept { - return {&r}; - } -}; - -template -stopped_receiver(Token, bool) -> stopped_receiver; - -static_assert(receiver_of< - stopped_receiver, - completion_signatures>); - -TEST_CASE("any_sender - does connect with stop token", "[types][any_sender]") { - using stoppable_sender = any_sender_of; - stoppable_sender sender = when_any(just(21)); - in_place_stop_source stop_source{}; - stopped_receiver receiver{stop_source.get_token(), true}; - stop_source.request_stop(); - auto do_check = connect(std::move(sender), std::move(receiver)); - // This CHECKS whether set_value is called - start(do_check); -} + move_throws(move_throws&&) noexcept(false) { + } -TEST_CASE("any_sender - does connect with an user-defined stop token", "[types][any_sender]") { - using stoppable_sender = any_sender_of; - stoppable_sender sender = when_any(just(21)); - SECTION("stopped true") { - stopped_token token{true}; - stopped_receiver receiver{token, true}; - auto do_check = connect(std::move(sender), std::move(receiver)); - // This CHECKS whether set_value is called - start(do_check); - } - SECTION("stopped false") { - stopped_token token{false}; - stopped_receiver receiver{token, false}; - auto do_check = connect(std::move(sender), std::move(receiver)); - // This CHECKS whether set_value is called - start(do_check); + move_throws& operator=(move_throws&&) noexcept(false) { + return *this; + } + }; + + using any_unique = __any::__unique_storage_t; + any_unique s1{}; + any_unique s2 = move_throws{}; + static_assert(std::is_move_assignable_v); + static_assert(!std::is_copy_assignable_v); + + CHECK(s2.__get_vtable()); + CHECK(s1.__get_vtable() != s2.__get_vtable()); + CHECK(s1.__get_object_pointer() == nullptr); + CHECK(s2.__get_object_pointer() != nullptr); + // Test SBO + std::intptr_t obj_ptr = reinterpret_cast(s2.__get_object_pointer()); + std::intptr_t s2_ptr = reinterpret_cast(&s2); + CHECK(std::abs(s2_ptr - obj_ptr) >= std::intptr_t(sizeof(any_unique))); + + s1 = std::move(s2); + CHECK(s2.__get_vtable()); + CHECK(s1.__get_vtable() != s2.__get_vtable()); + CHECK(s1.__get_object_pointer() != nullptr); + CHECK(s2.__get_object_pointer() == nullptr); + + s1 = std::move(s2); + CHECK(s1.__get_object_pointer() == nullptr); + CHECK(s2.__get_object_pointer() == nullptr); + } + + /////////////////////////////////////////////////////////////////////////////// + // any_sender + + template + using any_sender_of = + typename any_receiver_ref>::template any_sender<>; + + TEST_CASE("any sender is a sender", "[types][any_sender]") { + CHECK(stdexec::sender>); + CHECK(std::is_move_assignable_v>); + CHECK(std::is_nothrow_move_assignable_v>); + CHECK(!std::is_copy_assignable_v>); + any_sender_of sender = just(); + } + + TEST_CASE("sync_wait works on any_sender_of", "[types][any_sender]") { + int value = 0; + any_sender_of sender = just(42) | then([&](int v) noexcept { value = v; }); + CHECK(std::same_as< + completion_signatures_of_t>, + completion_signatures>); + sync_wait(std::move(sender)); + CHECK(value == 42); + } + + TEST_CASE("construct any_sender_of recursively from when_all", "[types][any_sender]") { + any_sender_of sender = just(); + using sender_t = any_sender_of; + using when_all_t = decltype(when_all(std::move(sender))); + static_assert(std::is_constructible_v); + } + + TEST_CASE("sync_wait returns value", "[types][any_sender]") { + any_sender_of sender = just(21) | then([&](int v) noexcept { return 2 * v; }); + CHECK(std::same_as< + completion_signatures_of_t>, + completion_signatures>); + auto [value1] = *sync_wait(std::move(sender)); + CHECK(value1 == 42); + } + + template + using my_sender_of = any_sender_of; + + TEST_CASE("sync_wait returns value and exception", "[types][any_sender]") { + my_sender_of sender = just(21) | then([&](int v) { return 2 * v; }); + auto [value] = *sync_wait(std::move(sender)); + CHECK(value == 42); + + sender = just(21) | then([&](int v) { + throw 420; + return 2 * v; + }); + CHECK_THROWS_AS(sync_wait(std::move(sender)), int); + } + + TEST_CASE("any_sender is connectable with any_receiver_ref", "[types][any_sender]") { + using Sigs = completion_signatures; + using receiver_ref = any_receiver_ref; + using sender = receiver_ref::any_sender<>; + REQUIRE(receiver_of); + sender sndr = just_stopped(); + { + sink_receiver rcvr{}; + receiver_ref ref = rcvr; + auto op = connect(std::move(sndr), std::move(ref)); + CHECK(rcvr.value_.index() == 0); + start(op); + CHECK(rcvr.value_.index() == 3); + } + sndr = just(42); + { + sink_receiver rcvr{}; + receiver_ref ref = rcvr; + auto op = connect(std::move(sndr), std::move(ref)); + CHECK(rcvr.value_.index() == 0); + start(op); + CHECK(rcvr.value_.index() == 1); + } + sndr = when_any(just(42)); + { + sink_receiver rcvr{}; + receiver_ref ref = rcvr; + auto op = connect(std::move(sndr), std::move(ref)); + CHECK(rcvr.value_.index() == 0); + start(op); + CHECK(rcvr.value_.index() == 1); + } } -} - -TEST_CASE( - "any_sender - does connect with stop token if the get_stop_token query is registered with " - "in_place_stop_token", - "[types][any_sender]") { - using Sigs = completion_signatures; - using receiver_ref = - any_receiver_ref>; - using stoppable_sender = receiver_ref::any_sender<>; - stoppable_sender sender = when_any(just(21)); - in_place_stop_source stop_source{}; - stopped_receiver receiver{stop_source.get_token(), true}; - stop_source.request_stop(); - auto do_check = connect(std::move(sender), std::move(receiver)); - // This CHECKS whether a set_stopped is called - start(do_check); -} - -TEST_CASE( - "any_sender - does connect with stop token if the get_stop_token query is registered with " - "never_stop_token", - "[types][any_sender]") { - using Sigs = completion_signatures; - using receiver_ref = - any_receiver_ref>; - using unstoppable_sender = receiver_ref::any_sender<>; - unstoppable_sender sender = when_any(just(21)); - in_place_stop_source stop_source{}; - stopped_receiver receiver{stop_source.get_token(), false}; - stop_source.request_stop(); - auto do_check = connect(std::move(sender), std::move(receiver)); - // This CHECKS whether a set_stopped is called - start(do_check); -} - -/////////////////////////////////////////////////////////////////////////////// -// any_scheduler - -template -using my_scheduler = typename any_sender_of<>::any_scheduler; -TEST_CASE("any scheduler with inline_scheduler", "[types][any_sender]") { - static_assert(scheduler>); - my_scheduler<> scheduler = exec::inline_scheduler(); - my_scheduler<> copied = scheduler; - CHECK(copied == scheduler); + class stopped_token { + private: + bool stopped_{true}; - auto sched = schedule(scheduler); - static_assert(sender); - std::same_as> auto get_sched = get_completion_scheduler( - get_env(sched)); - CHECK(get_sched == scheduler); + struct __callback_type { + template + explicit __callback_type(stopped_token t, Fn&& f) noexcept { + if (t.stopped_) { + static_cast(f)(); + } + } + }; + public: + constexpr stopped_token() noexcept = default; - bool called = false; - sync_wait(std::move(sched) | then([&] { called = true; })); - CHECK(called); -} + explicit constexpr stopped_token(bool stopped) noexcept + : stopped_{stopped} { + } -TEST_CASE("queryable any_scheduler with inline_scheduler", "[types][any_sender]") { - using my_scheduler2 = - my_scheduler>; - static_assert(scheduler); - my_scheduler2 scheduler = exec::inline_scheduler(); - my_scheduler2 copied = scheduler; - CHECK(copied == scheduler); - - auto sched = schedule(scheduler); - static_assert(sender); - std::same_as auto get_sched = get_completion_scheduler( - get_env(sched)); - CHECK(get_sched == scheduler); - - CHECK( - get_forward_progress_guarantee(scheduler) - == get_forward_progress_guarantee(exec::inline_scheduler())); - - bool called = false; - sync_wait(std::move(sched) | then([&] { called = true; })); - CHECK(called); -} + template + using callback_type = __callback_type; -TEST_CASE("any_scheduler adds set_value_t() completion sig", "[types][any_scheduler][any_sender]") { - using scheduler_t = any_sender_of<>::any_scheduler<>; - using schedule_t = decltype(schedule(std::declval())); - CHECK( - std::is_same_v, completion_signatures>); - CHECK(scheduler); -} + static std::true_type stop_requested() noexcept { + return {}; + } -TEST_CASE( - "any_scheduler uniquely adds set_value_t() completion sig", - "[types][any_scheduler][any_sender]") { - using scheduler_t = any_sender_of::any_scheduler<>; - using schedule_t = decltype(schedule(std::declval())); - CHECK( - std::is_same_v, completion_signatures>); - CHECK(scheduler); -} + static std::true_type stop_possible() noexcept { + return {}; + } -TEST_CASE( - "any_scheduler adds set_value_t() completion sig (along with error)", - "[types][any_sender]") { - using scheduler_t = any_sender_of::any_scheduler<>; - using schedule_t = decltype(schedule(std::declval())); - CHECK(sender_of); - CHECK(sender_of); -} + bool operator==(const stopped_token&) const noexcept = default; + }; -TEST_CASE( - "User-defined completion_scheduler is ignored", - "[types][any_scheduler][any_sender]") { - using not_scheduler_t = // - any_receiver_ref> // - ::any_sender.signature> // - ::any_scheduler<>; - CHECK(scheduler); -} + template + struct stopped_receiver_base { + using is_receiver = void; + Token stop_token_{}; + }; -template -using stoppable_scheduler = any_sender_of::any_scheduler; + template + struct stopped_receiver_env { + const stopped_receiver_base* receiver_; -TEST_CASE("any scheduler with static_thread_pool", "[types][any_sender]") { - exec::static_thread_pool pool(1); - stoppable_scheduler<> scheduler = pool.get_scheduler(); - auto copied = scheduler; - CHECK(copied == scheduler); + friend Token tag_invoke(get_stop_token_t, const stopped_receiver_env& env) noexcept { + return env.receiver_->stop_token_; + } + }; - auto sched = schedule(scheduler); - static_assert(sender); - std::same_as> auto get_sched = get_completion_scheduler( - get_env(sched)); - CHECK(get_sched == scheduler); + template + struct stopped_receiver : stopped_receiver_base { + stopped_receiver(Token token, bool expect_stop) + : stopped_receiver_base{token} + , expect_stop_{expect_stop} { + } - bool called = false; - sync_wait(std::move(sched) | then([&] { called = true; })); - CHECK(called); -} + bool expect_stop_{false}; -TEST_CASE("queryable any_scheduler with static_thread_pool", "[types][any_sender]") { - using my_scheduler = - stoppable_scheduler>; + template + friend void tag_invoke(set_value_t, const stopped_receiver& r, Args&&...) noexcept { + CHECK(!r.expect_stop_); + } - exec::static_thread_pool pool(1); - my_scheduler scheduler = pool.get_scheduler(); - auto copied = scheduler; - CHECK(copied == scheduler); + friend void tag_invoke(set_stopped_t, const stopped_receiver& r) noexcept { + CHECK(r.expect_stop_); + } - auto sched = schedule(scheduler); - static_assert(sender); - std::same_as auto get_sched = get_completion_scheduler(get_env(sched)); - CHECK(get_sched == scheduler); + friend stopped_receiver_env tag_invoke(get_env_t, const stopped_receiver& r) noexcept { + return {&r}; + } + }; - CHECK( - get_forward_progress_guarantee(scheduler) - == get_forward_progress_guarantee(pool.get_scheduler())); + template + stopped_receiver(Token, bool) -> stopped_receiver; - bool called = false; - sync_wait(std::move(sched) | then([&] { called = true; })); - CHECK(called); -} + static_assert(receiver_of< + stopped_receiver, + completion_signatures>); -TEST_CASE("Scheduler with error handling and set_stopped", "[types][any_scheduler][any_sender]") { - using receiver_ref = - any_receiver_ref>; - using sender_t = receiver_ref::any_sender<>; - using scheduler_t = sender_t::any_scheduler<>; - scheduler_t scheduler = exec::inline_scheduler(); - { - auto op = connect(schedule(scheduler), expect_void_receiver{}); - start(op); - } - scheduler = stopped_scheduler(); - { - auto op = connect(schedule(scheduler), expect_stopped_receiver{}); - start(op); - } - scheduler = error_scheduler<>{std::make_exception_ptr(std::logic_error("test"))}; - { - auto op = connect(schedule(scheduler), expect_error_receiver<>{}); - start(op); + TEST_CASE("any_sender - does connect with stop token", "[types][any_sender]") { + using stoppable_sender = any_sender_of; + stoppable_sender sender = when_any(just(21)); + in_place_stop_source stop_source{}; + stopped_receiver receiver{stop_source.get_token(), true}; + stop_source.request_stop(); + auto do_check = connect(std::move(sender), std::move(receiver)); + // This CHECKS whether set_value is called + start(do_check); } -} -TEST_CASE("Schedule Sender lifetime", "[types][any_scheduler][any_sender]") { - using receiver_ref = - any_receiver_ref>; - using sender_t = receiver_ref::any_sender<>; - using scheduler_t = sender_t::any_scheduler<>; - scheduler_t scheduler = exec::inline_scheduler(); - auto sched = schedule(scheduler); - scheduler = stopped_scheduler(); - { - auto op = connect(schedule(scheduler), expect_stopped_receiver{}); - start(op); - } - { - auto op = connect(std::move(sched), expect_void_receiver{}); - start(op); + TEST_CASE("any_sender - does connect with an user-defined stop token", "[types][any_sender]") { + using stoppable_sender = any_sender_of; + stoppable_sender sender = when_any(just(21)); + SECTION("stopped true") { + stopped_token token{true}; + stopped_receiver receiver{token, true}; + auto do_check = connect(std::move(sender), std::move(receiver)); + // This CHECKS whether set_value is called + start(do_check); + } + SECTION("stopped false") { + stopped_token token{false}; + stopped_receiver receiver{token, false}; + auto do_check = connect(std::move(sender), std::move(receiver)); + // This CHECKS whether set_value is called + start(do_check); + } } -} -// A scheduler that counts how many instances are extant. -struct counting_scheduler { - using __id = counting_scheduler; - using __t = counting_scheduler; - - static int count; - - counting_scheduler() noexcept { - ++count; + TEST_CASE( + "any_sender - does connect with stop token if the get_stop_token query is registered with " + "in_place_stop_token", + "[types][any_sender]") { + using Sigs = completion_signatures; + using receiver_ref = + any_receiver_ref>; + using stoppable_sender = receiver_ref::any_sender<>; + stoppable_sender sender = when_any(just(21)); + in_place_stop_source stop_source{}; + stopped_receiver receiver{stop_source.get_token(), true}; + stop_source.request_stop(); + auto do_check = connect(std::move(sender), std::move(receiver)); + // This CHECKS whether a set_stopped is called + start(do_check); } - counting_scheduler(const counting_scheduler&) noexcept { - ++count; + TEST_CASE( + "any_sender - does connect with stop token if the get_stop_token query is registered with " + "never_stop_token", + "[types][any_sender]") { + using Sigs = completion_signatures; + using receiver_ref = + any_receiver_ref>; + using unstoppable_sender = receiver_ref::any_sender<>; + unstoppable_sender sender = when_any(just(21)); + in_place_stop_source stop_source{}; + stopped_receiver receiver{stop_source.get_token(), false}; + stop_source.request_stop(); + auto do_check = connect(std::move(sender), std::move(receiver)); + // This CHECKS whether a set_stopped is called + start(do_check); } - counting_scheduler(counting_scheduler&&) noexcept { - ++count; + /////////////////////////////////////////////////////////////////////////////// + // any_scheduler + + template + using my_scheduler = typename any_sender_of<>::any_scheduler; + + TEST_CASE("any scheduler with inline_scheduler", "[types][any_sender]") { + static_assert(scheduler>); + my_scheduler<> scheduler = exec::inline_scheduler(); + my_scheduler<> copied = scheduler; + CHECK(copied == scheduler); + + auto sched = schedule(scheduler); + static_assert(sender); + std::same_as> auto get_sched = get_completion_scheduler( + get_env(sched)); + CHECK(get_sched == scheduler); + + bool called = false; + sync_wait(std::move(sched) | then([&] { called = true; })); + CHECK(called); + } + + TEST_CASE("queryable any_scheduler with inline_scheduler", "[types][any_sender]") { + using my_scheduler2 = + my_scheduler>; + static_assert(scheduler); + my_scheduler2 scheduler = exec::inline_scheduler(); + my_scheduler2 copied = scheduler; + CHECK(copied == scheduler); + + auto sched = schedule(scheduler); + static_assert(sender); + std::same_as auto get_sched = get_completion_scheduler( + get_env(sched)); + CHECK(get_sched == scheduler); + + CHECK( + get_forward_progress_guarantee(scheduler) + == get_forward_progress_guarantee(exec::inline_scheduler())); + + bool called = false; + sync_wait(std::move(sched) | then([&] { called = true; })); + CHECK(called); + } + + TEST_CASE( + "any_scheduler adds set_value_t() completion sig", + "[types][any_scheduler][any_sender]") { + using scheduler_t = any_sender_of<>::any_scheduler<>; + using schedule_t = decltype(schedule(std::declval())); + CHECK( + std::is_same_v, completion_signatures>); + CHECK(scheduler); + } + + TEST_CASE( + "any_scheduler uniquely adds set_value_t() completion sig", + "[types][any_scheduler][any_sender]") { + using scheduler_t = any_sender_of::any_scheduler<>; + using schedule_t = decltype(schedule(std::declval())); + CHECK( + std::is_same_v, completion_signatures>); + CHECK(scheduler); + } + + TEST_CASE( + "any_scheduler adds set_value_t() completion sig (along with error)", + "[types][any_sender]") { + using scheduler_t = any_sender_of::any_scheduler<>; + using schedule_t = decltype(schedule(std::declval())); + CHECK(sender_of); + CHECK(sender_of); + } + + TEST_CASE( + "User-defined completion_scheduler is ignored", + "[types][any_scheduler][any_sender]") { + using not_scheduler_t = // + any_receiver_ref> // + ::any_sender.signature> // + ::any_scheduler<>; + CHECK(scheduler); + } + + template + using stoppable_scheduler = any_sender_of::any_scheduler; + + TEST_CASE("any scheduler with static_thread_pool", "[types][any_sender]") { + exec::static_thread_pool pool(1); + stoppable_scheduler<> scheduler = pool.get_scheduler(); + auto copied = scheduler; + CHECK(copied == scheduler); + + auto sched = schedule(scheduler); + static_assert(sender); + std::same_as> auto get_sched = get_completion_scheduler( + get_env(sched)); + CHECK(get_sched == scheduler); + + bool called = false; + sync_wait(std::move(sched) | then([&] { called = true; })); + CHECK(called); + } + + TEST_CASE("queryable any_scheduler with static_thread_pool", "[types][any_sender]") { + using my_scheduler = + stoppable_scheduler>; + + exec::static_thread_pool pool(1); + my_scheduler scheduler = pool.get_scheduler(); + auto copied = scheduler; + CHECK(copied == scheduler); + + auto sched = schedule(scheduler); + static_assert(sender); + std::same_as auto get_sched = get_completion_scheduler( + get_env(sched)); + CHECK(get_sched == scheduler); + + CHECK( + get_forward_progress_guarantee(scheduler) + == get_forward_progress_guarantee(pool.get_scheduler())); + + bool called = false; + sync_wait(std::move(sched) | then([&] { called = true; })); + CHECK(called); + } + + TEST_CASE("Scheduler with error handling and set_stopped", "[types][any_scheduler][any_sender]") { + using receiver_ref = + any_receiver_ref>; + using sender_t = receiver_ref::any_sender<>; + using scheduler_t = sender_t::any_scheduler<>; + scheduler_t scheduler = exec::inline_scheduler(); + { + auto op = connect(schedule(scheduler), expect_void_receiver{}); + start(op); + } + scheduler = stopped_scheduler(); + { + auto op = connect(schedule(scheduler), expect_stopped_receiver{}); + start(op); + } + scheduler = error_scheduler<>{std::make_exception_ptr(std::logic_error("test"))}; + { + auto op = connect(schedule(scheduler), expect_error_receiver<>{}); + start(op); + } } - ~counting_scheduler() { - --count; + TEST_CASE("Schedule Sender lifetime", "[types][any_scheduler][any_sender]") { + using receiver_ref = + any_receiver_ref>; + using sender_t = receiver_ref::any_sender<>; + using scheduler_t = sender_t::any_scheduler<>; + scheduler_t scheduler = exec::inline_scheduler(); + auto sched = schedule(scheduler); + scheduler = stopped_scheduler(); + { + auto op = connect(schedule(scheduler), expect_stopped_receiver{}); + start(op); + } + { + auto op = connect(std::move(sched), expect_void_receiver{}); + start(op); + } } - bool operator==(const counting_scheduler&) const noexcept = default; + // A scheduler that counts how many instances are extant. + struct counting_scheduler { + using __id = counting_scheduler; + using __t = counting_scheduler; - private: - template - struct operation : immovable { - R recv_; + static int count; - friend void tag_invoke(ex::start_t, operation& self) noexcept { - ex::set_value((R&&) self.recv_); + counting_scheduler() noexcept { + ++count; } - }; - - struct sender { - using __id = sender; - using __t = sender; - using is_sender = void; - using completion_signatures = ex::completion_signatures; + counting_scheduler(const counting_scheduler&) noexcept { + ++count; + } - template - friend operation tag_invoke(ex::connect_t, sender self, R r) { - return {{}, (R&&) r}; + counting_scheduler(counting_scheduler&&) noexcept { + ++count; } - friend auto tag_invoke(ex::get_completion_scheduler_t, sender) noexcept - -> counting_scheduler { - return {}; + ~counting_scheduler() { + --count; } - friend const sender& tag_invoke(ex::get_env_t, const sender& self) noexcept { - return self; + bool operator==(const counting_scheduler&) const noexcept = default; + + private: + template + struct operation : immovable { + R recv_; + + friend void tag_invoke(ex::start_t, operation& self) noexcept { + ex::set_value((R&&) self.recv_); + } + }; + + struct sender { + using __id = sender; + using __t = sender; + + using is_sender = void; + using completion_signatures = ex::completion_signatures; + + template + friend operation tag_invoke(ex::connect_t, sender self, R r) { + return {{}, (R&&) r}; + } + + friend auto tag_invoke(ex::get_completion_scheduler_t, sender) noexcept + -> counting_scheduler { + return {}; + } + + friend const sender& tag_invoke(ex::get_env_t, const sender& self) noexcept { + return self; + } + }; + + friend sender tag_invoke(ex::schedule_t, counting_scheduler) noexcept { + return {}; } }; - friend sender tag_invoke(ex::schedule_t, counting_scheduler) noexcept { - return {}; - } -}; - -int counting_scheduler::count = 0; - -TEST_CASE( - "check that any_scheduler cleans up all resources", - "[types][any_scheduler][any_sender]") { - using receiver_ref = any_receiver_ref>; - using sender_t = receiver_ref::any_sender<>; - using scheduler_t = sender_t::any_scheduler<>; - { - scheduler_t scheduler = exec::inline_scheduler{}; - scheduler = counting_scheduler{}; + int counting_scheduler::count = 0; + + TEST_CASE( + "check that any_scheduler cleans up all resources", + "[types][any_scheduler][any_sender]") { + using receiver_ref = any_receiver_ref>; + using sender_t = receiver_ref::any_sender<>; + using scheduler_t = sender_t::any_scheduler<>; { - auto op = connect(schedule(scheduler), expect_value_receiver<>{}); - start(op); + scheduler_t scheduler = exec::inline_scheduler{}; + scheduler = counting_scheduler{}; + { + auto op = connect(schedule(scheduler), expect_value_receiver<>{}); + start(op); + } } + CHECK(counting_scheduler::count == 0); } - CHECK(counting_scheduler::count == 0); -} \ No newline at end of file +} diff --git a/test/exec/test_at_coroutine_exit.cpp b/test/exec/test_at_coroutine_exit.cpp index 4b5613497..7b6d9e31c 100644 --- a/test/exec/test_at_coroutine_exit.cpp +++ b/test/exec/test_at_coroutine_exit.cpp @@ -273,174 +273,179 @@ namespace { #endif // REQUIRE_TERMINATE -} // unnamed namespace + TEST_CASE("OneCleanupAction", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_one_cleanup_action(result)); + REQUIRE(result == 4); + } + + TEST_CASE("TwoCleanupActions", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_two_cleanup_actions(result)); + REQUIRE(result == 8); + } + + TEST_CASE("OnStoppedTwoCleanupActions", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_on_stopped_two_cleanup_actions_with_stop(result)); + REQUIRE(result == 8); + } + + TEST_CASE("OneCleanupActionWithContinuation", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(with_continuation(result, test_one_cleanup_action(result))); + REQUIRE(result == 12); + } + + TEST_CASE("TwoCleanupActionsWithContinuation", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(with_continuation(result, test_two_cleanup_actions(result))); + REQUIRE(result == 24); + } + + TEST_CASE("CleanupActionWithSender", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_sender_cleanup_action(result)); + REQUIRE(result == 1); + } + + TEST_CASE("OneCleanupActionWithStop", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_one_cleanup_action_with_stop(result)); + REQUIRE(result == 2); + } + + TEST_CASE("OnSucceededOneCleanupAction", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_on_succeeded_one_cleanup_action(result)); + REQUIRE(result == 6); + } + + TEST_CASE("OnSucceededOneCleanupActionWithStop", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_on_succeeded_one_cleanup_action_with_stop(result)); + REQUIRE(result == 1); + } + + TEST_CASE("OnSucceededOneCleanupActionWithError", "[task][at_coroutine_exit]") { + int result = 0; + CHECK_THROWS_AS( + stdexec::sync_wait(test_on_succeeded_one_cleanup_action_with_error(result)), int); + REQUIRE(result == 1); + } + + TEST_CASE("OnStoppedOneCleanupActionSuccess", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_on_stopped_one_cleanup_action(result)); + REQUIRE(result == 2); + } + + TEST_CASE("OnStoppedOneCleanupActionWithStop", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_on_stopped_one_cleanup_action_with_stop(result)); + REQUIRE(result == 3); + } + + TEST_CASE("OnStoppedOneCleanupActionWithError", "[task][at_coroutine_exit]") { + int result = 0; + CHECK_THROWS_AS(stdexec::sync_wait(test_on_stopped_one_cleanup_action_with_error(result)), int); + REQUIRE(result == 1); + } + + TEST_CASE("OnFailedOneCleanupActionSuccess", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_on_failed_one_cleanup_action(result)); + REQUIRE(result == 2); + } -TEST_CASE("OneCleanupAction", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_one_cleanup_action(result)); - REQUIRE(result == 4); -} - -TEST_CASE("TwoCleanupActions", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_two_cleanup_actions(result)); - REQUIRE(result == 8); -} - -TEST_CASE("OnStoppedTwoCleanupActions", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_on_stopped_two_cleanup_actions_with_stop(result)); - REQUIRE(result == 8); -} - -TEST_CASE("OneCleanupActionWithContinuation", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(with_continuation(result, test_one_cleanup_action(result))); - REQUIRE(result == 12); -} - -TEST_CASE("TwoCleanupActionsWithContinuation", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(with_continuation(result, test_two_cleanup_actions(result))); - REQUIRE(result == 24); -} - -TEST_CASE("CleanupActionWithSender", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_sender_cleanup_action(result)); - REQUIRE(result == 1); -} - -TEST_CASE("OneCleanupActionWithStop", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_one_cleanup_action_with_stop(result)); - REQUIRE(result == 2); -} - -TEST_CASE("OnSucceededOneCleanupAction", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_on_succeeded_one_cleanup_action(result)); - REQUIRE(result == 6); -} - -TEST_CASE("OnSucceededOneCleanupActionWithStop", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_on_succeeded_one_cleanup_action_with_stop(result)); - REQUIRE(result == 1); -} - -TEST_CASE("OnSucceededOneCleanupActionWithError", "[task][at_coroutine_exit]") { - int result = 0; - CHECK_THROWS_AS(stdexec::sync_wait(test_on_succeeded_one_cleanup_action_with_error(result)), int); - REQUIRE(result == 1); -} - -TEST_CASE("OnStoppedOneCleanupActionSuccess", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_on_stopped_one_cleanup_action(result)); - REQUIRE(result == 2); -} - -TEST_CASE("OnStoppedOneCleanupActionWithStop", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_on_stopped_one_cleanup_action_with_stop(result)); - REQUIRE(result == 3); -} - -TEST_CASE("OnStoppedOneCleanupActionWithError", "[task][at_coroutine_exit]") { - int result = 0; - CHECK_THROWS_AS(stdexec::sync_wait(test_on_stopped_one_cleanup_action_with_error(result)), int); - REQUIRE(result == 1); -} - -TEST_CASE("OnFailedOneCleanupActionSuccess", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_on_failed_one_cleanup_action(result)); - REQUIRE(result == 2); -} - -TEST_CASE("OnFailedOneCleanupActionWithStop", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_on_failed_one_cleanup_action_with_stop(result)); - REQUIRE(result == 1); -} - -TEST_CASE("OnFailedOneCleanupActionWithError", "[task][at_coroutine_exit]") { - int result = 0; - CHECK_THROWS_AS(stdexec::sync_wait(test_on_failed_one_cleanup_action_with_error(result)), int); - REQUIRE(result == 3); -} - -TEST_CASE("TwoCleanupActionsWithStop", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_two_cleanup_actions_with_stop(result)); - REQUIRE(result == 2); -} - -TEST_CASE("OneCleanupActionWithContinuationAndStop", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(with_continuation(result, test_one_cleanup_action_with_stop(result))); - REQUIRE(result == 2); -} - -TEST_CASE("TwoCleanupActionsWithContinuationAndStop", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(with_continuation(result, test_two_cleanup_actions_with_stop(result))); - REQUIRE(result == 2); -} - -TEST_CASE("CleanupActionWithStatefulSender", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_stateful_cleanup_action(result, 42)); - REQUIRE(result == 42); -} - -TEST_CASE("CleanupActionWithMutableStateful", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_mutable_stateful_cleanup_action(result)); - REQUIRE(result == 10); -} - -TEST_CASE("OnSuccessCleanupActionWithMutableStateful", "[task][at_coroutine_exit]") { - int result = 0; - stdexec::sync_wait(test_on_succeeded_mutable_stateful_cleanup_action(result)); - REQUIRE(result == 10); -} + TEST_CASE("OnFailedOneCleanupActionWithStop", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_on_failed_one_cleanup_action_with_stop(result)); + REQUIRE(result == 1); + } + + TEST_CASE("OnFailedOneCleanupActionWithError", "[task][at_coroutine_exit]") { + int result = 0; + CHECK_THROWS_AS(stdexec::sync_wait(test_on_failed_one_cleanup_action_with_error(result)), int); + REQUIRE(result == 3); + } + + TEST_CASE("TwoCleanupActionsWithStop", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_two_cleanup_actions_with_stop(result)); + REQUIRE(result == 2); + } + + TEST_CASE("OneCleanupActionWithContinuationAndStop", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(with_continuation(result, test_one_cleanup_action_with_stop(result))); + REQUIRE(result == 2); + } + + TEST_CASE("TwoCleanupActionsWithContinuationAndStop", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(with_continuation(result, test_two_cleanup_actions_with_stop(result))); + REQUIRE(result == 2); + } + + TEST_CASE("CleanupActionWithStatefulSender", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_stateful_cleanup_action(result, 42)); + REQUIRE(result == 42); + } + + TEST_CASE("CleanupActionWithMutableStateful", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_mutable_stateful_cleanup_action(result)); + REQUIRE(result == 10); + } + + TEST_CASE("OnSuccessCleanupActionWithMutableStateful", "[task][at_coroutine_exit]") { + int result = 0; + stdexec::sync_wait(test_on_succeeded_mutable_stateful_cleanup_action(result)); + REQUIRE(result == 10); + } #ifdef REQUIRE_TERMINATE -TEST_CASE("CancelInCleanupActionCallsTerminate", "[task][at_coroutine_exit]") { - int result = 0; - test_cancel_in_cleanup_action_causes_death(result); -} - -TEST_CASE("CancelDuringCancellationUnwindCallsTerminate", "[task][at_coroutine_exit]") { - int result = 0; - test_cancel_during_cancellation_unwind_causes_death(result); -} - -TEST_CASE("ThrowInCleanupActionCallsTerminate", "[task][at_coroutine_exit]") { - int result = 0; - test_throw_in_cleanup_action_causes_death(result); -} - -TEST_CASE("ThrowInCleanupActionDuringExceptionUnwindCallsTerminate", "[task][at_coroutine_exit]") { - int result = 0; - test_throw_in_cleanup_action_during_exception_unwind_causes_death(result); -} - -TEST_CASE("CancelInCleanupActionDuringExceptionUnwindCallsTerminate", "[task][at_coroutine_exit]") { - int result = 0; - test_cancel_in_cleanup_action_during_exception_unwind_causes_death(result); -} - -TEST_CASE( - "ThrowInCleanupActionDuringCancellationUnwindCallsTerminate", - "[task][at_coroutine_exit]") { - int result = 0; - test_throw_in_cleanup_action_during_cancellation_unwind_causes_death(result); -} + TEST_CASE("CancelInCleanupActionCallsTerminate", "[task][at_coroutine_exit]") { + int result = 0; + test_cancel_in_cleanup_action_causes_death(result); + } + + TEST_CASE("CancelDuringCancellationUnwindCallsTerminate", "[task][at_coroutine_exit]") { + int result = 0; + test_cancel_during_cancellation_unwind_causes_death(result); + } + + TEST_CASE("ThrowInCleanupActionCallsTerminate", "[task][at_coroutine_exit]") { + int result = 0; + test_throw_in_cleanup_action_causes_death(result); + } + + TEST_CASE( + "ThrowInCleanupActionDuringExceptionUnwindCallsTerminate", + "[task][at_coroutine_exit]") { + int result = 0; + test_throw_in_cleanup_action_during_exception_unwind_causes_death(result); + } + + TEST_CASE( + "CancelInCleanupActionDuringExceptionUnwindCallsTerminate", + "[task][at_coroutine_exit]") { + int result = 0; + test_cancel_in_cleanup_action_during_exception_unwind_causes_death(result); + } + + TEST_CASE( + "ThrowInCleanupActionDuringCancellationUnwindCallsTerminate", + "[task][at_coroutine_exit]") { + int result = 0; + test_throw_in_cleanup_action_during_cancellation_unwind_causes_death(result); + } #endif // REQUIRE_TERMINATE +} // unnamed namespace + #endif // !STDEXEC_STD_NO_COROUTINES_ diff --git a/test/exec/test_create.cpp b/test/exec/test_create.cpp index 891cbbbfd..6a0887357 100644 --- a/test/exec/test_create.cpp +++ b/test/exec/test_create.cpp @@ -55,44 +55,44 @@ namespace { pool_.get_scheduler(), ex::then(ex::just(), [=]() noexcept { completed(context); }))); } }; -} // anonymous namespace -TEST_CASE_METHOD( - create_test_fixture, - "wrap an async API that computes a result", - "[detail][create]") { - auto snd = [this](int a, int b) { - return exec::create([a, b, this](Context& ctx) noexcept { - anIntAPI(a, b, &ctx, [](void* pv, int result) { - ex::set_value(std::move(static_cast(pv)->receiver), (int) result); + TEST_CASE_METHOD( + create_test_fixture, + "wrap an async API that computes a result", + "[detail][create]") { + auto snd = [this](int a, int b) { + return exec::create([a, b, this](Context& ctx) noexcept { + anIntAPI(a, b, &ctx, [](void* pv, int result) { + ex::set_value(std::move(static_cast(pv)->receiver), (int) result); + }); }); - }); - }(1, 2); + }(1, 2); - REQUIRE_NOTHROW([&] { - auto [res] = stdexec::sync_wait(std::move(snd)).value(); - CHECK(res == 3); - }()); -} + REQUIRE_NOTHROW([&] { + auto [res] = stdexec::sync_wait(std::move(snd)).value(); + CHECK(res == 3); + }()); + } -TEST_CASE_METHOD( - create_test_fixture, - "wrap an async API that doesn't compute a result", - "[detail][create]") { - bool called = false; - auto snd = [&called, this]() { - return exec::create( - [this](Context& ctx) noexcept { - aVoidAPI(&ctx, [](void* pv) { - Context& ctx = *static_cast(pv); - *std::get<0>(ctx.args) = true; - ex::set_value(std::move(ctx.receiver)); - }); - }, - &called); - }(); + TEST_CASE_METHOD( + create_test_fixture, + "wrap an async API that doesn't compute a result", + "[detail][create]") { + bool called = false; + auto snd = [&called, this]() { + return exec::create( + [this](Context& ctx) noexcept { + aVoidAPI(&ctx, [](void* pv) { + Context& ctx = *static_cast(pv); + *std::get<0>(ctx.args) = true; + ex::set_value(std::move(ctx.receiver)); + }); + }, + &called); + }(); - std::optional> res = stdexec::sync_wait(std::move(snd)); - CHECK(res.has_value()); - CHECK(called); -} + std::optional> res = stdexec::sync_wait(std::move(snd)); + CHECK(res.has_value()); + CHECK(called); + } +} // anonymous namespace diff --git a/test/exec/test_env.cpp b/test/exec/test_env.cpp index 6c0d6c2d5..e9f5a4c34 100644 --- a/test/exec/test_env.cpp +++ b/test/exec/test_env.cpp @@ -41,21 +41,21 @@ namespace { return stdexec::tag_invoke(*this, e); } } bar{}; -} -TEST_CASE("Test make_env works", "[env]") { - auto e = exec::make_env(exec::with(foo, 42)); - CHECK(foo(e) == 42); + TEST_CASE("Test make_env works", "[env]") { + auto e = exec::make_env(exec::with(foo, 42)); + CHECK(foo(e) == 42); - auto e2 = exec::make_env(e, exec::with(bar, 43)); - CHECK(foo(e2) == 42); - CHECK(bar(e2) == 43); + auto e2 = exec::make_env(e, exec::with(bar, 43)); + CHECK(foo(e2) == 42); + CHECK(bar(e2) == 43); - auto e3 = exec::make_env(e2, exec::with(foo, 44)); - CHECK(foo(e3) == 44); - CHECK(bar(e3) == 43); + auto e3 = exec::make_env(e2, exec::with(foo, 44)); + CHECK(foo(e3) == 44); + CHECK(bar(e3) == 43); - auto e4 = exec::make_env(e3, exec::with(foo)); - STATIC_REQUIRE(!std::invocable); - CHECK(bar(e4) == 43); + auto e4 = exec::make_env(e3, exec::with(foo)); + STATIC_REQUIRE(!std::invocable); + CHECK(bar(e4) == 43); + } } diff --git a/test/exec/test_finally.cpp b/test/exec/test_finally.cpp index be3c9dfa7..5eb258976 100644 --- a/test/exec/test_finally.cpp +++ b/test/exec/test_finally.cpp @@ -5,66 +5,69 @@ using namespace stdexec; -template -struct __mall_contained_in { - template - using __f = __mand<__mapply<__contains, Haystack>...>; -}; +namespace { -template -using __all_contained_in = __mapply<__mall_contained_in, Needles>; + template + struct __mall_contained_in { + template + using __f = __mand<__mapply<__contains, Haystack>...>; + }; -template -using __equivalent = __mand< - __all_contained_in, - std::is_same<__mapply<__msize, Needles>, __mapply<__msize, Haystack>>>; + template + using __all_contained_in = __mapply<__mall_contained_in, Needles>; -TEST_CASE("finally is a sender", "[adaptors][finally]") { - auto s = exec::finally(just(), just()); - STATIC_REQUIRE(sender); -} + template + using __equivalent = __mand< + __all_contained_in, + std::is_same<__mapply<__msize, Needles>, __mapply<__msize, Haystack>>>; -TEST_CASE("finally is a sender in empty env", "[adaptors][finally]") { - auto s = exec::finally(just(), just()); - STATIC_REQUIRE(sender_in); - STATIC_REQUIRE(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); -} + TEST_CASE("finally is a sender", "[adaptors][finally]") { + auto s = exec::finally(just(), just()); + STATIC_REQUIRE(sender); + } -TEST_CASE("finally executes the final action", "[adaptors][finally]") { - bool called = false; - auto s = exec::finally(just(), just() | then([&called]() noexcept { called = true; })); - STATIC_REQUIRE(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); - sync_wait(s); - CHECK(called); -} + TEST_CASE("finally is a sender in empty env", "[adaptors][finally]") { + auto s = exec::finally(just(), just()); + STATIC_REQUIRE(sender_in); + STATIC_REQUIRE(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + } -TEST_CASE("finally executes the final action and returns integer", "[adaptors][finally]") { - bool called = false; - auto s = exec::finally(just(42), just() | then([&called]() noexcept { called = true; })); - STATIC_REQUIRE(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); - auto [i] = *sync_wait(s); - CHECK(called); - CHECK(i == 42); -} + TEST_CASE("finally executes the final action", "[adaptors][finally]") { + bool called = false; + auto s = exec::finally(just(), just() | then([&called]() noexcept { called = true; })); + STATIC_REQUIRE(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + sync_wait(s); + CHECK(called); + } + + TEST_CASE("finally executes the final action and returns integer", "[adaptors][finally]") { + bool called = false; + auto s = exec::finally(just(42), just() | then([&called]() noexcept { called = true; })); + STATIC_REQUIRE(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + auto [i] = *sync_wait(s); + CHECK(called); + CHECK(i == 42); + } -TEST_CASE("finally does not execute the final action and throws integer", "[adaptors][finally]") { - bool called = false; + TEST_CASE("finally does not execute the final action and throws integer", "[adaptors][finally]") { + bool called = false; - auto s = exec::finally( - just(21) | then([](int x) { - throw 42; - return x; - }), - just() | then([&called]() noexcept { called = true; })); - STATIC_REQUIRE(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); - CHECK_THROWS_AS(sync_wait(s), int); - CHECK(called); + auto s = exec::finally( + just(21) | then([](int x) { + throw 42; + return x; + }), + just() | then([&called]() noexcept { called = true; })); + STATIC_REQUIRE(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + CHECK_THROWS_AS(sync_wait(s), int); + CHECK(called); + } } diff --git a/test/exec/test_io_uring_context.cpp b/test/exec/test_io_uring_context.cpp index c7fc0824c..0516940e4 100644 --- a/test/exec/test_io_uring_context.cpp +++ b/test/exec/test_io_uring_context.cpp @@ -28,146 +28,79 @@ using namespace stdexec; using namespace exec; using namespace std::chrono_literals; -// clang-12 does not know jthread yet -class jthread { - std::thread thread_; +namespace { - public: - template - explicit jthread(Callable&& callable, Args&&... args) - : thread_(std::forward(callable), std::forward(args)...) { - } - - jthread(jthread&& other) noexcept - : thread_(std::move(other.thread_)) { - } + // clang-12 does not know jthread yet + class jthread { + std::thread thread_; - jthread& operator=(jthread&& other) noexcept { - thread_ = std::move(other.thread_); - return *this; - } - - ~jthread() { - if (thread_.joinable()) { - thread_.join(); + public: + template + explicit jthread(Callable&& callable, Args&&... args) + : thread_(std::forward(callable), std::forward(args)...) { } - } - std::thread::id get_id() const noexcept { - return thread_.get_id(); - } -}; + jthread(jthread&& other) noexcept + : thread_(std::move(other.thread_)) { + } -TEST_CASE("io_uring_context - unused context", "[types][io_uring][schedulers]") { - io_uring_context context; - CHECK(context.is_running() == false); -} + jthread& operator=(jthread&& other) noexcept { + thread_ = std::move(other.thread_); + return *this; + } -TEST_CASE("io_uring_context Satisfy concepts", "[types][io_uring][schedulers]") { - STATIC_REQUIRE(timed_scheduler); - STATIC_REQUIRE_FALSE(std::is_move_assignable_v); -} + ~jthread() { + if (thread_.joinable()) { + thread_.join(); + } + } -TEST_CASE("io_uring_context Schedule runs in io thread", "[types][io_uring][schedulers]") { - io_uring_context context; - io_uring_scheduler scheduler = context.get_scheduler(); - jthread io_thread{[&] { - context.run_until_stopped(); - }}; - { - scope_guard guard{[&]() noexcept { - context.request_stop(); - }}; - bool is_called = false; - sync_wait(schedule(scheduler) | then([&] { - CHECK(io_thread.get_id() == std::this_thread::get_id()); - is_called = true; - })); - CHECK(is_called); + std::thread::id get_id() const noexcept { + return thread_.get_id(); + } + }; - is_called = false; - sync_wait(schedule_after(scheduler, 1ms) | then([&] { - CHECK(io_thread.get_id() == std::this_thread::get_id()); - is_called = true; - })); - CHECK(is_called); + TEST_CASE("io_uring_context - unused context", "[types][io_uring][schedulers]") { + io_uring_context context; + CHECK(context.is_running() == false); } -} - -TEST_CASE( - "io_uring_context Call io_uring::run_until_empty with start_detached", - "[types][io_uring][schedulers]") { - io_uring_context context; - io_uring_scheduler scheduler = context.get_scheduler(); - bool is_called = false; - start_detached(schedule(scheduler) | then([&] { - CHECK(context.is_running()); - is_called = true; - })); - context.run_until_empty(); - CHECK(is_called); - CHECK(!context.is_running()); - CHECK(!context.stop_requested()); -} -TEST_CASE( - "io_uring_context Call io_uring::run_until_empty with sync_wait", - "[types][io_uring][schedulers]") { - io_uring_context context; - io_uring_scheduler scheduler = context.get_scheduler(); - auto just_run = just() | then([&] { context.run_until_empty(); }); - bool is_called = false; - sync_wait(when_all( - schedule_after(scheduler, 500us) | then([&] { - CHECK(context.is_running()); - is_called = true; - }), - just_run)); - CHECK(is_called); - CHECK(!context.is_running()); - CHECK(!context.stop_requested()); -} + TEST_CASE("io_uring_context Satisfy concepts", "[types][io_uring][schedulers]") { + STATIC_REQUIRE(timed_scheduler); + STATIC_REQUIRE_FALSE(std::is_move_assignable_v); + } -TEST_CASE( - "io_uring_context Call io_uring::run with sync_wait and when_any", - "[types][io_uring][schedulers]") { - io_uring_context context; - io_uring_scheduler scheduler = context.get_scheduler(); - bool is_called = false; - sync_wait(when_any( - schedule_after(scheduler, 500us) | then([&] { - CHECK(context.is_running()); - is_called = true; - }), - context.run())); - CHECK(is_called); - CHECK(!context.is_running()); - CHECK(context.stop_requested()); -} + TEST_CASE("io_uring_context Schedule runs in io thread", "[types][io_uring][schedulers]") { + io_uring_context context; + io_uring_scheduler scheduler = context.get_scheduler(); + jthread io_thread{[&] { + context.run_until_stopped(); + }}; + { + scope_guard guard{[&]() noexcept { + context.request_stop(); + }}; + bool is_called = false; + sync_wait(schedule(scheduler) | then([&] { + CHECK(io_thread.get_id() == std::this_thread::get_id()); + is_called = true; + })); + CHECK(is_called); -TEST_CASE( - "io_uring_context Call io_uring::run with sync_wait and when_all", - "[types][io_uring][schedulers]") { - io_uring_context context; - io_uring_scheduler scheduler = context.get_scheduler(); - bool is_called = false; - sync_wait(when_all( - schedule_after(scheduler, 500us) | then([&] { - CHECK(context.is_running()); - is_called = true; - }), - context.run(until::empty))); - CHECK(is_called); - CHECK(!context.is_running()); - CHECK(!context.stop_requested()); -} + is_called = false; + sync_wait(schedule_after(scheduler, 1ms) | then([&] { + CHECK(io_thread.get_id() == std::this_thread::get_id()); + is_called = true; + })); + CHECK(is_called); + } + } -TEST_CASE( - "io_uring_context Explicitly stop the io_uring_context", - "[types][io_uring][schedulers]") { - io_uring_context context; - io_uring_scheduler scheduler = context.get_scheduler(); - { + TEST_CASE( + "io_uring_context Call io_uring::run_until_empty with start_detached", + "[types][io_uring][schedulers]") { + io_uring_context context; + io_uring_scheduler scheduler = context.get_scheduler(); bool is_called = false; start_detached(schedule(scheduler) | then([&] { CHECK(context.is_running()); @@ -178,158 +111,228 @@ TEST_CASE( CHECK(!context.is_running()); CHECK(!context.stop_requested()); } - context.request_stop(); - CHECK(context.stop_requested()); - context.run_until_stopped(); - CHECK(context.stop_requested()); - bool is_stopped = false; - sync_wait(schedule(scheduler) | then([&] { CHECK(false); }) | stdexec::upon_stopped([&] { - is_stopped = true; - })); - CHECK(is_stopped); -} -TEST_CASE( - "io_uring_context Thread-safe to schedule from multiple threads", - "[types][io_uring][schedulers]") { - io_uring_context context; - io_uring_scheduler scheduler = context.get_scheduler(); - jthread io_thread{[&] { - context.run_until_stopped(); - }}; - auto fn = [&] { - CHECK(io_thread.get_id() == std::this_thread::get_id()); - }; - { - scope_guard guard{[&]() noexcept { - context.request_stop(); - }}; - jthread thread1{[&] { - for (int i = 0; i < 10; ++i) { - sync_wait(when_all( - schedule(scheduler) | then(fn), - schedule(scheduler) | then(fn), - schedule(scheduler) | then(fn), - schedule(scheduler) | then(fn), - schedule(scheduler) | then(fn), - schedule(scheduler) | then(fn), - schedule(scheduler) | then(fn), - schedule(scheduler) | then(fn), - schedule(scheduler) | then(fn), - schedule(scheduler) | then(fn))); - } - }}; - jthread thread2{[&] { - for (int i = 0; i < 10; ++i) { - auto tp = std::chrono::steady_clock::now() + 500us; - sync_wait(when_all( - schedule_at(scheduler, tp) | then(fn), - schedule_at(scheduler, tp) | then(fn), - schedule_at(scheduler, tp) | then(fn), - schedule_at(scheduler, tp) | then(fn), - schedule_at(scheduler, tp) | then(fn), - schedule_at(scheduler, tp) | then(fn), - schedule_at(scheduler, tp) | then(fn), - schedule_at(scheduler, tp) | then(fn), - schedule_at(scheduler, tp) | then(fn), - schedule_at(scheduler, tp) | then(fn))); - } - }}; - jthread thread3{[&] { - for (int i = 0; i < 10; ++i) { - sync_wait(when_all( - schedule_after(scheduler, 500us) | then(fn), - schedule_after(scheduler, 500us) | then(fn), - schedule_after(scheduler, 500us) | then(fn), - schedule_after(scheduler, 500us) | then(fn), - schedule_after(scheduler, 500us) | then(fn), - schedule_after(scheduler, 500us) | then(fn), - schedule_after(scheduler, 500us) | then(fn), - schedule_after(scheduler, 500us) | then(fn), - schedule_after(scheduler, 500us) | then(fn), - schedule_after(scheduler, 500us) | then(fn))); - } - }}; + TEST_CASE( + "io_uring_context Call io_uring::run_until_empty with sync_wait", + "[types][io_uring][schedulers]") { + io_uring_context context; + io_uring_scheduler scheduler = context.get_scheduler(); + auto just_run = just() | then([&] { context.run_until_empty(); }); + bool is_called = false; + sync_wait(when_all( + schedule_after(scheduler, 500us) | then([&] { + CHECK(context.is_running()); + is_called = true; + }), + just_run)); + CHECK(is_called); + CHECK(!context.is_running()); + CHECK(!context.stop_requested()); } -} -TEST_CASE("io_uring_context Stop io_uring_context", "[types][io_uring][schedulers]") { - io_uring_context context; - io_uring_scheduler scheduler = context.get_scheduler(); - jthread io_thread{[&] { - context.run_until_stopped(); - }}; - { - single_thread_context ctx1{}; - auto sch1 = ctx1.get_scheduler(); + TEST_CASE( + "io_uring_context Call io_uring::run with sync_wait and when_any", + "[types][io_uring][schedulers]") { + io_uring_context context; + io_uring_scheduler scheduler = context.get_scheduler(); bool is_called = false; - sync_wait(finally( - schedule(scheduler) | then([&]() noexcept { is_called = true; }), - schedule(sch1) | then([&]() noexcept { context.request_stop(); }))); + sync_wait(when_any( + schedule_after(scheduler, 500us) | then([&] { + CHECK(context.is_running()); + is_called = true; + }), + context.run())); CHECK(is_called); + CHECK(!context.is_running()); + CHECK(context.stop_requested()); } - bool is_called = false; - sync_wait(schedule(scheduler) | then([&] { is_called = true; })); - CHECK_FALSE(is_called); -} -TEST_CASE("io_uring_context schedule_after 0s", "[types][io_uring][schedulers]") { - io_uring_context context; - io_uring_scheduler scheduler = context.get_scheduler(); - jthread io_thread{[&] { - context.run_until_stopped(); - }}; - { - scope_guard guard{[&]() noexcept { - context.request_stop(); - }}; + TEST_CASE( + "io_uring_context Call io_uring::run with sync_wait and when_all", + "[types][io_uring][schedulers]") { + io_uring_context context; + io_uring_scheduler scheduler = context.get_scheduler(); bool is_called = false; - sync_wait(when_any( - schedule_after(scheduler, 0s) | then([&] { - CHECK(io_thread.get_id() == std::this_thread::get_id()); + sync_wait(when_all( + schedule_after(scheduler, 500us) | then([&] { + CHECK(context.is_running()); is_called = true; }), - schedule_after(scheduler, 5ms))); + context.run(until::empty))); CHECK(is_called); + CHECK(!context.is_running()); + CHECK(!context.stop_requested()); } -} -TEST_CASE("io_uring_context schedule_after -1s", "[types][io_uring][schedulers]") { - io_uring_context context; - io_uring_scheduler scheduler = context.get_scheduler(); - jthread io_thread{[&] { + TEST_CASE( + "io_uring_context Explicitly stop the io_uring_context", + "[types][io_uring][schedulers]") { + io_uring_context context; + io_uring_scheduler scheduler = context.get_scheduler(); + { + bool is_called = false; + start_detached(schedule(scheduler) | then([&] { + CHECK(context.is_running()); + is_called = true; + })); + context.run_until_empty(); + CHECK(is_called); + CHECK(!context.is_running()); + CHECK(!context.stop_requested()); + } + context.request_stop(); + CHECK(context.stop_requested()); context.run_until_stopped(); - }}; - { - scope_guard guard{[&]() noexcept { - context.request_stop(); + CHECK(context.stop_requested()); + bool is_stopped = false; + sync_wait(schedule(scheduler) | then([&] { CHECK(false); }) | stdexec::upon_stopped([&] { + is_stopped = true; + })); + CHECK(is_stopped); + } + + TEST_CASE( + "io_uring_context Thread-safe to schedule from multiple threads", + "[types][io_uring][schedulers]") { + io_uring_context context; + io_uring_scheduler scheduler = context.get_scheduler(); + jthread io_thread{[&] { + context.run_until_stopped(); }}; - bool is_called_1 = false; - bool is_called_2 = false; - auto start = std::chrono::steady_clock::now(); - auto timeout = 100ms; - sync_wait(when_any( - schedule_after(scheduler, -1s) | then([&] { - CHECK(io_thread.get_id() == std::this_thread::get_id()); - is_called_1 = true; - }), - schedule_after(scheduler, timeout) | then([&] { is_called_2 = true; }))); - auto end = std::chrono::steady_clock::now(); - std::chrono::nanoseconds diff = end - start; - CHECK(diff.count() < std::chrono::duration_cast(timeout).count()); - CHECK(is_called_1 == true); - CHECK(is_called_2 == false); + auto fn = [&] { + CHECK(io_thread.get_id() == std::this_thread::get_id()); + }; + { + scope_guard guard{[&]() noexcept { + context.request_stop(); + }}; + jthread thread1{[&] { + for (int i = 0; i < 10; ++i) { + sync_wait(when_all( + schedule(scheduler) | then(fn), + schedule(scheduler) | then(fn), + schedule(scheduler) | then(fn), + schedule(scheduler) | then(fn), + schedule(scheduler) | then(fn), + schedule(scheduler) | then(fn), + schedule(scheduler) | then(fn), + schedule(scheduler) | then(fn), + schedule(scheduler) | then(fn), + schedule(scheduler) | then(fn))); + } + }}; + jthread thread2{[&] { + for (int i = 0; i < 10; ++i) { + auto tp = std::chrono::steady_clock::now() + 500us; + sync_wait(when_all( + schedule_at(scheduler, tp) | then(fn), + schedule_at(scheduler, tp) | then(fn), + schedule_at(scheduler, tp) | then(fn), + schedule_at(scheduler, tp) | then(fn), + schedule_at(scheduler, tp) | then(fn), + schedule_at(scheduler, tp) | then(fn), + schedule_at(scheduler, tp) | then(fn), + schedule_at(scheduler, tp) | then(fn), + schedule_at(scheduler, tp) | then(fn), + schedule_at(scheduler, tp) | then(fn))); + } + }}; + jthread thread3{[&] { + for (int i = 0; i < 10; ++i) { + sync_wait(when_all( + schedule_after(scheduler, 500us) | then(fn), + schedule_after(scheduler, 500us) | then(fn), + schedule_after(scheduler, 500us) | then(fn), + schedule_after(scheduler, 500us) | then(fn), + schedule_after(scheduler, 500us) | then(fn), + schedule_after(scheduler, 500us) | then(fn), + schedule_after(scheduler, 500us) | then(fn), + schedule_after(scheduler, 500us) | then(fn), + schedule_after(scheduler, 500us) | then(fn), + schedule_after(scheduler, 500us) | then(fn))); + } + }}; + } + } + + TEST_CASE("io_uring_context Stop io_uring_context", "[types][io_uring][schedulers]") { + io_uring_context context; + io_uring_scheduler scheduler = context.get_scheduler(); + jthread io_thread{[&] { + context.run_until_stopped(); + }}; + { + single_thread_context ctx1{}; + auto sch1 = ctx1.get_scheduler(); + bool is_called = false; + sync_wait(finally( + schedule(scheduler) | then([&]() noexcept { is_called = true; }), + schedule(sch1) | then([&]() noexcept { context.request_stop(); }))); + CHECK(is_called); + } + bool is_called = false; + sync_wait(schedule(scheduler) | then([&] { is_called = true; })); + CHECK_FALSE(is_called); + } + + TEST_CASE("io_uring_context schedule_after 0s", "[types][io_uring][schedulers]") { + io_uring_context context; + io_uring_scheduler scheduler = context.get_scheduler(); + jthread io_thread{[&] { + context.run_until_stopped(); + }}; + { + scope_guard guard{[&]() noexcept { + context.request_stop(); + }}; + bool is_called = false; + sync_wait(when_any( + schedule_after(scheduler, 0s) | then([&] { + CHECK(io_thread.get_id() == std::this_thread::get_id()); + is_called = true; + }), + schedule_after(scheduler, 5ms))); + CHECK(is_called); + } } -} -TEST_CASE("io_uring_context - reuse context after being used", "[types][io_uring][schedulers]") { - io_uring_context context; - io_uring_scheduler scheduler = context.get_scheduler(); - CHECK(sync_wait(exec::when_any(schedule(scheduler), context.run()))); - CHECK(!sync_wait(exec::when_any(schedule(scheduler), context.run()))); - context.reset(); - CHECK(sync_wait(exec::when_any(schedule(scheduler), context.run()))); - CHECK(!sync_wait(exec::when_any(schedule(scheduler), context.run()))); + TEST_CASE("io_uring_context schedule_after -1s", "[types][io_uring][schedulers]") { + io_uring_context context; + io_uring_scheduler scheduler = context.get_scheduler(); + jthread io_thread{[&] { + context.run_until_stopped(); + }}; + { + scope_guard guard{[&]() noexcept { + context.request_stop(); + }}; + bool is_called_1 = false; + bool is_called_2 = false; + auto start = std::chrono::steady_clock::now(); + auto timeout = 100ms; + sync_wait(when_any( + schedule_after(scheduler, -1s) | then([&] { + CHECK(io_thread.get_id() == std::this_thread::get_id()); + is_called_1 = true; + }), + schedule_after(scheduler, timeout) | then([&] { is_called_2 = true; }))); + auto end = std::chrono::steady_clock::now(); + std::chrono::nanoseconds diff = end - start; + CHECK(diff.count() < std::chrono::duration_cast(timeout).count()); + CHECK(is_called_1 == true); + CHECK(is_called_2 == false); + } + } + + TEST_CASE("io_uring_context - reuse context after being used", "[types][io_uring][schedulers]") { + io_uring_context context; + io_uring_scheduler scheduler = context.get_scheduler(); + CHECK(sync_wait(exec::when_any(schedule(scheduler), context.run()))); + CHECK(!sync_wait(exec::when_any(schedule(scheduler), context.run()))); + context.reset(); + CHECK(sync_wait(exec::when_any(schedule(scheduler), context.run()))); + CHECK(!sync_wait(exec::when_any(schedule(scheduler), context.run()))); + } } #endif diff --git a/test/exec/test_materialize.cpp b/test/exec/test_materialize.cpp index d5976a79f..aedb31852 100644 --- a/test/exec/test_materialize.cpp +++ b/test/exec/test_materialize.cpp @@ -22,63 +22,65 @@ namespace stdexec { using namespace stdexec; using namespace exec; +namespace { -template - requires __completion_tag> -using __dematerialize_value = completion_signatures(_Args...)>; + template + requires __completion_tag> + using __dematerialize_value = completion_signatures(_Args...)>; -TEST_CASE("materialize completion signatures", "[adaptors][materialize]") { - auto just_ = materialize(just()); - static_assert(sender); - static_assert(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); - auto demat_just = dematerialize(just_); - static_assert(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); + TEST_CASE("materialize completion signatures", "[adaptors][materialize]") { + auto just_ = materialize(just()); + static_assert(sender); + static_assert(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + auto demat_just = dematerialize(just_); + static_assert(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); - auto just_string = materialize(just(std::string("foo"))); - static_assert(sender); - static_assert(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); + auto just_string = materialize(just(std::string("foo"))); + static_assert(sender); + static_assert(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); - auto demat_just_string = dematerialize(just_string); - static_assert(std::same_as< - completion_signatures_of_t, - completion_signatures>); + auto demat_just_string = dematerialize(just_string); + static_assert(std::same_as< + completion_signatures_of_t, + completion_signatures>); - auto just_stopped_ = materialize(just_stopped()); - static_assert(sender); - static_assert(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); - auto demat_just_stopped = dematerialize(just_stopped_); - static_assert(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); - // wait_for_value(std::move(snd), movable(42)); -} + auto just_stopped_ = materialize(just_stopped()); + static_assert(sender); + static_assert(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + auto demat_just_stopped = dematerialize(just_stopped_); + static_assert(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + // wait_for_value(std::move(snd), movable(42)); + } -TEST_CASE("materialize value", "[adaptors][materialize]") { - auto just_42 = materialize(just(42)); - auto [tag, i] = *sync_wait(just_42); - static_assert(std::same_as); - static_assert(std::same_as); - CHECK(i == 42); - auto [tag2, i2] = *sync_wait(std::move(just_42)); - CHECK(i2 == 42); -} + TEST_CASE("materialize value", "[adaptors][materialize]") { + auto just_42 = materialize(just(42)); + auto [tag, i] = *sync_wait(just_42); + static_assert(std::same_as); + static_assert(std::same_as); + CHECK(i == 42); + auto [tag2, i2] = *sync_wait(std::move(just_42)); + CHECK(i2 == 42); + } -TEST_CASE("materialize stop", "[adaptors][materialize]") { - auto just_stop = materialize(just_stopped()); - auto [tag] = *sync_wait(just_stop); - static_assert(std::same_as); -} + TEST_CASE("materialize stop", "[adaptors][materialize]") { + auto just_stop = materialize(just_stopped()); + auto [tag] = *sync_wait(just_stop); + static_assert(std::same_as); + } -TEST_CASE("dematerialize value", "[adaptors][materialize]") { - auto just_42 = dematerialize(materialize(just(42))); - auto [i] = *sync_wait(just_42); - CHECK(i == 42); + TEST_CASE("dematerialize value", "[adaptors][materialize]") { + auto just_42 = dematerialize(materialize(just(42))); + auto [i] = *sync_wait(just_42); + CHECK(i == 42); + } } diff --git a/test/exec/test_on.cpp b/test/exec/test_on.cpp index 53e7a49fd..0d1eef3d6 100644 --- a/test/exec/test_on.cpp +++ b/test/exec/test_on.cpp @@ -25,174 +25,178 @@ namespace ex = stdexec; -template -inline auto _with_scheduler(Sched sched = {}) { - return exec::write(exec::with(ex::get_scheduler, std::move(sched))); -} - -template -inline auto _make_env_with_sched(Sched sched = {}) { - return exec::make_env(exec::with(ex::get_scheduler, std::move(sched))); -} - -using _env_with_sched_t = decltype(_make_env_with_sched()); - -TEST_CASE("exec::on returns a sender", "[adaptors][exec::on]") { - auto snd = exec::on(inline_scheduler{}, ex::just(13)); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("exec::on with environment returns a sender", "[adaptors][exec::on]") { - auto snd = exec::on(inline_scheduler{}, ex::just(13)); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("exec::on simple example", "[adaptors][exec::on]") { - auto snd = exec::on(inline_scheduler{}, ex::just(13)); - auto op = ex::connect( - std::move(snd), expect_value_receiver{env_tag{}, _make_env_with_sched(), 13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("exec::on calls the receiver when the scheduler dictates", "[adaptors][exec::on]") { - int recv_value{0}; - impulse_scheduler sched; - auto env = _make_env_with_sched(); - auto snd = exec::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{env, recv_value}); - ex::start(op); - // Up until this point, the scheduler didn't start any task; no effect expected - CHECK(recv_value == 0); - - // Tell the scheduler to start executing one task - sched.start_next(); - CHECK(recv_value == 13); -} - -TEST_CASE("exec::on calls the given sender when the scheduler dictates", "[adaptors][exec::on]") { - bool called{false}; - auto snd_base = ex::just() // - | ex::then([&]() -> int { - called = true; - return 19; - }); - - int recv_value{0}; - impulse_scheduler sched; - auto env = _make_env_with_sched(); - auto snd = exec::on(sched, std::move(snd_base)); - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{env, recv_value}); - ex::start(op); - // Up until this point, the scheduler didn't start any task - // The base sender shouldn't be started - CHECK_FALSE(called); - - // Tell the scheduler to start executing one task - sched.start_next(); - - // Now the base sender is called, and a value is sent to the receiver - CHECK(called); - CHECK(recv_value == 19); -} - -TEST_CASE("exec::on works when changing threads", "[adaptors][exec::on]") { - exec::static_thread_pool pool{2}; - bool called{false}; - // launch some work on the thread pool - ex::sender auto snd = exec::on(pool.get_scheduler(), ex::just()) // - | ex::then([&] { called = true; }) | _with_scheduler(); - stdexec::sync_wait(std::move(snd)); - // the work should be executed - REQUIRE(called); -} - -TEST_CASE("exec::on can be called with rvalue ref scheduler", "[adaptors][exec::on]") { - auto env = _make_env_with_sched(); - auto snd = exec::on(inline_scheduler{}, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver{env_tag{}, env, 13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("exec::on can be called with const ref scheduler", "[adaptors][exec::on]") { - auto env = _make_env_with_sched(); - const inline_scheduler sched; - auto snd = exec::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver{env_tag{}, env, 13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("exec::on can be called with ref scheduler", "[adaptors][exec::on]") { - auto env = _make_env_with_sched(); - inline_scheduler sched; - auto snd = exec::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver{env_tag{}, env, 13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("exec::on forwards set_error calls", "[adaptors][exec::on]") { - auto env = _make_env_with_sched(); - error_scheduler sched{std::exception_ptr{}}; - auto snd = exec::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_error_receiver{env, std::exception_ptr{}}); - ex::start(op); - // The receiver checks if we receive an error -} - -TEST_CASE("exec::on forwards set_error calls of other types", "[adaptors][exec::on]") { - auto env = _make_env_with_sched(); - error_scheduler sched{std::string{"error"}}; - auto snd = exec::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_error_receiver{env, std::string{"error"}}); - ex::start(op); - // The receiver checks if we receive an error -} - -TEST_CASE("exec::on forwards set_stopped calls", "[adaptors][exec::on]") { - auto env = _make_env_with_sched(); - stopped_scheduler sched{}; - auto snd = exec::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{env}); - ex::start(op); - // The receiver checks if we receive the stopped signal -} - -TEST_CASE( - "exec::on has the values_type corresponding to the given values", - "[adaptors][exec::on]") { - inline_scheduler sched{}; - - check_val_types>>(exec::on(sched, ex::just(1)) | _with_scheduler()); - check_val_types>>( - exec::on(sched, ex::just(3, 0.14)) | _with_scheduler()); - check_val_types>>( - exec::on(sched, ex::just(3, 0.14, std::string{"pi"})) | _with_scheduler()); -} - -TEST_CASE("exec::on keeps error_types from scheduler's sender", "[adaptors][exec::on]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{43}; - - check_err_types>( - exec::on(sched1, ex::just(1)) | _with_scheduler()); - check_err_types>( - exec::on(sched2, ex::just(2)) | _with_scheduler()); - check_err_types>( - exec::on(sched3, ex::just(3)) | _with_scheduler()); -} - -TEST_CASE("exec::on keeps sends_stopped from scheduler's sender", "[adaptors][exec::on]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - stopped_scheduler sched3{}; - - check_sends_stopped(exec::on(sched1, ex::just(1)) | _with_scheduler()); - check_sends_stopped(exec::on(sched2, ex::just(2)) | _with_scheduler()); - check_sends_stopped(exec::on(sched3, ex::just(3)) | _with_scheduler()); +namespace { + + template + inline auto _with_scheduler(Sched sched = {}) { + return exec::write(exec::with(ex::get_scheduler, std::move(sched))); + } + + template + inline auto _make_env_with_sched(Sched sched = {}) { + return exec::make_env(exec::with(ex::get_scheduler, std::move(sched))); + } + + using _env_with_sched_t = decltype(_make_env_with_sched()); + + TEST_CASE("exec::on returns a sender", "[adaptors][exec::on]") { + auto snd = exec::on(inline_scheduler{}, ex::just(13)); + static_assert(ex::sender); + (void) snd; + } + + TEST_CASE("exec::on with environment returns a sender", "[adaptors][exec::on]") { + auto snd = exec::on(inline_scheduler{}, ex::just(13)); + static_assert(ex::sender_in); + (void) snd; + } + + TEST_CASE("exec::on simple example", "[adaptors][exec::on]") { + auto snd = exec::on(inline_scheduler{}, ex::just(13)); + auto op = ex::connect( + std::move(snd), expect_value_receiver{env_tag{}, _make_env_with_sched(), 13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("exec::on calls the receiver when the scheduler dictates", "[adaptors][exec::on]") { + int recv_value{0}; + impulse_scheduler sched; + auto env = _make_env_with_sched(); + auto snd = exec::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{env, recv_value}); + ex::start(op); + // Up until this point, the scheduler didn't start any task; no effect expected + CHECK(recv_value == 0); + + // Tell the scheduler to start executing one task + sched.start_next(); + CHECK(recv_value == 13); + } + + TEST_CASE("exec::on calls the given sender when the scheduler dictates", "[adaptors][exec::on]") { + bool called{false}; + auto snd_base = ex::just() // + | ex::then([&]() -> int { + called = true; + return 19; + }); + + int recv_value{0}; + impulse_scheduler sched; + auto env = _make_env_with_sched(); + auto snd = exec::on(sched, std::move(snd_base)); + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{env, recv_value}); + ex::start(op); + // Up until this point, the scheduler didn't start any task + // The base sender shouldn't be started + CHECK_FALSE(called); + + // Tell the scheduler to start executing one task + sched.start_next(); + + // Now the base sender is called, and a value is sent to the receiver + CHECK(called); + CHECK(recv_value == 19); + } + + TEST_CASE("exec::on works when changing threads", "[adaptors][exec::on]") { + exec::static_thread_pool pool{2}; + bool called{false}; + // launch some work on the thread pool + ex::sender auto snd = exec::on(pool.get_scheduler(), ex::just()) // + | ex::then([&] { called = true; }) | _with_scheduler(); + stdexec::sync_wait(std::move(snd)); + // the work should be executed + REQUIRE(called); + } + + TEST_CASE("exec::on can be called with rvalue ref scheduler", "[adaptors][exec::on]") { + auto env = _make_env_with_sched(); + auto snd = exec::on(inline_scheduler{}, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver{env_tag{}, env, 13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("exec::on can be called with const ref scheduler", "[adaptors][exec::on]") { + auto env = _make_env_with_sched(); + const inline_scheduler sched; + auto snd = exec::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver{env_tag{}, env, 13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("exec::on can be called with ref scheduler", "[adaptors][exec::on]") { + auto env = _make_env_with_sched(); + inline_scheduler sched; + auto snd = exec::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver{env_tag{}, env, 13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("exec::on forwards set_error calls", "[adaptors][exec::on]") { + auto env = _make_env_with_sched(); + error_scheduler sched{std::exception_ptr{}}; + auto snd = exec::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_error_receiver{env, std::exception_ptr{}}); + ex::start(op); + // The receiver checks if we receive an error + } + + TEST_CASE("exec::on forwards set_error calls of other types", "[adaptors][exec::on]") { + auto env = _make_env_with_sched(); + error_scheduler sched{std::string{"error"}}; + auto snd = exec::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_error_receiver{env, std::string{"error"}}); + ex::start(op); + // The receiver checks if we receive an error + } + + TEST_CASE("exec::on forwards set_stopped calls", "[adaptors][exec::on]") { + auto env = _make_env_with_sched(); + stopped_scheduler sched{}; + auto snd = exec::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{env}); + ex::start(op); + // The receiver checks if we receive the stopped signal + } + + TEST_CASE( + "exec::on has the values_type corresponding to the given values", + "[adaptors][exec::on]") { + inline_scheduler sched{}; + + check_val_types>>( + exec::on(sched, ex::just(1)) | _with_scheduler()); + check_val_types>>( + exec::on(sched, ex::just(3, 0.14)) | _with_scheduler()); + check_val_types>>( + exec::on(sched, ex::just(3, 0.14, std::string{"pi"})) | _with_scheduler()); + } + + TEST_CASE("exec::on keeps error_types from scheduler's sender", "[adaptors][exec::on]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{43}; + + check_err_types>( + exec::on(sched1, ex::just(1)) | _with_scheduler()); + check_err_types>( + exec::on(sched2, ex::just(2)) | _with_scheduler()); + check_err_types>( + exec::on(sched3, ex::just(3)) | _with_scheduler()); + } + + TEST_CASE("exec::on keeps sends_stopped from scheduler's sender", "[adaptors][exec::on]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + stopped_scheduler sched3{}; + + check_sends_stopped(exec::on(sched1, ex::just(1)) | _with_scheduler()); + check_sends_stopped(exec::on(sched2, ex::just(2)) | _with_scheduler()); + check_sends_stopped(exec::on(sched3, ex::just(3)) | _with_scheduler()); + } } diff --git a/test/exec/test_on2.cpp b/test/exec/test_on2.cpp index 544bc51b9..7af217369 100644 --- a/test/exec/test_on2.cpp +++ b/test/exec/test_on2.cpp @@ -24,350 +24,353 @@ namespace ex = stdexec; -template -inline auto _with_scheduler(Sched sched = {}) { - return exec::write(exec::with(ex::get_scheduler, std::move(sched))); -} - -TEST_CASE( - "exec::on transitions back to the receiver's scheduler when completing with a value", - "[adaptors][exec::on]") { - bool called{false}; - auto snd_base = ex::just() // - | ex::then([&]() -> int { - called = true; - return 19; - }); - - int recv_value{0}; - impulse_scheduler sched1; - impulse_scheduler sched2; - auto snd = exec::on(sched1, std::move(snd_base)) | _with_scheduler(sched2); - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); - ex::start(op); - // Up until this point, the scheduler didn't start any task - // The base sender shouldn't be started - CHECK_FALSE(called); - - // Tell sched1 to start executing one task - sched1.start_next(); - - // Now the base sender is called, and execution is transfered to sched2 - CHECK(called); - CHECK(recv_value == 0); - - // Tell sched2 to start executing one task - sched2.start_next(); - - // Now the base sender is called, and a value is sent to the receiver - CHECK(recv_value == 19); -} - -TEST_CASE( - "exec::on transitions back to the receiver's scheduler when completing with an error", - "[adaptors][exec::on]") { - bool called{false}; - auto snd_base = ex::just() // - | ex::let_value([&]() { - called = true; - return ex::just_error(19); - }); - - int recv_error{0}; - impulse_scheduler sched1; - impulse_scheduler sched2; - auto snd = exec::on(sched1, std::move(snd_base)) | _with_scheduler(sched2); - auto op = ex::connect(std::move(snd), expect_error_receiver_ex{recv_error}); - ex::start(op); - // Up until this point, the scheduler didn't start any task - // The base sender shouldn't be started - CHECK_FALSE(called); - - // Tell sched1 to start executing one task - sched1.start_next(); - - // Now the base sender is called, and execution is transfered to sched2 - CHECK(called); - CHECK(recv_error == 0); - - // Tell sched2 to start executing one task - sched2.start_next(); - - // Now the base sender is called, and an error is sent to the receiver - CHECK(recv_error == 19); -} - -TEST_CASE( - "inner on transitions back to outer on's scheduler when completing with a value", - "[adaptors][exec::on]") { - bool called{false}; - auto snd_base = ex::just() // - | ex::then([&]() -> int { - called = true; - return 19; - }); - - int recv_value{0}; - impulse_scheduler sched1; - impulse_scheduler sched2; - impulse_scheduler sched3; - auto snd = exec::on(sched1, exec::on(sched2, std::move(snd_base))) | _with_scheduler(sched3); - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); - ex::start(op); - // Up until this point, the scheduler didn't start any task - // The base sender shouldn't be started - CHECK_FALSE(called); - - // Tell sched1 to start executing one task. This will post - // work to sched2 - sched1.start_next(); - - // The base sender shouldn't be started - CHECK_FALSE(called); - - // Tell sched2 to start executing one task. This will execute - // the base sender and post work back to sched1 - sched2.start_next(); - - // Now the base sender is called, and execution is transfered back - // to sched1 - CHECK(called); - CHECK(recv_value == 0); - - // Tell sched1 to start executing one task. This will post work to - // sched3 - sched1.start_next(); - - // The final receiver still hasn't been called - CHECK(recv_value == 0); - - // Tell sched3 to start executing one task. It should call the - // final receiver - sched3.start_next(); - - // Now the value is sent to the receiver - CHECK(recv_value == 19); -} - -TEST_CASE( - "inner on transitions back to outer on's scheduler when completing with an error", - "[adaptors][exec::on]") { - bool called{false}; - auto snd_base = ex::just() // - | ex::let_value([&]() { - called = true; - return ex::just_error(19); - }); - - int recv_error{0}; - impulse_scheduler sched1; - impulse_scheduler sched2; - impulse_scheduler sched3; - auto snd = exec::on(sched1, exec::on(sched2, std::move(snd_base))) | _with_scheduler(sched3); - auto op = ex::connect(std::move(snd), expect_error_receiver_ex{recv_error}); - ex::start(op); - // Up until this point, the scheduler didn't start any task - // The base sender shouldn't be started - CHECK_FALSE(called); - - // Tell sched1 to start executing one task. This will post - // work to sched2 - sched1.start_next(); - - // The base sender shouldn't be started - CHECK_FALSE(called); - - // Tell sched2 to start executing one task. This will execute - // the base sender and post work back to sched1 - sched2.start_next(); - - // Now the base sender is called, and execution is transfered back - // to sched1 - CHECK(called); - CHECK(recv_error == 0); - - // Tell sched1 to start executing one task. This will post work to - // sched3 - sched1.start_next(); - - // The final receiver still hasn't been called - CHECK(recv_error == 0); - - // Tell sched3 to start executing one task. It should call the - // final receiver - sched3.start_next(); - - // Now the error is sent to the receiver - CHECK(recv_error == 19); -} - -TEST_CASE( - "exec::on(closure) transitions onto and back off of the scheduler when completing with a value", - "[adaptors][exec::on]") { - bool called{false}; - auto closure = ex::then([&]() -> int { - called = true; - return 19; - }); - - int recv_value{0}; - impulse_scheduler sched1; - impulse_scheduler sched2; - auto snd = ex::just() | exec::on(sched1, std::move(closure)) | _with_scheduler(sched2); - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); - ex::start(op); - // Up until this point, the scheduler didn't start any task - // The closure shouldn't be started - CHECK_FALSE(called); - - // Tell sched1 to start executing one task - sched1.start_next(); - - // Now the closure is called, and execution is transfered to sched2 - CHECK(called); - CHECK(recv_value == 0); - - // Tell sched2 to start executing one task - sched2.start_next(); - - // Now the closure is called, and a value is sent to the receiver - CHECK(recv_value == 19); -} - -TEST_CASE( - "exec::on(closure) transitions onto and back off of the scheduler when completing with " - "an error", - "[adaptors][exec::on]") { - bool called{false}; - auto closure = ex::let_value([&]() { - called = true; - return ex::just_error(19); - }); - - int recv_error{0}; - impulse_scheduler sched1; - impulse_scheduler sched2; - auto snd = ex::just() | exec::on(sched1, std::move(closure)) | _with_scheduler(sched2); - auto op = ex::connect(std::move(snd), expect_error_receiver_ex{recv_error}); - ex::start(op); - // Up until this point, the scheduler didn't start any task - // The closure shouldn't be started - CHECK_FALSE(called); - - // Tell sched1 to start executing one task - sched1.start_next(); - - // Now the closure is called, and execution is transfered to sched2 - CHECK(called); - CHECK(recv_error == 0); - - // Tell sched2 to start executing one task - sched2.start_next(); - - // Now the closure is called, and a error is sent to the receiver - CHECK(recv_error == 19); -} - -TEST_CASE( - "inner on(closure) transitions back to outer on's scheduler when completing with a value", - "[adaptors][exec::on]") { - bool called{false}; - auto closure = ex::then([&](int i) -> int { - called = true; - return i; - }); - - int recv_value{0}; - impulse_scheduler sched1; - impulse_scheduler sched2; - impulse_scheduler sched3; - auto snd = exec::on(sched1, ex::just(19)) | exec::on(sched2, std::move(closure)) - | _with_scheduler(sched3); - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); - ex::start(op); - // Up until this point, the scheduler didn't start any task - // The closure shouldn't be started - CHECK_FALSE(called); - - // Tell sched1 to start executing one task. This will post - // work to sched3 - sched1.start_next(); - - // The closure shouldn't be started - CHECK_FALSE(called); - - // Tell sched3 to start executing one task. This post work to - // sched2. - sched3.start_next(); - - // The closure shouldn't be started - CHECK_FALSE(called); - - // Tell sched2 to start executing one task. This will execute - // the closure and post work back to sched3 - sched2.start_next(); - - // Now the closure is called, and execution is transfered back - // to sched3 - CHECK(called); - CHECK(recv_value == 0); - - // Tell sched3 to start executing one task. This will call the - // receiver - sched3.start_next(); - - // Now the value is sent to the receiver - CHECK(recv_value == 19); -} - -TEST_CASE( - "inner on(closure) transitions back to outer on's scheduler when completing with an error", - "[adaptors][exec::on]") { - bool called{false}; - auto closure = ex::let_value([&](int i) { - called = true; - return ex::just_error(i); - }); - - int recv_error{0}; - impulse_scheduler sched1; - impulse_scheduler sched2; - impulse_scheduler sched3; - auto snd = exec::on(sched1, ex::just(19)) | exec::on(sched2, std::move(closure)) - | _with_scheduler(sched3); - auto op = ex::connect(std::move(snd), expect_error_receiver_ex{recv_error}); - ex::start(op); - // Up until this point, the scheduler didn't start any task - // The closure shouldn't be started - CHECK_FALSE(called); - - // Tell sched1 to start executing one task. This will post - // work to sched3 - sched1.start_next(); - - // The closure shouldn't be started - CHECK_FALSE(called); - - // Tell sched3 to start executing one task. This post work to - // sched2. - sched3.start_next(); - - // The closure shouldn't be started - CHECK_FALSE(called); - - // Tell sched2 to start executing one task. This will execute - // the closure and post work back to sched3 - sched2.start_next(); - - // Now the closure is called, and execution is transfered back - // to sched3 - CHECK(called); - CHECK(recv_error == 0); - - // Tell sched3 to start executing one task. This will call the - // receiver - sched3.start_next(); - - // Now the error is sent to the receiver - CHECK(recv_error == 19); +namespace { + + template + inline auto _with_scheduler(Sched sched = {}) { + return exec::write(exec::with(ex::get_scheduler, std::move(sched))); + } + + TEST_CASE( + "exec::on transitions back to the receiver's scheduler when completing with a value", + "[adaptors][exec::on]") { + bool called{false}; + auto snd_base = ex::just() // + | ex::then([&]() -> int { + called = true; + return 19; + }); + + int recv_value{0}; + impulse_scheduler sched1; + impulse_scheduler sched2; + auto snd = exec::on(sched1, std::move(snd_base)) | _with_scheduler(sched2); + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); + ex::start(op); + // Up until this point, the scheduler didn't start any task + // The base sender shouldn't be started + CHECK_FALSE(called); + + // Tell sched1 to start executing one task + sched1.start_next(); + + // Now the base sender is called, and execution is transfered to sched2 + CHECK(called); + CHECK(recv_value == 0); + + // Tell sched2 to start executing one task + sched2.start_next(); + + // Now the base sender is called, and a value is sent to the receiver + CHECK(recv_value == 19); + } + + TEST_CASE( + "exec::on transitions back to the receiver's scheduler when completing with an error", + "[adaptors][exec::on]") { + bool called{false}; + auto snd_base = ex::just() // + | ex::let_value([&]() { + called = true; + return ex::just_error(19); + }); + + int recv_error{0}; + impulse_scheduler sched1; + impulse_scheduler sched2; + auto snd = exec::on(sched1, std::move(snd_base)) | _with_scheduler(sched2); + auto op = ex::connect(std::move(snd), expect_error_receiver_ex{recv_error}); + ex::start(op); + // Up until this point, the scheduler didn't start any task + // The base sender shouldn't be started + CHECK_FALSE(called); + + // Tell sched1 to start executing one task + sched1.start_next(); + + // Now the base sender is called, and execution is transfered to sched2 + CHECK(called); + CHECK(recv_error == 0); + + // Tell sched2 to start executing one task + sched2.start_next(); + + // Now the base sender is called, and an error is sent to the receiver + CHECK(recv_error == 19); + } + + TEST_CASE( + "inner on transitions back to outer on's scheduler when completing with a value", + "[adaptors][exec::on]") { + bool called{false}; + auto snd_base = ex::just() // + | ex::then([&]() -> int { + called = true; + return 19; + }); + + int recv_value{0}; + impulse_scheduler sched1; + impulse_scheduler sched2; + impulse_scheduler sched3; + auto snd = exec::on(sched1, exec::on(sched2, std::move(snd_base))) | _with_scheduler(sched3); + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); + ex::start(op); + // Up until this point, the scheduler didn't start any task + // The base sender shouldn't be started + CHECK_FALSE(called); + + // Tell sched1 to start executing one task. This will post + // work to sched2 + sched1.start_next(); + + // The base sender shouldn't be started + CHECK_FALSE(called); + + // Tell sched2 to start executing one task. This will execute + // the base sender and post work back to sched1 + sched2.start_next(); + + // Now the base sender is called, and execution is transfered back + // to sched1 + CHECK(called); + CHECK(recv_value == 0); + + // Tell sched1 to start executing one task. This will post work to + // sched3 + sched1.start_next(); + + // The final receiver still hasn't been called + CHECK(recv_value == 0); + + // Tell sched3 to start executing one task. It should call the + // final receiver + sched3.start_next(); + + // Now the value is sent to the receiver + CHECK(recv_value == 19); + } + + TEST_CASE( + "inner on transitions back to outer on's scheduler when completing with an error", + "[adaptors][exec::on]") { + bool called{false}; + auto snd_base = ex::just() // + | ex::let_value([&]() { + called = true; + return ex::just_error(19); + }); + + int recv_error{0}; + impulse_scheduler sched1; + impulse_scheduler sched2; + impulse_scheduler sched3; + auto snd = exec::on(sched1, exec::on(sched2, std::move(snd_base))) | _with_scheduler(sched3); + auto op = ex::connect(std::move(snd), expect_error_receiver_ex{recv_error}); + ex::start(op); + // Up until this point, the scheduler didn't start any task + // The base sender shouldn't be started + CHECK_FALSE(called); + + // Tell sched1 to start executing one task. This will post + // work to sched2 + sched1.start_next(); + + // The base sender shouldn't be started + CHECK_FALSE(called); + + // Tell sched2 to start executing one task. This will execute + // the base sender and post work back to sched1 + sched2.start_next(); + + // Now the base sender is called, and execution is transfered back + // to sched1 + CHECK(called); + CHECK(recv_error == 0); + + // Tell sched1 to start executing one task. This will post work to + // sched3 + sched1.start_next(); + + // The final receiver still hasn't been called + CHECK(recv_error == 0); + + // Tell sched3 to start executing one task. It should call the + // final receiver + sched3.start_next(); + + // Now the error is sent to the receiver + CHECK(recv_error == 19); + } + + TEST_CASE( + "exec::on(closure) transitions onto and back off of the scheduler when completing with a value", + "[adaptors][exec::on]") { + bool called{false}; + auto closure = ex::then([&]() -> int { + called = true; + return 19; + }); + + int recv_value{0}; + impulse_scheduler sched1; + impulse_scheduler sched2; + auto snd = ex::just() | exec::on(sched1, std::move(closure)) | _with_scheduler(sched2); + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); + ex::start(op); + // Up until this point, the scheduler didn't start any task + // The closure shouldn't be started + CHECK_FALSE(called); + + // Tell sched1 to start executing one task + sched1.start_next(); + + // Now the closure is called, and execution is transfered to sched2 + CHECK(called); + CHECK(recv_value == 0); + + // Tell sched2 to start executing one task + sched2.start_next(); + + // Now the closure is called, and a value is sent to the receiver + CHECK(recv_value == 19); + } + + TEST_CASE( + "exec::on(closure) transitions onto and back off of the scheduler when completing with " + "an error", + "[adaptors][exec::on]") { + bool called{false}; + auto closure = ex::let_value([&]() { + called = true; + return ex::just_error(19); + }); + + int recv_error{0}; + impulse_scheduler sched1; + impulse_scheduler sched2; + auto snd = ex::just() | exec::on(sched1, std::move(closure)) | _with_scheduler(sched2); + auto op = ex::connect(std::move(snd), expect_error_receiver_ex{recv_error}); + ex::start(op); + // Up until this point, the scheduler didn't start any task + // The closure shouldn't be started + CHECK_FALSE(called); + + // Tell sched1 to start executing one task + sched1.start_next(); + + // Now the closure is called, and execution is transfered to sched2 + CHECK(called); + CHECK(recv_error == 0); + + // Tell sched2 to start executing one task + sched2.start_next(); + + // Now the closure is called, and a error is sent to the receiver + CHECK(recv_error == 19); + } + + TEST_CASE( + "inner on(closure) transitions back to outer on's scheduler when completing with a value", + "[adaptors][exec::on]") { + bool called{false}; + auto closure = ex::then([&](int i) -> int { + called = true; + return i; + }); + + int recv_value{0}; + impulse_scheduler sched1; + impulse_scheduler sched2; + impulse_scheduler sched3; + auto snd = exec::on(sched1, ex::just(19)) | exec::on(sched2, std::move(closure)) + | _with_scheduler(sched3); + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); + ex::start(op); + // Up until this point, the scheduler didn't start any task + // The closure shouldn't be started + CHECK_FALSE(called); + + // Tell sched1 to start executing one task. This will post + // work to sched3 + sched1.start_next(); + + // The closure shouldn't be started + CHECK_FALSE(called); + + // Tell sched3 to start executing one task. This post work to + // sched2. + sched3.start_next(); + + // The closure shouldn't be started + CHECK_FALSE(called); + + // Tell sched2 to start executing one task. This will execute + // the closure and post work back to sched3 + sched2.start_next(); + + // Now the closure is called, and execution is transfered back + // to sched3 + CHECK(called); + CHECK(recv_value == 0); + + // Tell sched3 to start executing one task. This will call the + // receiver + sched3.start_next(); + + // Now the value is sent to the receiver + CHECK(recv_value == 19); + } + + TEST_CASE( + "inner on(closure) transitions back to outer on's scheduler when completing with an error", + "[adaptors][exec::on]") { + bool called{false}; + auto closure = ex::let_value([&](int i) { + called = true; + return ex::just_error(i); + }); + + int recv_error{0}; + impulse_scheduler sched1; + impulse_scheduler sched2; + impulse_scheduler sched3; + auto snd = exec::on(sched1, ex::just(19)) | exec::on(sched2, std::move(closure)) + | _with_scheduler(sched3); + auto op = ex::connect(std::move(snd), expect_error_receiver_ex{recv_error}); + ex::start(op); + // Up until this point, the scheduler didn't start any task + // The closure shouldn't be started + CHECK_FALSE(called); + + // Tell sched1 to start executing one task. This will post + // work to sched3 + sched1.start_next(); + + // The closure shouldn't be started + CHECK_FALSE(called); + + // Tell sched3 to start executing one task. This post work to + // sched2. + sched3.start_next(); + + // The closure shouldn't be started + CHECK_FALSE(called); + + // Tell sched2 to start executing one task. This will execute + // the closure and post work back to sched3 + sched2.start_next(); + + // Now the closure is called, and execution is transfered back + // to sched3 + CHECK(called); + CHECK(recv_error == 0); + + // Tell sched3 to start executing one task. This will call the + // receiver + sched3.start_next(); + + // Now the error is sent to the receiver + CHECK(recv_error == 19); + } } diff --git a/test/exec/test_on3.cpp b/test/exec/test_on3.cpp index d2bc07113..c245143a6 100644 --- a/test/exec/test_on3.cpp +++ b/test/exec/test_on3.cpp @@ -23,36 +23,39 @@ namespace ex = stdexec; -static const auto env = exec::make_env(exec::with(ex::get_scheduler, inline_scheduler{})); - -TEST_CASE("Can pass exec::on sender to start_detached", "[adaptors][exec::on]") { - ex::start_detached(exec::on(inline_scheduler{}, ex::just()), env); -} - -TEST_CASE("Can pass exec::on sender to split", "[adaptors][exec::on]") { - auto snd = ex::split(exec::on(inline_scheduler{}, ex::just()), env); - (void) snd; -} - -TEST_CASE("Can pass exec::on sender to ensure_started", "[adaptors][exec::on]") { - auto snd = ex::ensure_started(exec::on(inline_scheduler{}, ex::just()), env); - (void) snd; -} - -TEST_CASE("Can pass exec::on sender to async_scope::spawn", "[adaptors][exec::on]") { - exec::async_scope scope; - impulse_scheduler sched; - scope.spawn(exec::on(sched, ex::just()), env); - sched.start_next(); - stdexec::sync_wait(scope.on_empty()); -} - -TEST_CASE("Can pass exec::on sender to async_scope::spawn_future", "[adaptors][exec::on]") { - exec::async_scope scope; - impulse_scheduler sched; - auto fut = scope.spawn_future(exec::on(sched, ex::just(42)), env); - sched.start_next(); - auto [i] = stdexec::sync_wait(std::move(fut)).value(); - CHECK(i == 42); - stdexec::sync_wait(scope.on_empty()); +namespace { + + static const auto env = exec::make_env(exec::with(ex::get_scheduler, inline_scheduler{})); + + TEST_CASE("Can pass exec::on sender to start_detached", "[adaptors][exec::on]") { + ex::start_detached(exec::on(inline_scheduler{}, ex::just()), env); + } + + TEST_CASE("Can pass exec::on sender to split", "[adaptors][exec::on]") { + auto snd = ex::split(exec::on(inline_scheduler{}, ex::just()), env); + (void) snd; + } + + TEST_CASE("Can pass exec::on sender to ensure_started", "[adaptors][exec::on]") { + auto snd = ex::ensure_started(exec::on(inline_scheduler{}, ex::just()), env); + (void) snd; + } + + TEST_CASE("Can pass exec::on sender to async_scope::spawn", "[adaptors][exec::on]") { + exec::async_scope scope; + impulse_scheduler sched; + scope.spawn(exec::on(sched, ex::just()), env); + sched.start_next(); + stdexec::sync_wait(scope.on_empty()); + } + + TEST_CASE("Can pass exec::on sender to async_scope::spawn_future", "[adaptors][exec::on]") { + exec::async_scope scope; + impulse_scheduler sched; + auto fut = scope.spawn_future(exec::on(sched, ex::just(42)), env); + sched.start_next(); + auto [i] = stdexec::sync_wait(std::move(fut)).value(); + CHECK(i == 42); + stdexec::sync_wait(scope.on_empty()); + } } diff --git a/test/exec/test_repeat_effect_until.cpp b/test/exec/test_repeat_effect_until.cpp index 6cec97d6d..64d1851c3 100644 --- a/test/exec/test_repeat_effect_until.cpp +++ b/test/exec/test_repeat_effect_until.cpp @@ -34,131 +34,135 @@ using namespace stdexec; -struct boolean_sender { - using is_sender = void; - using __t = boolean_sender; - using __id = boolean_sender; - using completion_signatures = stdexec::completion_signatures; - - template - struct operation { - Receiver rcvr_; - int counter_; - - friend void tag_invoke(start_t, operation &self) noexcept { - if (self.counter_ == 0) { - set_value((Receiver &&) self.rcvr_, true); - } else { - set_value((Receiver &&) self.rcvr_, false); +namespace { + + struct boolean_sender { + using is_sender = void; + using __t = boolean_sender; + using __id = boolean_sender; + using completion_signatures = stdexec::completion_signatures; + + template + struct operation { + Receiver rcvr_; + int counter_; + + friend void tag_invoke(start_t, operation &self) noexcept { + if (self.counter_ == 0) { + set_value((Receiver &&) self.rcvr_, true); + } else { + set_value((Receiver &&) self.rcvr_, false); + } } + }; + + template Receiver> + friend operation tag_invoke(connect_t, boolean_sender self, Receiver rcvr) { + return {(Receiver &&) rcvr, --*self.counter_}; } + + std::shared_ptr counter_ = std::make_shared(1000); }; - template Receiver> - friend operation tag_invoke(connect_t, boolean_sender self, Receiver rcvr) { - return {(Receiver &&) rcvr, --*self.counter_}; + TEST_CASE("repeat_effect_until returns a sender", "[adaptors][repeat_effect_until]") { + auto snd = exec::repeat_effect_until(ex::just() | then([] { return false; })); + static_assert(ex::sender); + (void) snd; } - std::shared_ptr counter_ = std::make_shared(1000); -}; - -TEST_CASE("repeat_effect_until returns a sender", "[adaptors][repeat_effect_until]") { - auto snd = exec::repeat_effect_until(ex::just() | then([] { return false; })); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE( - "repeat_effect_until with environment returns a sender", - "[adaptors][repeat_effect_until]") { - auto snd = exec::repeat_effect_until(just() | then([] { return true; })); - static_assert(ex::sender_in); - (void) snd; -} + TEST_CASE( + "repeat_effect_until with environment returns a sender", + "[adaptors][repeat_effect_until]") { + auto snd = exec::repeat_effect_until(just() | then([] { return true; })); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE( - "repeat_effect_until produces void value to downstream receiver", - "[adaptors][repeat_effect_until]") { - sender auto source = just(1) | then([](int n) { return true; }); - sender auto snd = exec::repeat_effect_until(std::move(source)); - // The receiver checks if we receive the void value - auto op = stdexec::connect(std::move(snd), expect_void_receiver{}); - start(op); -} + TEST_CASE( + "repeat_effect_until produces void value to downstream receiver", + "[adaptors][repeat_effect_until]") { + sender auto source = just(1) | then([](int n) { return true; }); + sender auto snd = exec::repeat_effect_until(std::move(source)); + // The receiver checks if we receive the void value + auto op = stdexec::connect(std::move(snd), expect_void_receiver{}); + start(op); + } -TEST_CASE("simple example for repeat_effect_until", "[adaptors][repeat_effect_until]") { - sender auto snd = exec::repeat_effect_until(boolean_sender{}); - stdexec::sync_wait(std::move(snd)); -} + TEST_CASE("simple example for repeat_effect_until", "[adaptors][repeat_effect_until]") { + sender auto snd = exec::repeat_effect_until(boolean_sender{}); + stdexec::sync_wait(std::move(snd)); + } -TEST_CASE("repeat_effect_until works with pipeline operator", "[adaptors][repeat_effect_until]") { - bool should_stopped = true; - ex::sender auto snd = just(should_stopped) | exec::repeat_effect_until() | then([] { return 1; }); - wait_for_value(std::move(snd), 1); -} + TEST_CASE("repeat_effect_until works with pipeline operator", "[adaptors][repeat_effect_until]") { + bool should_stopped = true; + ex::sender auto snd = just(should_stopped) | exec::repeat_effect_until() + | then([] { return 1; }); + wait_for_value(std::move(snd), 1); + } -TEST_CASE( - "repeat_effect_until works when input sender produces an int value", - "[adaptors][repeat_effect_until]") { - sender auto snd = exec::repeat_effect_until(just(1)); - auto op = stdexec::connect(std::move(snd), expect_void_receiver{}); - start(op); -} + TEST_CASE( + "repeat_effect_until works when input sender produces an int value", + "[adaptors][repeat_effect_until]") { + sender auto snd = exec::repeat_effect_until(just(1)); + auto op = stdexec::connect(std::move(snd), expect_void_receiver{}); + start(op); + } -TEST_CASE( - "repeat_effect_until works when input sender produces an object that can be converted to bool" - "[adaptors][repeat_effect_until]") { - struct pred { - operator bool() { - return --n <= 100; - } + TEST_CASE( + "repeat_effect_until works when input sender produces an object that can be converted to bool" + "[adaptors][repeat_effect_until]") { + struct pred { + operator bool() { + return --n <= 100; + } - int n = 100; - }; + int n = 100; + }; - pred p; - auto input_snd = just() | then([&p] { return p; }); - stdexec::sync_wait(exec::repeat_effect_until(std::move(input_snd))); -} + pred p; + auto input_snd = just() | then([&p] { return p; }); + stdexec::sync_wait(exec::repeat_effect_until(std::move(input_snd))); + } -TEST_CASE( - "repeat_effect_until forwards set_error calls of other types", - "[adaptors][repeat_effect_until]") { - auto snd = just_error(std::string("error")) | exec::repeat_effect_until(); - auto op = ex::connect(std::move(snd), expect_error_receiver{std::string("error")}); - start(op); -} + TEST_CASE( + "repeat_effect_until forwards set_error calls of other types", + "[adaptors][repeat_effect_until]") { + auto snd = just_error(std::string("error")) | exec::repeat_effect_until(); + auto op = ex::connect(std::move(snd), expect_error_receiver{std::string("error")}); + start(op); + } -TEST_CASE("repeat_effect_until forwards set_stopped calls", "[adaptors][repeat_effect_until]") { - auto snd = just_stopped() | exec::repeat_effect_until(); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - start(op); -} + TEST_CASE("repeat_effect_until forwards set_stopped calls", "[adaptors][repeat_effect_until]") { + auto snd = just_stopped() | exec::repeat_effect_until(); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + start(op); + } -TEST_CASE( - "running deeply recursing algo on repeat_effect_until doesn't blow the stack", - "[adaptors][repeat_effect_until]") { - int n = 1; - sender auto snd = exec::repeat_effect_until(just() | then([&n] { - ++n; - return n == 1'000'000; - })); - stdexec::sync_wait(std::move(snd)); - CHECK(n == 1'000'000); -} + TEST_CASE( + "running deeply recursing algo on repeat_effect_until doesn't blow the stack", + "[adaptors][repeat_effect_until]") { + int n = 1; + sender auto snd = exec::repeat_effect_until(just() | then([&n] { + ++n; + return n == 1'000'000; + })); + stdexec::sync_wait(std::move(snd)); + CHECK(n == 1'000'000); + } -TEST_CASE("repeat_effect_until works when changing threads", "[adaptors][repeat_effect_until]") { - exec::static_thread_pool pool{2}; - bool called{false}; - sender auto snd = exec::on( - pool.get_scheduler(), // - ex::just() // - | ex::then([&] { - called = true; - return called; - }) - | exec::repeat_effect_until()); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(called); + TEST_CASE("repeat_effect_until works when changing threads", "[adaptors][repeat_effect_until]") { + exec::static_thread_pool pool{2}; + bool called{false}; + sender auto snd = exec::on( + pool.get_scheduler(), // + ex::just() // + | ex::then([&] { + called = true; + return called; + }) + | exec::repeat_effect_until()); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(called); + } } diff --git a/test/exec/test_sequence_senders.cpp b/test/exec/test_sequence_senders.cpp index bde5b1595..d87efd07f 100644 --- a/test/exec/test_sequence_senders.cpp +++ b/test/exec/test_sequence_senders.cpp @@ -22,113 +22,118 @@ using namespace stdexec; using namespace exec; -struct nop_operation { - friend void tag_invoke(start_t, nop_operation&) noexcept { - } -}; - -TEST_CASE("sequence_senders - nop_operation is an operation state", "[sequence_senders]") { - STATIC_REQUIRE(operation_state); -} +namespace { -template <__completion_signature... _Sigs> -struct some_sender_of { - using is_sender = void; - using completion_signatures = stdexec::completion_signatures<_Sigs...>; - - template - friend nop_operation tag_invoke(connect_t, some_sender_of self, R&& rcvr); -}; - -TEST_CASE("sequence_senders - some_sender_of is a sender", "[sequence_senders]") { - STATIC_REQUIRE(sender>); - STATIC_REQUIRE(sender_in, empty_env>); - STATIC_REQUIRE(same_as< - completion_signatures_of_t>, - completion_signatures>); - STATIC_REQUIRE(same_as< - completion_signatures_of_t>, - completion_signatures>); -} + struct nop_operation { + friend void tag_invoke(start_t, nop_operation&) noexcept { + } + }; -template <__completion_signature... _Sigs> -struct test_receiver { - using is_receiver = void; - - template - requires __one_of<_Tag(_Args...), _Sigs...> - friend void tag_invoke(_Tag, test_receiver&&, _Args&&...) noexcept { + TEST_CASE("sequence_senders - nop_operation is an operation state", "[sequence_senders]") { + STATIC_REQUIRE(operation_state); } - friend empty_env tag_invoke(get_env_t, test_receiver) noexcept { - return {}; + template <__completion_signature... _Sigs> + struct some_sender_of { + using is_sender = void; + using completion_signatures = stdexec::completion_signatures<_Sigs...>; + + template + friend nop_operation tag_invoke(connect_t, some_sender_of self, R&& rcvr) { + return {}; + } + }; + + TEST_CASE("sequence_senders - some_sender_of is a sender", "[sequence_senders]") { + STATIC_REQUIRE(sender>); + STATIC_REQUIRE(sender_in, empty_env>); + STATIC_REQUIRE(same_as< + completion_signatures_of_t>, + completion_signatures>); + STATIC_REQUIRE(same_as< + completion_signatures_of_t>, + completion_signatures>); } -}; - -TEST_CASE("sequence_senders - test_receiver is a receiver of its Sigs", "[sequence_senders]") { - STATIC_REQUIRE(receiver>); - STATIC_REQUIRE(receiver_of, completion_signatures>); - STATIC_REQUIRE_FALSE( - receiver_of, completion_signatures>); - STATIC_REQUIRE_FALSE( - receiver_of, completion_signatures>); - STATIC_REQUIRE_FALSE( - receiver_of, completion_signatures>); - STATIC_REQUIRE(sender_to, test_receiver>); - STATIC_REQUIRE_FALSE(sender_to< - some_sender_of, - test_receiver>); -} - -template <__completion_signature... _Sigs> -struct next_receiver { - using is_receiver = void; - template > _Item> - friend _Item tag_invoke(set_next_t, next_receiver&, _Item&& __item) noexcept { - return __item; + template <__completion_signature... _Sigs> + struct test_receiver { + using is_receiver = void; + + template + requires __one_of<_Tag(_Args...), _Sigs...> + friend void tag_invoke(_Tag, test_receiver&&, _Args&&...) noexcept { + } + + friend empty_env tag_invoke(get_env_t, test_receiver) noexcept { + return {}; + } + }; + + TEST_CASE("sequence_senders - test_receiver is a receiver of its Sigs", "[sequence_senders]") { + STATIC_REQUIRE(receiver>); + STATIC_REQUIRE(receiver_of, completion_signatures>); + STATIC_REQUIRE_FALSE( + receiver_of, completion_signatures>); + STATIC_REQUIRE_FALSE( + receiver_of, completion_signatures>); + STATIC_REQUIRE_FALSE( + receiver_of, completion_signatures>); + STATIC_REQUIRE(sender_to, test_receiver>); + STATIC_REQUIRE_FALSE(sender_to< + some_sender_of, + test_receiver>); } - friend void tag_invoke(set_value_t, next_receiver&&) noexcept { + template <__completion_signature... _Sigs> + struct next_receiver { + using is_receiver = void; + + template > _Item> + friend _Item tag_invoke(set_next_t, next_receiver&, _Item&& __item) noexcept { + return __item; + } + + friend void tag_invoke(set_value_t, next_receiver&&) noexcept { + } + + friend void tag_invoke(set_stopped_t, next_receiver&&) noexcept { + } + + template + friend void tag_invoke(set_error_t, next_receiver&&, E&&) noexcept { + } + + friend empty_env tag_invoke(get_env_t, const next_receiver&) noexcept { + return {}; + } + }; + + TEST_CASE("sequence_senders - Test missing next signature", "[sequence_senders]") { + using just_t = decltype(just()); + using just_int_t = decltype(just(0)); + using next_receiver_t = next_receiver; + STATIC_REQUIRE(receiver); + STATIC_REQUIRE(sequence_receiver_of>); + STATIC_REQUIRE_FALSE(sequence_receiver_of>); + STATIC_REQUIRE(sender_to); } - friend void tag_invoke(set_stopped_t, next_receiver&&) noexcept { + template <__completion_signature... _Sigs> + struct some_sequence_sender_of { + using is_sender = sequence_tag; + using completion_signatures = stdexec::completion_signatures; + using item_types = exec::item_types>; + + template + friend nop_operation tag_invoke(subscribe_t, some_sequence_sender_of self, R&& rcvr); + }; + + TEST_CASE("sequence_senders - Test for subscribe", "[sequence_senders]") { + using next_receiver_t = next_receiver; + using seq_sender_t = some_sequence_sender_of; + STATIC_REQUIRE(sender); + STATIC_REQUIRE(sequence_sender); + STATIC_REQUIRE(sequence_sender_to); + STATIC_REQUIRE(sequence_sender_to, next_receiver_t>); } - - template - friend void tag_invoke(set_error_t, next_receiver&&, E&&) noexcept { - } - - friend empty_env tag_invoke(get_env_t, const next_receiver&) noexcept { - return {}; - } -}; - -TEST_CASE("sequence_senders - Test missing next signature", "[sequence_senders]") { - using just_t = decltype(just()); - using just_int_t = decltype(just(0)); - using next_receiver_t = next_receiver; - STATIC_REQUIRE(receiver); - STATIC_REQUIRE(sequence_receiver_of>); - STATIC_REQUIRE_FALSE(sequence_receiver_of>); - STATIC_REQUIRE(sender_to); -} - -template <__completion_signature... _Sigs> -struct some_sequence_sender_of { - using is_sender = sequence_tag; - using completion_signatures = stdexec::completion_signatures; - using item_types = exec::item_types>; - - template - friend nop_operation tag_invoke(subscribe_t, some_sequence_sender_of self, R&& rcvr); -}; - -TEST_CASE("sequence_senders - Test for subscribe", "[sequence_senders]") { - using next_receiver_t = next_receiver; - using seq_sender_t = some_sequence_sender_of; - STATIC_REQUIRE(sender); - STATIC_REQUIRE(sequence_sender); - STATIC_REQUIRE(sequence_sender_to); - STATIC_REQUIRE(sequence_sender_to, next_receiver_t>); } diff --git a/test/exec/test_task.cpp b/test/exec/test_task.cpp index 2aa1bd7ad..41f07aafc 100644 --- a/test/exec/test_task.cpp +++ b/test/exec/test_task.cpp @@ -104,171 +104,176 @@ namespace { scheduler1, scheduler2, id1, id2); CHECK(get_id() == id1); } -} - -TEST_CASE("Test stickiness with two single threads", "[types][sticky][task]") { - single_thread_context context1; - single_thread_context context2; - scheduler auto scheduler1 = context1.get_scheduler(); - scheduler auto scheduler2 = context2.get_scheduler(); - sync_wait(when_all( - schedule(scheduler1) | then([] { __thread_id = 1; }), - schedule(scheduler2) | then([] { __thread_id = 2; }))); - auto id1 = 1; - auto id2 = 2; - auto t = test_stickiness_for_two_single_thread_contexts(scheduler1, scheduler2, id1, id2); - sync_wait(std::move(t)); -} -TEST_CASE("Test stickiness with two single threads with on", "[types][sticky][task]") { - single_thread_context context1; - single_thread_context context2; - scheduler auto scheduler1 = context1.get_scheduler(); - scheduler auto scheduler2 = context2.get_scheduler(); - sync_wait(when_all( - schedule(scheduler1) | then([] { __thread_id = 1; }), - schedule(scheduler2) | then([] { __thread_id = 2; }))); - auto id1 = 1; - auto id2 = 2; - auto t = on( - scheduler1, test_stickiness_for_two_single_thread_contexts_(scheduler1, scheduler2, id1, id2)); - sync_wait(std::move(t) | then([&] { CHECK(get_id() == id1); })); -} + TEST_CASE("Test stickiness with two single threads", "[types][sticky][task]") { + single_thread_context context1; + single_thread_context context2; + scheduler auto scheduler1 = context1.get_scheduler(); + scheduler auto scheduler2 = context2.get_scheduler(); + sync_wait(when_all( + schedule(scheduler1) | then([] { __thread_id = 1; }), + schedule(scheduler2) | then([] { __thread_id = 2; }))); + auto id1 = 1; + auto id2 = 2; + auto t = test_stickiness_for_two_single_thread_contexts(scheduler1, scheduler2, id1, id2); + sync_wait(std::move(t)); + } -TEST_CASE("Test stickiness with two single threads with sender", "[types][sticky][task]") { - single_thread_context context1; - single_thread_context context2; - scheduler auto scheduler1 = context1.get_scheduler(); - scheduler auto scheduler2 = context2.get_scheduler(); - sync_wait(when_all( - schedule(scheduler1) | then([] { __thread_id = 1; }), - schedule(scheduler2) | then([] { __thread_id = 2; }))); - auto id1 = 1; - auto id2 = 2; - auto t = test_stickiness_for_two_single_thread_contexts_with_sender( - scheduler1, scheduler2, id1, id2); - sync_wait(std::move(t)); -} + TEST_CASE("Test stickiness with two single threads with on", "[types][sticky][task]") { + single_thread_context context1; + single_thread_context context2; + scheduler auto scheduler1 = context1.get_scheduler(); + scheduler auto scheduler2 = context2.get_scheduler(); + sync_wait(when_all( + schedule(scheduler1) | then([] { __thread_id = 1; }), + schedule(scheduler2) | then([] { __thread_id = 2; }))); + auto id1 = 1; + auto id2 = 2; + auto t = on( + scheduler1, + test_stickiness_for_two_single_thread_contexts_(scheduler1, scheduler2, id1, id2)); + sync_wait(std::move(t) | then([&] { CHECK(get_id() == id1); })); + } -TEST_CASE("Test stickiness with two single threads with sender with on", "[types][sticky][task]") { - single_thread_context context1; - single_thread_context context2; - scheduler auto scheduler1 = context1.get_scheduler(); - scheduler auto scheduler2 = context2.get_scheduler(); - sync_wait(when_all( - schedule(scheduler1) | then([] { __thread_id = 1; }), - schedule(scheduler2) | then([] { __thread_id = 2; }))); - auto id1 = 1; - auto id2 = 2; - auto t = on( - scheduler1, - test_stickiness_for_two_single_thread_contexts_with_sender_(scheduler1, scheduler2, id1, id2)); - sync_wait(std::move(t) | then([&] { CHECK(get_id() == id1); })); -} + TEST_CASE("Test stickiness with two single threads with sender", "[types][sticky][task]") { + single_thread_context context1; + single_thread_context context2; + scheduler auto scheduler1 = context1.get_scheduler(); + scheduler auto scheduler2 = context2.get_scheduler(); + sync_wait(when_all( + schedule(scheduler1) | then([] { __thread_id = 1; }), + schedule(scheduler2) | then([] { __thread_id = 2; }))); + auto id1 = 1; + auto id2 = 2; + auto t = test_stickiness_for_two_single_thread_contexts_with_sender( + scheduler1, scheduler2, id1, id2); + sync_wait(std::move(t)); + } -TEST_CASE("Use two inline schedulers", "[types][sticky][task]") { - scheduler auto scheduler1 = exec::inline_scheduler{}; - scheduler auto scheduler2 = exec::inline_scheduler{}; - sync_wait(when_all( - schedule(scheduler1) | then([] { __thread_id = 0; }), - schedule(scheduler2) | then([] { __thread_id = 0; }))); - auto id1 = 0; - auto id2 = 0; - auto t = test_stickiness_for_two_single_thread_contexts(scheduler1, scheduler2, id1, id2); - sync_wait(std::move(t)); -} + TEST_CASE( + "Test stickiness with two single threads with sender with on", + "[types][sticky][task]") { + single_thread_context context1; + single_thread_context context2; + scheduler auto scheduler1 = context1.get_scheduler(); + scheduler auto scheduler2 = context2.get_scheduler(); + sync_wait(when_all( + schedule(scheduler1) | then([] { __thread_id = 1; }), + schedule(scheduler2) | then([] { __thread_id = 2; }))); + auto id1 = 1; + auto id2 = 2; + auto t = on( + scheduler1, + test_stickiness_for_two_single_thread_contexts_with_sender_( + scheduler1, scheduler2, id1, id2)); + sync_wait(std::move(t) | then([&] { CHECK(get_id() == id1); })); + } -namespace { - task test_stick_on_main_nested( - scheduler auto sched1, - scheduler auto sched2, - auto id_main_thread, - [[maybe_unused]] auto id1, - [[maybe_unused]] auto id2) { - CHECK(get_id() == id_main_thread); - co_await schedule(sched1); - CHECK(get_id() == id_main_thread); + TEST_CASE("Use two inline schedulers", "[types][sticky][task]") { + scheduler auto scheduler1 = exec::inline_scheduler{}; + scheduler auto scheduler2 = exec::inline_scheduler{}; + sync_wait(when_all( + schedule(scheduler1) | then([] { __thread_id = 0; }), + schedule(scheduler2) | then([] { __thread_id = 0; }))); + auto id1 = 0; + auto id2 = 0; + auto t = test_stickiness_for_two_single_thread_contexts(scheduler1, scheduler2, id1, id2); + sync_wait(std::move(t)); } - task test_stick_on_main( - scheduler auto sched1, - scheduler auto sched2, - auto id_main_thread, - [[maybe_unused]] auto id1, - [[maybe_unused]] auto id2) { - CHECK(get_id() == id_main_thread); - co_await schedule(sched1); - CHECK(get_id() == id_main_thread); - co_await schedule(sched2); - CHECK(get_id() == id_main_thread); - co_await test_stick_on_main_nested(sched1, sched2, id_main_thread, id1, id2); - CHECK(get_id() == id_main_thread); + namespace { + task test_stick_on_main_nested( + scheduler auto sched1, + scheduler auto sched2, + auto id_main_thread, + [[maybe_unused]] auto id1, + [[maybe_unused]] auto id2) { + CHECK(get_id() == id_main_thread); + co_await schedule(sched1); + CHECK(get_id() == id_main_thread); + } + + task test_stick_on_main( + scheduler auto sched1, + scheduler auto sched2, + auto id_main_thread, + [[maybe_unused]] auto id1, + [[maybe_unused]] auto id2) { + CHECK(get_id() == id_main_thread); + co_await schedule(sched1); + CHECK(get_id() == id_main_thread); + co_await schedule(sched2); + CHECK(get_id() == id_main_thread); + co_await test_stick_on_main_nested(sched1, sched2, id_main_thread, id1, id2); + CHECK(get_id() == id_main_thread); + } } -} -TEST_CASE("Stick on main thread if completes_inline is not used", "[types][sticky][task]") { - single_thread_context context1; - single_thread_context context2; - scheduler auto scheduler1 = context1.get_scheduler(); - scheduler auto scheduler2 = context2.get_scheduler(); - sync_wait(when_all( - schedule(scheduler1) | then([] { __thread_id = 1; }), - schedule(scheduler2) | then([] { __thread_id = 2; }))); - auto id1 = 1; - auto id2 = 2; - auto id_main_thread = 0; - auto t = test_stick_on_main(scheduler1, scheduler2, id_main_thread, id1, id2); - sync_wait(std::move(t)); -} + TEST_CASE("Stick on main thread if completes_inline is not used", "[types][sticky][task]") { + single_thread_context context1; + single_thread_context context2; + scheduler auto scheduler1 = context1.get_scheduler(); + scheduler auto scheduler2 = context2.get_scheduler(); + sync_wait(when_all( + schedule(scheduler1) | then([] { __thread_id = 1; }), + schedule(scheduler2) | then([] { __thread_id = 2; }))); + auto id1 = 1; + auto id2 = 2; + auto id_main_thread = 0; + auto t = test_stick_on_main(scheduler1, scheduler2, id_main_thread, id1, id2); + sync_wait(std::move(t)); + } -exec::task check_stop_possible() { - auto stop_token = co_await stdexec::get_stop_token(); - CHECK(stop_token.stop_possible()); -} + exec::task check_stop_possible() { + auto stop_token = co_await stdexec::get_stop_token(); + CHECK(stop_token.stop_possible()); + } -TEST_CASE("task - stop token is forwarded", "[types][task]") { - single_thread_context context{}; - exec::async_scope scope; - scope.spawn(stdexec::on(context.get_scheduler(), check_stop_possible())); - CHECK(stdexec::sync_wait(scope.on_empty())); -} + TEST_CASE("task - stop token is forwarded", "[types][task]") { + single_thread_context context{}; + exec::async_scope scope; + scope.spawn(stdexec::on(context.get_scheduler(), check_stop_possible())); + CHECK(stdexec::sync_wait(scope.on_empty())); + } -TEST_CASE("task - can stop early", "[types][task]") { - int count = 0; - auto work = [](int& count) -> exec::task { - count += 1; - co_await [](int& count) -> exec::task { - count += 2; - co_await stdexec::just_stopped(); - count += 4; + TEST_CASE("task - can stop early", "[types][task]") { + int count = 0; + auto work = [](int& count) -> exec::task { + count += 1; + co_await [](int& count) -> exec::task { + count += 2; + co_await stdexec::just_stopped(); + count += 4; + }(count); + count += 8; }(count); - count += 8; - }(count); - auto res = stdexec::sync_wait(std::move(work)); - CHECK(!res.has_value()); - CHECK(count == 3); -} + auto res = stdexec::sync_wait(std::move(work)); + CHECK(!res.has_value()); + CHECK(count == 3); + } -TEST_CASE("task - can error early", "[types][task]") { - int count = 0; - auto work = [](int& count) -> exec::task { - count += 1; - co_await [](int& count) -> exec::task { - count += 2; - co_await stdexec::just_error(std::runtime_error("on noes")); - count += 4; + TEST_CASE("task - can error early", "[types][task]") { + int count = 0; + auto work = [](int& count) -> exec::task { + count += 1; + co_await [](int& count) -> exec::task { + count += 2; + co_await stdexec::just_error(std::runtime_error("on noes")); + count += 4; + }(count); + count += 8; }(count); - count += 8; - }(count); - try { - stdexec::sync_wait(std::move(work)); - CHECK(false); - } catch (const std::runtime_error& e) { - CHECK(std::string_view(e.what()) == "on noes"); + try { + stdexec::sync_wait(std::move(work)); + CHECK(false); + } catch (const std::runtime_error& e) { + CHECK(std::string_view(e.what()) == "on noes"); + } + CHECK(count == 3); } - CHECK(count == 3); + } #endif diff --git a/test/exec/test_trampoline_scheduler.cpp b/test/exec/test_trampoline_scheduler.cpp index 3cfa05286..0798aecfa 100644 --- a/test/exec/test_trampoline_scheduler.cpp +++ b/test/exec/test_trampoline_scheduler.cpp @@ -27,55 +27,58 @@ using namespace stdexec; -struct try_again { }; - -struct fails_alot { - using is_sender = void; - using __t = fails_alot; - using __id = fails_alot; - using completion_signatures = - stdexec::completion_signatures; - - template - struct operation { - Receiver rcvr_; - int counter_; - - friend void tag_invoke(start_t, operation& self) noexcept { - if (self.counter_ == 0) { - set_value((Receiver&&) self.rcvr_); - } else { - set_error((Receiver&&) self.rcvr_, try_again{}); +namespace { + + struct try_again { }; + + struct fails_alot { + using is_sender = void; + using __t = fails_alot; + using __id = fails_alot; + using completion_signatures = + stdexec::completion_signatures; + + template + struct operation { + Receiver rcvr_; + int counter_; + + friend void tag_invoke(start_t, operation& self) noexcept { + if (self.counter_ == 0) { + set_value((Receiver&&) self.rcvr_); + } else { + set_error((Receiver&&) self.rcvr_, try_again{}); + } } - } - }; + }; - template Receiver> - friend operation tag_invoke(connect_t, fails_alot self, Receiver rcvr) { - return {(Receiver&&) rcvr, --*self.counter_}; - } + template Receiver> + friend operation tag_invoke(connect_t, fails_alot self, Receiver rcvr) { + return {(Receiver&&) rcvr, --*self.counter_}; + } - std::shared_ptr counter_ = std::make_shared(1'000'000); -}; + std::shared_ptr counter_ = std::make_shared(1'000'000); + }; -// #if defined(REQUIRE_TERMINATE) -// // For some reason, when compiling with nvc++, the forked process dies with SIGSEGV -// // but the error code returned from ::wait reports success, so this test fails. -// TEST_CASE("running deeply recursing algo blows the stack", "[schedulers][trampoline_scheduler]") { + // #if defined(REQUIRE_TERMINATE) + // // For some reason, when compiling with nvc++, the forked process dies with SIGSEGV + // // but the error code returned from ::wait reports success, so this test fails. + // TEST_CASE("running deeply recursing algo blows the stack", "[schedulers][trampoline_scheduler]") { -// auto recurse_deeply = retry(fails_alot{}); -// REQUIRE_TERMINATE([&] { sync_wait(std::move(recurse_deeply)); }); -// } -// #endif + // auto recurse_deeply = retry(fails_alot{}); + // REQUIRE_TERMINATE([&] { sync_wait(std::move(recurse_deeply)); }); + // } + // #endif -TEST_CASE( - "running deeply recursing algo on trampoline_scheduler doesn't blow the stack", - "[schedulers][trampoline_scheduler]") { + TEST_CASE( + "running deeply recursing algo on trampoline_scheduler doesn't blow the stack", + "[schedulers][trampoline_scheduler]") { - using stdexec::__sync_wait::__env; - exec::trampoline_scheduler sched; - stdexec::run_loop loop; + using stdexec::__sync_wait::__env; + exec::trampoline_scheduler sched; + stdexec::run_loop loop; - auto recurse_deeply = retry(exec::on(sched, fails_alot{})); - sync_wait(std::move(recurse_deeply)); + auto recurse_deeply = retry(exec::on(sched, fails_alot{})); + sync_wait(std::move(recurse_deeply)); + } } diff --git a/test/exec/test_type_async_scope.cpp b/test/exec/test_type_async_scope.cpp index 606eaaa22..845f4c267 100644 --- a/test/exec/test_type_async_scope.cpp +++ b/test/exec/test_type_async_scope.cpp @@ -39,72 +39,72 @@ namespace { ex::start(op); loop.run(); } -} // namespace -TEST_CASE("async_scope will complete", "[types][type_async_scope]") { - exec::static_thread_pool ctx{1}; + TEST_CASE("async_scope will complete", "[types][type_async_scope]") { + exec::static_thread_pool ctx{1}; - ex::scheduler auto sch = ctx.get_scheduler(); + ex::scheduler auto sch = ctx.get_scheduler(); - SECTION("after construction") { - exec::async_scope scope; - expect_empty(scope); - } + SECTION("after construction") { + exec::async_scope scope; + expect_empty(scope); + } - SECTION("after spawn") { - exec::async_scope scope; - ex::sender auto begin = ex::schedule(sch); - scope.spawn(begin); - stdexec::sync_wait(scope.on_empty()); - expect_empty(scope); - } + SECTION("after spawn") { + exec::async_scope scope; + ex::sender auto begin = ex::schedule(sch); + scope.spawn(begin); + stdexec::sync_wait(scope.on_empty()); + expect_empty(scope); + } + + SECTION("after nest result discarded") { + exec::async_scope scope; + ex::sender auto begin = ex::schedule(sch); + { + ex::sender auto nst = scope.nest(begin); + (void) nst; + } + stdexec::sync_wait(scope.on_empty()); + expect_empty(scope); + } - SECTION("after nest result discarded") { - exec::async_scope scope; - ex::sender auto begin = ex::schedule(sch); - { + SECTION("after nest result started") { + exec::async_scope scope; + ex::sender auto begin = ex::schedule(sch); ex::sender auto nst = scope.nest(begin); - (void) nst; + auto op = ex::connect(std::move(nst), expect_void_receiver{}); + ex::start(op); + stdexec::sync_wait(scope.on_empty()); + expect_empty(scope); } - stdexec::sync_wait(scope.on_empty()); - expect_empty(scope); - } - SECTION("after nest result started") { - exec::async_scope scope; - ex::sender auto begin = ex::schedule(sch); - ex::sender auto nst = scope.nest(begin); - auto op = ex::connect(std::move(nst), expect_void_receiver{}); - ex::start(op); - stdexec::sync_wait(scope.on_empty()); - expect_empty(scope); - } + SECTION("after spawn_future result discarded") { + exec::static_thread_pool ctx{1}; + exec::async_scope scope; + std::atomic_bool produced{false}; + ex::sender auto begin = ex::schedule(sch); + { + ex::sender auto ftr = scope.spawn_future(begin | stdexec::then([&]() { produced = true; })); + (void) ftr; + } + stdexec::sync_wait( + scope.on_empty() | stdexec::then([&]() { STDEXEC_ASSERT(produced.load()); })); + expect_empty(scope); + } - SECTION("after spawn_future result discarded") { - exec::static_thread_pool ctx{1}; - exec::async_scope scope; - std::atomic_bool produced{false}; - ex::sender auto begin = ex::schedule(sch); - { + SECTION("after spawn_future result started") { + exec::static_thread_pool ctx{1}; + exec::async_scope scope; + std::atomic_bool produced{false}; + ex::sender auto begin = ex::schedule(sch); ex::sender auto ftr = scope.spawn_future(begin | stdexec::then([&]() { produced = true; })); - (void) ftr; + stdexec::sync_wait( + scope.on_empty() | stdexec::then([&]() { STDEXEC_ASSERT(produced.load()); })); + auto op = ex::connect(std::move(ftr), expect_void_receiver{}); + ex::start(op); + stdexec::sync_wait(scope.on_empty()); + expect_empty(scope); } - stdexec::sync_wait( - scope.on_empty() | stdexec::then([&]() { STDEXEC_ASSERT(produced.load()); })); - expect_empty(scope); - } - - SECTION("after spawn_future result started") { - exec::static_thread_pool ctx{1}; - exec::async_scope scope; - std::atomic_bool produced{false}; - ex::sender auto begin = ex::schedule(sch); - ex::sender auto ftr = scope.spawn_future(begin | stdexec::then([&]() { produced = true; })); - stdexec::sync_wait( - scope.on_empty() | stdexec::then([&]() { STDEXEC_ASSERT(produced.load()); })); - auto op = ex::connect(std::move(ftr), expect_void_receiver{}); - ex::start(op); - stdexec::sync_wait(scope.on_empty()); - expect_empty(scope); } } diff --git a/test/exec/test_variant_sender.cpp b/test/exec/test_variant_sender.cpp index 72220725d..4bf7c8162 100644 --- a/test/exec/test_variant_sender.cpp +++ b/test/exec/test_variant_sender.cpp @@ -22,42 +22,45 @@ using namespace stdexec; using namespace exec; -template -struct overloaded : Ts... { - using Ts::operator()...; -}; -template -overloaded(Ts...) -> overloaded; - -using just_int_t = decltype(just(0)); -using just_void_t = decltype(just()); - -TEST_CASE("variant_sender - default constructible", "[types][variant_sender]") { - variant_sender variant{just()}; - CHECK(variant.index() == 0); -} +namespace { + + template + struct overloaded : Ts... { + using Ts::operator()...; + }; + template + overloaded(Ts...) -> overloaded; + + using just_int_t = decltype(just(0)); + using just_void_t = decltype(just()); + + TEST_CASE("variant_sender - default constructible", "[types][variant_sender]") { + variant_sender variant{just()}; + CHECK(variant.index() == 0); + } + + TEST_CASE("variant_sender - using an overloaded then adaptor", "[types][variant_sender]") { + variant_sender variant = just(); + int index = -1; + STATIC_REQUIRE(sender>); + sync_wait(variant | then([&index](auto... xs) { index = sizeof...(xs); })); + CHECK(index == 0); -TEST_CASE("variant_sender - using an overloaded then adaptor", "[types][variant_sender]") { - variant_sender variant = just(); - int index = -1; - STATIC_REQUIRE(sender>); - sync_wait(variant | then([&index](auto... xs) { index = sizeof...(xs); })); - CHECK(index == 0); - - variant.emplace<1>(just(42)); - auto [value] = - sync_wait( - variant - | then(overloaded{ - [&index] { - index = 0; - return 0; - }, - [&index](int xs) { - index = 1; - return xs; - }})) - .value(); - CHECK(index == 1); - CHECK(value == 42); + variant.emplace<1>(just(42)); + auto [value] = + sync_wait( + variant + | then(overloaded{ + [&index] { + index = 0; + return 0; + }, + [&index](int xs) { + index = 1; + return xs; + }})) + .value(); + CHECK(index == 1); + CHECK(value == 42); + } } diff --git a/test/exec/test_when_any.cpp b/test/exec/test_when_any.cpp index e6abfd4a3..cfa8919d3 100644 --- a/test/exec/test_when_any.cpp +++ b/test/exec/test_when_any.cpp @@ -22,218 +22,221 @@ #include #include -namespace ex = stdexec; +namespace stdexec { + template + struct __mall_contained_in { + template + using __f = __mand<__mapply<__contains, Haystack>...>; + }; -TEST_CASE("when_ny returns a sender", "[adaptors][when_any]") { - auto snd = exec::when_any(ex::just(3), ex::just(0.1415)); - static_assert(ex::sender); - (void) snd; -} + template + using __all_contained_in = __mapply<__mall_contained_in, Needles>; -TEST_CASE("when_any with environment returns a sender", "[adaptors][when_any]") { - auto snd = exec::when_any(ex::just(3), ex::just(0.1415)); - static_assert(ex::sender_in); - (void) snd; + template + using __equivalent = __mand< + __all_contained_in, + std::is_same, ex::__mapply>>; } -TEST_CASE("when_any simple example", "[adaptors][when_any]") { - auto snd = exec::when_any(ex::just(3.0)); - auto snd1 = std::move(snd) | ex::then([](double y) { return y + 0.1415; }); - const double expected = 3.0 + 0.1415; - auto op = ex::connect(std::move(snd1), expect_value_receiver{expected}); - ex::start(op); -} +namespace ex = stdexec; -TEST_CASE("when_any completes with only one sender", "[adaptors][when_any]") { - ex::sender auto snd = exec::when_any( // - completes_if{false} | ex::then([] { return 1; }), // - completes_if{true} | ex::then([] { return 42; }) // - ); - wait_for_value(std::move(snd), 42); - - ex::sender auto snd2 = exec::when_any( // - completes_if{true} | ex::then([] { return 1; }), // - completes_if{false} | ex::then([] { return 42; }) // - ); - wait_for_value(std::move(snd2), 1); -} +using namespace stdexec; -TEST_CASE("when_any with move-only types", "[adaptors][when_any]") { - ex::sender auto snd = exec::when_any( // - completes_if{false} | ex::then([] { return movable(1); }), - ex::just(movable(42)) // - ); - wait_for_value(std::move(snd), movable(42)); -} +namespace { -TEST_CASE("when_any forwards stop signal", "[adaptors][when_any]") { - stopped_scheduler stop; - int result = 42; - ex::sender auto snd = - exec::when_any( // - completes_if{false}, // - ex::schedule(stop) // - ) - | ex::then([&result] { result += 1; }); - ex::sync_wait(std::move(snd)); - REQUIRE(result == 42); -} + TEST_CASE("when_ny returns a sender", "[adaptors][when_any]") { + auto snd = exec::when_any(ex::just(3), ex::just(0.1415)); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE("nested when_any is stoppable", "[adaptors][when_any]") { - int result = 41; - ex::sender auto snd = - exec::when_any( - exec::when_any(completes_if{false}, completes_if{false}), - completes_if{false}, - ex::just(), - completes_if{false}) - | ex::then([&result] { result += 1; }); - ex::sync_wait(std::move(snd)); - REQUIRE(result == 42); -} + TEST_CASE("when_any with environment returns a sender", "[adaptors][when_any]") { + auto snd = exec::when_any(ex::just(3), ex::just(0.1415)); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("stop is forwarded", "[adaptors][when_any]") { - int result = 41; - ex::sender auto snd = exec::when_any(ex::just_stopped(), completes_if{false}) - | ex::upon_stopped([&result] { result += 1; }); - ex::sync_wait(std::move(snd)); - REQUIRE(result == 42); -} + TEST_CASE("when_any simple example", "[adaptors][when_any]") { + auto snd = exec::when_any(ex::just(3.0)); + auto snd1 = std::move(snd) | ex::then([](double y) { return y + 0.1415; }); + const double expected = 3.0 + 0.1415; + auto op = ex::connect(std::move(snd1), expect_value_receiver{expected}); + ex::start(op); + } -TEST_CASE("when_any is thread-safe", "[adaptors][when_any]") { - exec::single_thread_context ctx1; - exec::single_thread_context ctx2; - exec::single_thread_context ctx3; + TEST_CASE("when_any completes with only one sender", "[adaptors][when_any]") { + ex::sender auto snd = exec::when_any( // + completes_if{false} | ex::then([] { return 1; }), // + completes_if{true} | ex::then([] { return 42; }) // + ); + wait_for_value(std::move(snd), 42); + + ex::sender auto snd2 = exec::when_any( // + completes_if{true} | ex::then([] { return 1; }), // + completes_if{false} | ex::then([] { return 42; }) // + ); + wait_for_value(std::move(snd2), 1); + } - auto sch1 = ex::schedule(ctx1.get_scheduler()); - auto sch2 = ex::schedule(ctx2.get_scheduler()); - auto sch3 = ex::schedule(ctx3.get_scheduler()); + TEST_CASE("when_any with move-only types", "[adaptors][when_any]") { + ex::sender auto snd = exec::when_any( // + completes_if{false} | ex::then([] { return movable(1); }), + ex::just(movable(42)) // + ); + wait_for_value(std::move(snd), movable(42)); + } - int result = 41; + TEST_CASE("when_any forwards stop signal", "[adaptors][when_any]") { + stopped_scheduler stop; + int result = 42; + ex::sender auto snd = + exec::when_any( // + completes_if{false}, // + ex::schedule(stop) // + ) + | ex::then([&result] { result += 1; }); + ex::sync_wait(std::move(snd)); + REQUIRE(result == 42); + } - ex::sender auto snd = exec::when_any( - sch1 | ex::let_value([] { return exec::when_any(completes_if{false}); }), - sch2 | ex::let_value([] { return completes_if{false}; }), - sch3 | ex::then([&result] { result += 1; }), - completes_if{false}); + TEST_CASE("nested when_any is stoppable", "[adaptors][when_any]") { + int result = 41; + ex::sender auto snd = + exec::when_any( + exec::when_any(completes_if{false}, completes_if{false}), + completes_if{false}, + ex::just(), + completes_if{false}) + | ex::then([&result] { result += 1; }); + ex::sync_wait(std::move(snd)); + REQUIRE(result == 42); + } - ex::sync_wait(std::move(snd)); - REQUIRE(result == 42); -} + TEST_CASE("stop is forwarded", "[adaptors][when_any]") { + int result = 41; + ex::sender auto snd = exec::when_any(ex::just_stopped(), completes_if{false}) + | ex::upon_stopped([&result] { result += 1; }); + ex::sync_wait(std::move(snd)); + REQUIRE(result == 42); + } -using namespace stdexec; + TEST_CASE("when_any is thread-safe", "[adaptors][when_any]") { + exec::single_thread_context ctx1; + exec::single_thread_context ctx2; + exec::single_thread_context ctx3; -namespace stdexec { - template - struct __mall_contained_in { - template - using __f = __mand<__mapply<__contains, Haystack>...>; - }; + auto sch1 = ex::schedule(ctx1.get_scheduler()); + auto sch2 = ex::schedule(ctx2.get_scheduler()); + auto sch3 = ex::schedule(ctx3.get_scheduler()); - template - using __all_contained_in = __mapply<__mall_contained_in, Needles>; + int result = 41; - template - using __equivalent = __mand< - __all_contained_in, - std::is_same, ex::__mapply>>; -} + ex::sender auto snd = exec::when_any( + sch1 | ex::let_value([] { return exec::when_any(completes_if{false}); }), + sch2 | ex::let_value([] { return completes_if{false}; }), + sch3 | ex::then([&result] { result += 1; }), + completes_if{false}); -TEST_CASE("when_any completion signatures", "[adaptors][when_any]") { - struct move_throws { - move_throws() = default; + ex::sync_wait(std::move(snd)); + REQUIRE(result == 42); + } - move_throws(move_throws&&) noexcept(false) { - } + TEST_CASE("when_any completion signatures", "[adaptors][when_any]") { + struct move_throws { + move_throws() = default; + + move_throws(move_throws&&) noexcept(false) { + } + + move_throws& operator=(move_throws&&) noexcept(false) { + return *this; + } + }; + + auto just = exec::when_any(ex::just()); + static_assert(sender); + static_assert(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + + auto just_string = exec::when_any(ex::just(std::string("foo"))); + static_assert(sender); + static_assert(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + + auto just_stopped = exec::when_any(ex::just_stopped()); + static_assert(sender); + static_assert(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + + auto just_then = exec::when_any(ex::just() | ex::then([] { return 42; })); + static_assert(sender); + static_assert( + __v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + + auto just_then_noexcept = exec::when_any(ex::just() | ex::then([]() noexcept { return 42; })); + static_assert(sender); + static_assert(__v<__equivalent< + completion_signatures_of_t, + completion_signatures>>); + + auto just_move_throws = exec::when_any(ex::just(move_throws{})); + static_assert(sender); + static_assert( + __v<__equivalent< + completion_signatures_of_t, + completion_signatures< + set_value_t(move_throws&&), + set_stopped_t(), + set_error_t(std::exception_ptr)>>>); + + auto mulitple_senders = exec::when_any( + ex::just(3.1415), + ex::just(std::string()), + ex::just(std::string()), + ex::just() | ex::then([] { return 42; }), + ex::just() | ex::then([] { return 42; })); + static_assert(sender); + static_assert( + __v<__equivalent< + completion_signatures_of_t, + completion_signatures< + set_value_t(double&&), + set_value_t(std::string&&), + set_value_t(int&&), + set_stopped_t(), + set_error_t(std::exception_ptr)>>>); + // wait_for_value(std::move(snd), movable(42)); + } - move_throws& operator=(move_throws&&) noexcept(false) { - return *this; + template + struct dup_op { + Receiver rec; + + friend void tag_invoke(start_t, dup_op& self) noexcept { + stdexec::set_error( + static_cast(self.rec), std::make_exception_ptr(std::runtime_error("dup"))); } }; - auto just = exec::when_any(ex::just()); - static_assert(sender); - static_assert(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); - - auto just_string = exec::when_any(ex::just(std::string("foo"))); - static_assert(sender); - static_assert(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); - - auto just_stopped = exec::when_any(ex::just_stopped()); - static_assert(sender); - static_assert(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); - - auto just_then = exec::when_any(ex::just() | ex::then([] { return 42; })); - static_assert(sender); - static_assert( - __v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); - - auto just_then_noexcept = exec::when_any(ex::just() | ex::then([]() noexcept { return 42; })); - static_assert(sender); - static_assert(__v<__equivalent< - completion_signatures_of_t, - completion_signatures>>); - - auto just_move_throws = exec::when_any(ex::just(move_throws{})); - static_assert(sender); - static_assert( - __v<__equivalent< - completion_signatures_of_t, - completion_signatures< - set_value_t(move_throws&&), - set_stopped_t(), - set_error_t(std::exception_ptr)>>>); - - auto mulitple_senders = exec::when_any( - ex::just(3.1415), - ex::just(std::string()), - ex::just(std::string()), - ex::just() | ex::then([] { return 42; }), - ex::just() | ex::then([] { return 42; })); - static_assert(sender); - static_assert( - __v<__equivalent< - completion_signatures_of_t, - completion_signatures< - set_value_t(double&&), - set_value_t(std::string&&), - set_value_t(int&&), - set_stopped_t(), - set_error_t(std::exception_ptr)>>>); - // wait_for_value(std::move(snd), movable(42)); -} - -template -struct dup_op { - Receiver rec; + struct dup_sender { + using is_sender = void; + using completion_signatures = stdexec::completion_signatures< + set_value_t(), + set_error_t(std::exception_ptr), + set_error_t(std::exception_ptr&&)>; - friend void tag_invoke(start_t, dup_op& self) noexcept { - stdexec::set_error( - static_cast(self.rec), std::make_exception_ptr(std::runtime_error("dup"))); - } -}; - -struct dup_sender { - using is_sender = void; - using completion_signatures = stdexec::completion_signatures< - set_value_t(), - set_error_t(std::exception_ptr), - set_error_t(std::exception_ptr&&)>; + template + friend dup_op tag_invoke(connect_t, dup_sender, Receiver rec) noexcept { + return {static_cast(rec)}; + } + }; - template - friend dup_op tag_invoke(connect_t, dup_sender, Receiver rec) noexcept { - return {static_cast(rec)}; + TEST_CASE("when_any - with duplicate completions", "[adaptors][when_any]") { + REQUIRE_THROWS(stdexec::sync_wait(exec::when_any(dup_sender{}))); } -}; - -TEST_CASE("when_any - with duplicate completions", "[adaptors][when_any]") { - REQUIRE_THROWS(stdexec::sync_wait(exec::when_any(dup_sender{}))); -} \ No newline at end of file +} diff --git a/test/nvexec/bulk.cpp b/test/nvexec/bulk.cpp index 273e29ed7..72f60c2d8 100644 --- a/test/nvexec/bulk.cpp +++ b/test/nvexec/bulk.cpp @@ -10,174 +10,177 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE("nvexec bulk returns a sender", "[cuda][stream][adaptors][bulk]") { - nvexec::stream_context stream_ctx{}; - auto snd = ex::bulk(ex::schedule(stream_ctx.get_scheduler()), 42, [] {}); - STATIC_REQUIRE(ex::sender); - (void) snd; -} - -TEST_CASE("nvexec bulk executes on GPU", "[cuda][stream][adaptors][bulk]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t<4> flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::bulk(4, [=](int idx) { - if (is_on_gpu()) { - flags.set(idx); - } - }); - stdexec::sync_wait(std::move(snd)); +namespace { - REQUIRE(flags_storage.all_set_once()); -} + TEST_CASE("nvexec bulk returns a sender", "[cuda][stream][adaptors][bulk]") { + nvexec::stream_context stream_ctx{}; + auto snd = ex::bulk(ex::schedule(stream_ctx.get_scheduler()), 42, [] {}); + STATIC_REQUIRE(ex::sender); + (void) snd; + } -TEST_CASE("nvexec bulk forwards values on GPU", "[cuda][stream][adaptors][bulk]") { - nvexec::stream_context stream_ctx{}; + TEST_CASE("nvexec bulk executes on GPU", "[cuda][stream][adaptors][bulk]") { + nvexec::stream_context stream_ctx{}; - flags_storage_t<1024> flags_storage{}; - auto flags = flags_storage.get(); + flags_storage_t<4> flags_storage{}; + auto flags = flags_storage.get(); - auto snd = ex::transfer_just(stream_ctx.get_scheduler(), 42) // - | ex::bulk(1024, [=](int idx, int val) { - if (is_on_gpu()) { - if (val == 42) { + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | ex::bulk(4, [=](int idx) { + if (is_on_gpu()) { flags.set(idx); } - } - }); - stdexec::sync_wait(std::move(snd)); + }); + stdexec::sync_wait(std::move(snd)); - REQUIRE(flags_storage.all_set_once()); -} + REQUIRE(flags_storage.all_set_once()); + } -TEST_CASE("nvexec bulk forwards multiple values on GPU", "[cuda][stream][adaptors][bulk]") { - nvexec::stream_context stream_ctx{}; + TEST_CASE("nvexec bulk forwards values on GPU", "[cuda][stream][adaptors][bulk]") { + nvexec::stream_context stream_ctx{}; - flags_storage_t<2> flags_storage{}; - auto flags = flags_storage.get(); + flags_storage_t<1024> flags_storage{}; + auto flags = flags_storage.get(); - auto snd = ex::transfer_just(stream_ctx.get_scheduler(), 42, 4.2) // - | ex::bulk(2, [=](int idx, int i, double d) { - if (is_on_gpu()) { - if (i == 42 && d == 4.2) { - flags.set(idx); + auto snd = ex::transfer_just(stream_ctx.get_scheduler(), 42) // + | ex::bulk(1024, [=](int idx, int val) { + if (is_on_gpu()) { + if (val == 42) { + flags.set(idx); + } } - } - }); - const auto [i, d] = stdexec::sync_wait(std::move(snd)).value(); + }); + stdexec::sync_wait(std::move(snd)); - REQUIRE(flags_storage.all_set_once()); - REQUIRE(i == 42); - REQUIRE(d == 4.2); -} + REQUIRE(flags_storage.all_set_once()); + } -TEST_CASE( - "bulk forwards values that can be taken by reference on GPU", - "[cuda][stream][adaptors][bulk]") { - nvexec::stream_context stream_ctx{}; + TEST_CASE("nvexec bulk forwards multiple values on GPU", "[cuda][stream][adaptors][bulk]") { + nvexec::stream_context stream_ctx{}; - flags_storage_t<1024> flags_storage{}; - using flags_t = flags_storage_t<1024>::flags_t; - auto flags = flags_storage.get(); + flags_storage_t<2> flags_storage{}; + auto flags = flags_storage.get(); - auto snd = ex::transfer_just(stream_ctx.get_scheduler(), flags) // - | ex::bulk(1024, [](int idx, const flags_t& flags) { - if (is_on_gpu()) { - flags.set(idx); - } - }); - auto [flags_actual] = stdexec::sync_wait(std::move(snd)).value(); + auto snd = ex::transfer_just(stream_ctx.get_scheduler(), 42, 4.2) // + | ex::bulk(2, [=](int idx, int i, double d) { + if (is_on_gpu()) { + if (i == 42 && d == 4.2) { + flags.set(idx); + } + } + }); + const auto [i, d] = stdexec::sync_wait(std::move(snd)).value(); - REQUIRE(flags_storage.all_set_once()); -} + REQUIRE(flags_storage.all_set_once()); + REQUIRE(i == 42); + REQUIRE(d == 4.2); + } -TEST_CASE("nvexec bulk can preceed a sender without values", "[cuda][stream][adaptors][bulk]") { - nvexec::stream_context stream_ctx{}; + TEST_CASE( + "bulk forwards values that can be taken by reference on GPU", + "[cuda][stream][adaptors][bulk]") { + nvexec::stream_context stream_ctx{}; - flags_storage_t<3> flags_storage{}; - auto flags = flags_storage.get(); + flags_storage_t<1024> flags_storage{}; + using flags_t = flags_storage_t<1024>::flags_t; + auto flags = flags_storage.get(); - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::bulk( - 2, - [flags](int idx) { + auto snd = ex::transfer_just(stream_ctx.get_scheduler(), flags) // + | ex::bulk(1024, [](int idx, const flags_t& flags) { if (is_on_gpu()) { flags.set(idx); } - }) - | a_sender([flags] { - if (is_on_gpu()) { - flags.set(2); - } - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} + }); + auto [flags_actual] = stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(flags_storage.all_set_once()); + } -TEST_CASE("nvexec bulk can succeed a sender", "[cuda][stream][adaptors][bulk]") { - SECTION("without values") { + TEST_CASE("nvexec bulk can preceed a sender without values", "[cuda][stream][adaptors][bulk]") { nvexec::stream_context stream_ctx{}; + flags_storage_t<3> flags_storage{}; auto flags = flags_storage.get(); auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | ex::bulk( + 2, + [flags](int idx) { + if (is_on_gpu()) { + flags.set(idx); + } + }) | a_sender([flags] { if (is_on_gpu()) { flags.set(2); } - }) - | ex::bulk(2, [flags](int idx) { - if (is_on_gpu()) { - flags.set(idx); - } }); stdexec::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } - SECTION("with values") { - nvexec::stream_context stream_ctx{}; - flags_storage_t<2> flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | a_sender([]() -> bool { return is_on_gpu(); }) - | ex::bulk(2, [flags](int idx, bool a_sender_was_on_gpu) { - if (a_sender_was_on_gpu && is_on_gpu()) { - flags.set(idx); - } - }); - stdexec::sync_wait(std::move(snd)).value(); - - REQUIRE(flags_storage.all_set_once()); + TEST_CASE("nvexec bulk can succeed a sender", "[cuda][stream][adaptors][bulk]") { + SECTION("without values") { + nvexec::stream_context stream_ctx{}; + flags_storage_t<3> flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | a_sender([flags] { + if (is_on_gpu()) { + flags.set(2); + } + }) + | ex::bulk(2, [flags](int idx) { + if (is_on_gpu()) { + flags.set(idx); + } + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + SECTION("with values") { + nvexec::stream_context stream_ctx{}; + flags_storage_t<2> flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | a_sender([]() -> bool { return is_on_gpu(); }) + | ex::bulk(2, [flags](int idx, bool a_sender_was_on_gpu) { + if (a_sender_was_on_gpu && is_on_gpu()) { + flags.set(idx); + } + }); + stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(flags_storage.all_set_once()); + } } -} -TEST_CASE( - "nvexec bulk can succeed a sender that sends ref into opstate", - "[cuda][stream][adaptors][bulk]") { - nvexec::stream_context ctx; + TEST_CASE( + "nvexec bulk can succeed a sender that sends ref into opstate", + "[cuda][stream][adaptors][bulk]") { + nvexec::stream_context ctx; - double* inout = nullptr; - const int nelems = 10; - cudaMallocManaged(&inout, nelems * sizeof(double)); + double* inout = nullptr; + const int nelems = 10; + cudaMallocManaged(&inout, nelems * sizeof(double)); - auto task = - stdexec::transfer_just(ctx.get_scheduler(), std::span{inout, nelems}) - | stdexec::bulk(nelems, [](std::size_t i, std::span out) { out[i] = i; }) - | stdexec::let_value([](std::span out) { return stdexec::just(out); }) - | stdexec::bulk(nelems, [](std::size_t i, std::span out) { out[i] = 2.0 * out[i]; }); + auto task = + stdexec::transfer_just(ctx.get_scheduler(), std::span{inout, nelems}) + | stdexec::bulk(nelems, [](std::size_t i, std::span out) { out[i] = i; }) + | stdexec::let_value([](std::span out) { return stdexec::just(out); }) + | stdexec::bulk(nelems, [](std::size_t i, std::span out) { out[i] = 2.0 * out[i]; }); - stdexec::sync_wait(std::move(task)).value(); + stdexec::sync_wait(std::move(task)).value(); - for (int i = 0; i < nelems; ++i) { - REQUIRE(i * 2 == (int) inout[i]); - } + for (int i = 0; i < nelems; ++i) { + REQUIRE(i * 2 == (int) inout[i]); + } - cudaFree(inout); + cudaFree(inout); + } } diff --git a/test/nvexec/common.cuh b/test/nvexec/common.cuh index 935c68799..b433d8803 100644 --- a/test/nvexec/common.cuh +++ b/test/nvexec/common.cuh @@ -40,312 +40,315 @@ namespace nvexec { /**/ } // namespace nvexec -template - requires(N > 0) -class flags_storage_t { - int* flags_{}; +namespace { - public: - class flags_t { + template + requires(N > 0) + class flags_storage_t { int* flags_{}; - flags_t(int* flags) - : flags_(flags) { - } - public: - __device__ __host__ void set(int idx = 0) const { - if (idx < N) { - flags_[idx] += 1; + class flags_t { + int* flags_{}; + + flags_t(int* flags) + : flags_(flags) { + } + + public: + __device__ __host__ void set(int idx = 0) const { + if (idx < N) { + flags_[idx] += 1; + } } + + friend flags_storage_t; + }; + + flags_storage_t(const flags_storage_t&) = delete; + flags_storage_t(flags_storage_t&&) = delete; + + void operator()(const flags_storage_t&) = delete; + void operator()(flags_storage_t&&) = delete; + + flags_t get() { + return {flags_}; } - friend flags_storage_t; - }; + flags_storage_t() { + THROW_ON_CUDA_ERROR(cudaMallocManaged(&flags_, sizeof(int) * N)); + THROW_ON_CUDA_ERROR(cudaMemset(flags_, 0, sizeof(int) * N)); + } - flags_storage_t(const flags_storage_t&) = delete; - flags_storage_t(flags_storage_t&&) = delete; + ~flags_storage_t() { + THROW_ON_CUDA_ERROR(cudaFree(flags_)); + flags_ = nullptr; + } - void operator()(const flags_storage_t&) = delete; - void operator()(flags_storage_t&&) = delete; + bool is_set_n_times(int n) { + int host_flags[N]; + THROW_ON_CUDA_ERROR(cudaMemcpy(host_flags, flags_, sizeof(int) * N, cudaMemcpyDeviceToHost)); - flags_t get() { - return {flags_}; - } + return std::count(host_flags, host_flags + N, n) == N; + } - flags_storage_t() { - THROW_ON_CUDA_ERROR(cudaMallocManaged(&flags_, sizeof(int) * N)); - THROW_ON_CUDA_ERROR(cudaMemset(flags_, 0, sizeof(int) * N)); - } + bool all_set_once() { + return is_set_n_times(1); + } - ~flags_storage_t() { - THROW_ON_CUDA_ERROR(cudaFree(flags_)); - flags_ = nullptr; - } + bool all_unset() { + return !all_set_once(); + } + }; - bool is_set_n_times(int n) { - int host_flags[N]; - THROW_ON_CUDA_ERROR(cudaMemcpy(host_flags, flags_, sizeof(int) * N, cudaMemcpyDeviceToHost)); + namespace detail::a_sender { + template + struct operation_state_t { + using Sender = stdexec::__t; + using Receiver = stdexec::__t; + using inner_op_state_t = stdexec::connect_result_t; - return std::count(host_flags, host_flags + N, n) == N; - } + inner_op_state_t inner_op_; - bool all_set_once() { - return is_set_n_times(1); - } + friend void tag_invoke(stdexec::start_t, operation_state_t& op) noexcept { + stdexec::start(op.inner_op_); + } - bool all_unset() { - return !all_set_once(); - } -}; + operation_state_t(Sender&& sender, Receiver&& receiver) + : inner_op_{stdexec::connect((Sender&&) sender, (Receiver&&) receiver)} { + } + }; + + template + class receiver_t + : stdexec::receiver_adaptor, stdexec::__t> { + using Receiver = stdexec::__t; + friend stdexec::receiver_adaptor; + + static_assert(std::is_trivially_copyable_v); + static_assert(std::is_trivially_copyable_v); + Fun f_; + + template + STDEXEC_ATTRIBUTE((host, device)) + void set_value(As&&... as) && noexcept + requires stdexec::__callable + { + using result_t = std::invoke_result_t; + + if constexpr (std::is_same_v) { + f_((As&&) as...); + stdexec::set_value(std::move(this->base())); + } else { + stdexec::set_value(std::move(this->base()), f_((As&&) as...)); + } + } -namespace detail::a_sender { - template - struct operation_state_t { - using Sender = stdexec::__t; - using Receiver = stdexec::__t; - using inner_op_state_t = stdexec::connect_result_t; + public: + explicit receiver_t(Receiver rcvr, Fun fun) + : stdexec::receiver_adaptor((Receiver&&) rcvr) + , f_((Fun&&) fun) { + } + }; + + template + struct sender_t { + using is_sender = void; + using Sender = stdexec::__t; + using Fun = stdexec::__t; + + Sender sndr_; + Fun fun_; + + template + using receiver_th = receiver_t, Fun>; + + template + using op_t = operation_state_t< + stdexec::__x>, + stdexec::__id>>; + + template + using completion_signatures = stdexec::__try_make_completion_signatures< + stdexec::__copy_cvref_t, + Env, + stdexec::completion_signatures<>, + stdexec::__mbind_front_q>; + + template Self, stdexec::receiver Receiver> + requires stdexec:: + receiver_of>> + friend auto + tag_invoke(stdexec::connect_t, Self&& self, Receiver&& rcvr) -> op_t { + return op_t( + ((Self&&) self).sndr_, receiver_th((Receiver&&) rcvr, self.fun_)); + } - inner_op_state_t inner_op_; + template Self, class Env> + friend auto tag_invoke(stdexec::get_completion_signatures_t, Self&&, Env) + -> completion_signatures { + return {}; + } - friend void tag_invoke(stdexec::start_t, operation_state_t& op) noexcept { - stdexec::start(op.inner_op_); - } + friend auto tag_invoke(stdexec::get_env_t, const sender_t& self) // + noexcept(stdexec::__nothrow_callable) + -> stdexec::env_of_t { + return stdexec::get_env(self.sndr_); + } + }; + } // namespace detail::a_sender - operation_state_t(Sender&& sender, Receiver&& receiver) - : inner_op_{stdexec::connect((Sender&&) sender, (Receiver&&) receiver)} { - } - }; + namespace detail::a_receiverless_sender { + template + struct operation_state_t { + using Sender = stdexec::__t; + using Receiver = stdexec::__t; + using inner_op_state_t = stdexec::connect_result_t; - template - class receiver_t - : stdexec::receiver_adaptor, stdexec::__t> { - using Receiver = stdexec::__t; - friend stdexec::receiver_adaptor; - - static_assert(std::is_trivially_copyable_v); - static_assert(std::is_trivially_copyable_v); - Fun f_; - - template - STDEXEC_ATTRIBUTE((host, device)) - void set_value(As&&... as) && noexcept - requires stdexec::__callable - { - using result_t = std::invoke_result_t; - - if constexpr (std::is_same_v) { - f_((As&&) as...); - stdexec::set_value(std::move(this->base())); - } else { - stdexec::set_value(std::move(this->base()), f_((As&&) as...)); + inner_op_state_t inner_op_; + + friend void tag_invoke(stdexec::start_t, operation_state_t& op) noexcept { + stdexec::start(op.inner_op_); } - } - public: - explicit receiver_t(Receiver rcvr, Fun fun) - : stdexec::receiver_adaptor((Receiver&&) rcvr) - , f_((Fun&&) fun) { - } - }; + operation_state_t(Sender&& sender, Receiver&& receiver) + : inner_op_{stdexec::connect((Sender&&) sender, (Receiver&&) receiver)} { + } + }; + + template + struct sender_t { + using is_sender = void; + using Sender = stdexec::__t; + + Sender sndr_; + + template + using op_t = operation_state_t< + stdexec::__x>, + stdexec::__id>; + + template + using completion_signatures = stdexec::__try_make_completion_signatures< + stdexec::__copy_cvref_t, + Env, + stdexec::completion_signatures<>>; + + template Self, stdexec::receiver Receiver> + requires stdexec:: + receiver_of>> + friend auto + tag_invoke(stdexec::connect_t, Self&& self, Receiver&& rcvr) -> op_t { + return op_t(((Self&&) self).sndr_, (Receiver&&) rcvr); + } - template - struct sender_t { - using is_sender = void; - using Sender = stdexec::__t; - using Fun = stdexec::__t; - - Sender sndr_; - Fun fun_; - - template - using receiver_th = receiver_t, Fun>; - - template - using op_t = operation_state_t< - stdexec::__x>, - stdexec::__id>>; - - template - using completion_signatures = stdexec::__try_make_completion_signatures< - stdexec::__copy_cvref_t, - Env, - stdexec::completion_signatures<>, - stdexec::__mbind_front_q>; - - template Self, stdexec::receiver Receiver> - requires stdexec:: - receiver_of>> - friend auto - tag_invoke(stdexec::connect_t, Self&& self, Receiver&& rcvr) -> op_t { - return op_t( - ((Self&&) self).sndr_, receiver_th((Receiver&&) rcvr, self.fun_)); - } + template Self, class Env> + friend auto tag_invoke(stdexec::get_completion_signatures_t, Self&&, Env) + -> completion_signatures { + return {}; + } - template Self, class Env> - friend auto tag_invoke(stdexec::get_completion_signatures_t, Self&&, Env) - -> completion_signatures { - return {}; - } + friend auto tag_invoke(stdexec::get_env_t, const sender_t& self) // + noexcept(stdexec::__nothrow_callable) + -> stdexec::env_of_t { + return stdexec::get_env(self.sndr_); + } + }; + } // namespace detail::a_receiverless_sender - friend auto tag_invoke(stdexec::get_env_t, const sender_t& self) // - noexcept(stdexec::__nothrow_callable) - -> stdexec::env_of_t { - return stdexec::get_env(self.sndr_); - } + enum class a_sender_kind { + then, + receiverless }; -} // namespace detail::a_sender -namespace detail::a_receiverless_sender { - template - struct operation_state_t { - using Sender = stdexec::__t; - using Receiver = stdexec::__t; - using inner_op_state_t = stdexec::connect_result_t; + template + struct a_sender_helper_t; - inner_op_state_t inner_op_; + template <> + struct a_sender_helper_t { + template + using sender_th = detail::a_sender:: + sender_t>, stdexec::__x>>; - friend void tag_invoke(stdexec::start_t, operation_state_t& op) noexcept { - stdexec::start(op.inner_op_); + template + requires stdexec::sender> + sender_th<_Sender, _Fun> operator()(_Sender&& __sndr, _Fun __fun) const { + return sender_th<_Sender, _Fun>{(_Sender&&) __sndr, (_Fun&&) __fun}; } - operation_state_t(Sender&& sender, Receiver&& receiver) - : inner_op_{stdexec::connect((Sender&&) sender, (Receiver&&) receiver)} { - } + template + stdexec::__binder_back, _Fun> + operator()(_Fun __fun) const { + return {{}, {}, {(_Fun&&) __fun}}; + }; }; - template - struct sender_t { - using is_sender = void; - using Sender = stdexec::__t; - - Sender sndr_; - - template - using op_t = operation_state_t< - stdexec::__x>, - stdexec::__id>; - - template - using completion_signatures = stdexec::__try_make_completion_signatures< - stdexec::__copy_cvref_t, - Env, - stdexec::completion_signatures<>>; - - template Self, stdexec::receiver Receiver> - requires stdexec:: - receiver_of>> - friend auto - tag_invoke(stdexec::connect_t, Self&& self, Receiver&& rcvr) -> op_t { - return op_t(((Self&&) self).sndr_, (Receiver&&) rcvr); - } + template <> + struct a_sender_helper_t { + template + using receiverless_sender_th = + detail::a_receiverless_sender::sender_t>>; - template Self, class Env> - friend auto tag_invoke(stdexec::get_completion_signatures_t, Self&&, Env) - -> completion_signatures { - return {}; + template + requires stdexec::sender> + receiverless_sender_th<_Sender> operator()(_Sender&& __sndr) const { + return receiverless_sender_th<_Sender>{(_Sender&&) __sndr}; } - friend auto tag_invoke(stdexec::get_env_t, const sender_t& self) // - noexcept(stdexec::__nothrow_callable) - -> stdexec::env_of_t { - return stdexec::get_env(self.sndr_); + stdexec::__binder_back> operator()() const { + return {{}, {}, {}}; } }; -} // namespace detail::a_receiverless_sender - -enum class a_sender_kind { - then, - receiverless -}; - -template -struct a_sender_helper_t; - -template <> -struct a_sender_helper_t { - template - using sender_th = detail::a_sender:: - sender_t>, stdexec::__x>>; - - template - requires stdexec::sender> - sender_th<_Sender, _Fun> operator()(_Sender&& __sndr, _Fun __fun) const { - return sender_th<_Sender, _Fun>{(_Sender&&) __sndr, (_Fun&&) __fun}; - } - template - stdexec::__binder_back, _Fun> - operator()(_Fun __fun) const { - return {{}, {}, {(_Fun&&) __fun}}; + struct a_sender_t + : a_sender_helper_t + , a_sender_helper_t { + using a_sender_helper_t::operator(); + using a_sender_helper_t::operator(); }; -}; - -template <> -struct a_sender_helper_t { - template - using receiverless_sender_th = - detail::a_receiverless_sender::sender_t>>; - - template - requires stdexec::sender> - receiverless_sender_th<_Sender> operator()(_Sender&& __sndr) const { - return receiverless_sender_th<_Sender>{(_Sender&&) __sndr}; - } - stdexec::__binder_back> operator()() const { - return {{}, {}, {}}; - } -}; - -struct a_sender_t - : a_sender_helper_t - , a_sender_helper_t { - using a_sender_helper_t::operator(); - using a_sender_helper_t::operator(); -}; - -constexpr a_sender_t a_sender; - -struct move_only_t { - static constexpr int invalid() { - return -42; - } + constexpr a_sender_t a_sender; - move_only_t() = delete; - move_only_t(const move_only_t&) = delete; - move_only_t& operator=(move_only_t&&) = delete; - move_only_t& operator=(const move_only_t&) = delete; + struct move_only_t { + static constexpr int invalid() { + return -42; + } - __host__ __device__ move_only_t(int data) - : data_(data) - , self_(this) { - } + move_only_t() = delete; + move_only_t(const move_only_t&) = delete; + move_only_t& operator=(move_only_t&&) = delete; + move_only_t& operator=(const move_only_t&) = delete; - __host__ __device__ move_only_t(move_only_t&& other) - : data_(std::exchange(other.data_, invalid())) - , self_(this) { - } + __host__ __device__ move_only_t(int data) + : data_(data) + , self_(this) { + } - __host__ __device__ ~move_only_t() { - if (this != self_) { - // TODO Trap - std::printf("Error: move_only_t::~move_only_t failed\n"); + __host__ __device__ move_only_t(move_only_t&& other) + : data_(std::exchange(other.data_, invalid())) + , self_(this) { } - data_ = invalid(); - } - __host__ __device__ bool contains(int val) { - if (this != self_) { - std::printf("Error: move_only_t::contains failed: %p\n", (void*) self_); - return false; + __host__ __device__ ~move_only_t() { + if (this != self_) { + // TODO Trap + std::printf("Error: move_only_t::~move_only_t failed\n"); + } + data_ = invalid(); } - return data_ == val; - } + __host__ __device__ bool contains(int val) { + if (this != self_) { + std::printf("Error: move_only_t::contains failed: %p\n", (void*) self_); + return false; + } + + return data_ == val; + } - int data_{invalid()}; - move_only_t* self_; -}; + int data_{invalid()}; + move_only_t* self_; + }; -static_assert(!std::is_trivially_copyable_v); + static_assert(!std::is_trivially_copyable_v); +} diff --git a/test/nvexec/ensure_started.cpp b/test/nvexec/ensure_started.cpp index e5dacafe5..67070664a 100644 --- a/test/nvexec/ensure_started.cpp +++ b/test/nvexec/ensure_started.cpp @@ -12,83 +12,60 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE("nvexec ensure_started is eager", "[cuda][stream][adaptors][ensure_started]") { - nvexec::stream_context stream_ctx{}; +namespace { - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::ensure_started( - ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([=] { - if (is_on_gpu()) { - flags.set(); - } - })); - cudaDeviceSynchronize(); - - REQUIRE(flags_storage.all_set_once()); + TEST_CASE("nvexec ensure_started is eager", "[cuda][stream][adaptors][ensure_started]") { + nvexec::stream_context stream_ctx{}; - stdexec::sync_wait(std::move(snd)); -} + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); -TEST_CASE("nvexec ensure_started propagates values", "[cuda][stream][adaptors][ensure_started]") { - nvexec::stream_context stream_ctx{}; + auto snd = ex::ensure_started( + ex::schedule(stream_ctx.get_scheduler()) // + | ex::then([=] { + if (is_on_gpu()) { + flags.set(); + } + })); + cudaDeviceSynchronize(); - auto snd1 = ex::ensure_started( - ex::schedule(stream_ctx.get_scheduler()) | ex::then([]() -> bool { return is_on_gpu(); })); + REQUIRE(flags_storage.all_set_once()); - auto snd2 = std::move(snd1) - | ex::then([](bool prev_on_gpu) -> int { return prev_on_gpu && is_on_gpu(); }); + stdexec::sync_wait(std::move(snd)); + } - auto [v] = stdexec::sync_wait(std::move(snd2)).value(); + TEST_CASE("nvexec ensure_started propagates values", "[cuda][stream][adaptors][ensure_started]") { + nvexec::stream_context stream_ctx{}; - REQUIRE(v == 1); -} + auto snd1 = ex::ensure_started( + ex::schedule(stream_ctx.get_scheduler()) | ex::then([]() -> bool { return is_on_gpu(); })); -TEST_CASE( - "ensure_started can preceed a sender without values", - "[cuda][stream][adaptors][ensure_started]") { - nvexec::stream_context stream_ctx{}; + auto snd2 = std::move(snd1) + | ex::then([](bool prev_on_gpu) -> int { return prev_on_gpu && is_on_gpu(); }); - flags_storage_t<2> flags_storage{}; - auto flags = flags_storage.get(); + auto [v] = stdexec::sync_wait(std::move(snd2)).value(); - auto snd = ex::ensure_started( - ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([flags] { - if (is_on_gpu()) { - flags.set(0); - } - })) - | a_sender([flags] { - if (is_on_gpu()) { - flags.set(1); - } - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} + REQUIRE(v == 1); + } -TEST_CASE( - "nvexec ensure_started can succeed a sender", - "[cuda][stream][adaptors][ensure_started]") { - SECTION("without values") { + TEST_CASE( + "ensure_started can preceed a sender without values", + "[cuda][stream][adaptors][ensure_started]") { nvexec::stream_context stream_ctx{}; + flags_storage_t<2> flags_storage{}; auto flags = flags_storage.get(); auto snd = ex::ensure_started( ex::schedule(stream_ctx.get_scheduler()) // - | a_sender([flags] { + | ex::then([flags] { if (is_on_gpu()) { - flags.set(1); + flags.set(0); } })) - | ex::then([flags] { + | a_sender([flags] { if (is_on_gpu()) { - flags.set(0); + flags.set(1); } }); stdexec::sync_wait(std::move(snd)); @@ -96,21 +73,47 @@ TEST_CASE( REQUIRE(flags_storage.all_set_once()); } - SECTION("with values") { - nvexec::stream_context stream_ctx{}; - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::ensure_started( - ex::schedule(stream_ctx.get_scheduler()) // - | a_sender([]() -> bool { return is_on_gpu(); })) - | ex::then([flags](bool a_sender_was_on_gpu) { - if (a_sender_was_on_gpu && is_on_gpu()) { - flags.set(); - } - }); - stdexec::sync_wait(std::move(snd)).value(); + TEST_CASE( + "nvexec ensure_started can succeed a sender", + "[cuda][stream][adaptors][ensure_started]") { + SECTION("without values") { + nvexec::stream_context stream_ctx{}; + flags_storage_t<2> flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::ensure_started( + ex::schedule(stream_ctx.get_scheduler()) // + | a_sender([flags] { + if (is_on_gpu()) { + flags.set(1); + } + })) + | ex::then([flags] { + if (is_on_gpu()) { + flags.set(0); + } + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + SECTION("with values") { + nvexec::stream_context stream_ctx{}; + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::ensure_started( + ex::schedule(stream_ctx.get_scheduler()) // + | a_sender([]() -> bool { return is_on_gpu(); })) + | ex::then([flags](bool a_sender_was_on_gpu) { + if (a_sender_was_on_gpu && is_on_gpu()) { + flags.set(); + } + }); + stdexec::sync_wait(std::move(snd)).value(); - REQUIRE(flags_storage.all_set_once()); + REQUIRE(flags_storage.all_set_once()); + } } } diff --git a/test/nvexec/launch.cpp b/test/nvexec/launch.cpp index ce41c2ed6..7688d81be 100644 --- a/test/nvexec/launch.cpp +++ b/test/nvexec/launch.cpp @@ -31,7 +31,7 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -namespace { +namespace { namespace { constexpr std::size_t N = 2 * 1024; constexpr std::size_t THREAD_BLOCK_SIZE = 128u; constexpr std::size_t NUM_BLOCKS = (N + THREAD_BLOCK_SIZE - 1) / THREAD_BLOCK_SIZE; @@ -87,4 +87,4 @@ namespace { REQUIRE(flags_storage.all_set_once()); } -} +}} diff --git a/test/nvexec/let_error.cpp b/test/nvexec/let_error.cpp index 6eee98f35..387106983 100644 --- a/test/nvexec/let_error.cpp +++ b/test/nvexec/let_error.cpp @@ -8,77 +8,80 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE("nvexec let_error returns a sender", "[cuda][stream][adaptors][let_error]") { - nvexec::stream_context stream_ctx{}; - - auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) - | ex::let_error([](int) { return ex::just(); }); - STATIC_REQUIRE(ex::sender); - (void) snd; -} - -TEST_CASE("nvexec let_error executes on GPU", "[cuda][stream][adaptors][let_error]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) - | ex::let_error([=](int err) { - if (is_on_gpu() && err == 42) { - flags.set(); - } - - return ex::just(); - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} - -TEST_CASE( - "nvexec let_error can preceed a sender without values", - "[cuda][stream][adaptors][let_error]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t<2> flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::just_error(42) // - | ex::transfer(stream_ctx.get_scheduler()) // - | ex::let_error([flags](int err) { - if (is_on_gpu() && err == 42) { - flags.set(0); - } - - return ex::just(); - }) - | a_sender([flags] { - if (is_on_gpu()) { - flags.set(1); - } - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} - -TEST_CASE("nvexec let_error can succeed a sender", "[cuda][stream][adaptors][let_error]") { - nvexec::stream_context stream_ctx{}; - nvexec::stream_scheduler sch = stream_ctx.get_scheduler(); - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) // - | a_sender([]() noexcept {}) // - | ex::let_error([=](int err) { - if (is_on_gpu() && err == 42) { - flags.set(); - } - - return ex::schedule(sch); - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); +namespace { + + TEST_CASE("nvexec let_error returns a sender", "[cuda][stream][adaptors][let_error]") { + nvexec::stream_context stream_ctx{}; + + auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) + | ex::let_error([](int) { return ex::just(); }); + STATIC_REQUIRE(ex::sender); + (void) snd; + } + + TEST_CASE("nvexec let_error executes on GPU", "[cuda][stream][adaptors][let_error]") { + nvexec::stream_context stream_ctx{}; + + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) + | ex::let_error([=](int err) { + if (is_on_gpu() && err == 42) { + flags.set(); + } + + return ex::just(); + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + TEST_CASE( + "nvexec let_error can preceed a sender without values", + "[cuda][stream][adaptors][let_error]") { + nvexec::stream_context stream_ctx{}; + + flags_storage_t<2> flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::just_error(42) // + | ex::transfer(stream_ctx.get_scheduler()) // + | ex::let_error([flags](int err) { + if (is_on_gpu() && err == 42) { + flags.set(0); + } + + return ex::just(); + }) + | a_sender([flags] { + if (is_on_gpu()) { + flags.set(1); + } + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + TEST_CASE("nvexec let_error can succeed a sender", "[cuda][stream][adaptors][let_error]") { + nvexec::stream_context stream_ctx{}; + nvexec::stream_scheduler sch = stream_ctx.get_scheduler(); + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) // + | a_sender([]() noexcept {}) // + | ex::let_error([=](int err) { + if (is_on_gpu() && err == 42) { + flags.set(); + } + + return ex::schedule(sch); + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } } diff --git a/test/nvexec/let_stopped.cpp b/test/nvexec/let_stopped.cpp index 829b765cb..843545632 100644 --- a/test/nvexec/let_stopped.cpp +++ b/test/nvexec/let_stopped.cpp @@ -8,75 +8,78 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE("nvexec let_stopped returns a sender", "[cuda][stream][adaptors][let_stopped]") { - nvexec::stream_context stream_ctx{}; - - auto snd = ex::just_stopped() | ex::transfer(stream_ctx.get_scheduler()) - | ex::let_stopped([] { return ex::just(); }); - STATIC_REQUIRE(ex::sender); - (void) snd; -} - -TEST_CASE("nvexec let_stopped executes on GPU", "[cuda][stream][adaptors][let_stopped]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::just_stopped() // - | ex::transfer(stream_ctx.get_scheduler()) | ex::let_stopped([=] { - if (is_on_gpu()) { - flags.set(); - } - - return ex::just(); - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} - -TEST_CASE( - "let_stopped can preceed a sender without values", - "[cuda][stream][adaptors][let_stopped]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t<2> flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::just_stopped() | ex::transfer(stream_ctx.get_scheduler()) // - | ex::let_stopped([flags] { - if (is_on_gpu()) { - flags.set(0); - } - - return ex::just(); - }) - | a_sender([flags] { - if (is_on_gpu()) { - flags.set(1); - } - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} - -TEST_CASE("nvexec let_stopped can succeed a sender", "[cuda][stream][adaptors][let_stopped]") { - nvexec::stream_context stream_ctx{}; - nvexec::stream_scheduler sch = stream_ctx.get_scheduler(); - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::just_stopped() | ex::transfer(sch) | a_sender([]() noexcept {}) - | ex::let_stopped([=] { - if (is_on_gpu()) { - flags.set(); - } - - return ex::schedule(sch); - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); +namespace { + + TEST_CASE("nvexec let_stopped returns a sender", "[cuda][stream][adaptors][let_stopped]") { + nvexec::stream_context stream_ctx{}; + + auto snd = ex::just_stopped() | ex::transfer(stream_ctx.get_scheduler()) + | ex::let_stopped([] { return ex::just(); }); + STATIC_REQUIRE(ex::sender); + (void) snd; + } + + TEST_CASE("nvexec let_stopped executes on GPU", "[cuda][stream][adaptors][let_stopped]") { + nvexec::stream_context stream_ctx{}; + + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::just_stopped() // + | ex::transfer(stream_ctx.get_scheduler()) | ex::let_stopped([=] { + if (is_on_gpu()) { + flags.set(); + } + + return ex::just(); + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + TEST_CASE( + "let_stopped can preceed a sender without values", + "[cuda][stream][adaptors][let_stopped]") { + nvexec::stream_context stream_ctx{}; + + flags_storage_t<2> flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::just_stopped() | ex::transfer(stream_ctx.get_scheduler()) // + | ex::let_stopped([flags] { + if (is_on_gpu()) { + flags.set(0); + } + + return ex::just(); + }) + | a_sender([flags] { + if (is_on_gpu()) { + flags.set(1); + } + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + TEST_CASE("nvexec let_stopped can succeed a sender", "[cuda][stream][adaptors][let_stopped]") { + nvexec::stream_context stream_ctx{}; + nvexec::stream_scheduler sch = stream_ctx.get_scheduler(); + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::just_stopped() | ex::transfer(sch) | a_sender([]() noexcept {}) + | ex::let_stopped([=] { + if (is_on_gpu()) { + flags.set(); + } + + return ex::schedule(sch); + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } } diff --git a/test/nvexec/let_value.cpp b/test/nvexec/let_value.cpp index ec3412dab..b7eb6d78f 100644 --- a/test/nvexec/let_value.cpp +++ b/test/nvexec/let_value.cpp @@ -8,145 +8,148 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE("nvexec let_value returns a sender", "[cuda][stream][adaptors][let_value]") { - nvexec::stream_context stream_ctx{}; - auto snd = ex::let_value(ex::schedule(stream_ctx.get_scheduler()), [] { return ex::just(); }); - STATIC_REQUIRE(ex::sender); - (void) snd; -} +namespace { -TEST_CASE("nvexec let_value executes on GPU", "[cuda][stream][adaptors][let_value]") { - nvexec::stream_context stream_ctx{}; + TEST_CASE("nvexec let_value returns a sender", "[cuda][stream][adaptors][let_value]") { + nvexec::stream_context stream_ctx{}; + auto snd = ex::let_value(ex::schedule(stream_ctx.get_scheduler()), [] { return ex::just(); }); + STATIC_REQUIRE(ex::sender); + (void) snd; + } - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); + TEST_CASE("nvexec let_value executes on GPU", "[cuda][stream][adaptors][let_value]") { + nvexec::stream_context stream_ctx{}; - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::let_value([=] { - if (is_on_gpu()) { - flags.set(); - } - return ex::just(); - }); - stdexec::sync_wait(std::move(snd)); + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); - REQUIRE(flags_storage.all_set_once()); -} + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | ex::let_value([=] { + if (is_on_gpu()) { + flags.set(); + } + return ex::just(); + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + TEST_CASE("nvexec let_value accepts values on GPU", "[cuda][stream][adaptors][let_value]") { + nvexec::stream_context stream_ctx{}; + + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | ex::then([]() -> int { return 42; }) // + | ex::let_value([=](int val) { + if (is_on_gpu()) { + if (val == 42) { + flags.set(); + } + } + return ex::just(); + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + TEST_CASE( + "nvexec let_value accepts multiple values on GPU", + "[cuda][stream][adaptors][let_value]") { + nvexec::stream_context stream_ctx{}; + + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::transfer_just(stream_ctx.get_scheduler(), 42, 4.2) // + | ex::let_value([=](int i, double d) { + if (is_on_gpu()) { + if (i == 42 && d == 4.2) { + flags.set(); + } + } + return ex::just(); + }); + stdexec::sync_wait(std::move(snd)); -TEST_CASE("nvexec let_value accepts values on GPU", "[cuda][stream][adaptors][let_value]") { - nvexec::stream_context stream_ctx{}; + REQUIRE(flags_storage.all_set_once()); + } - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); + TEST_CASE("nvexec let_value returns values on GPU", "[cuda][stream][adaptors][let_value]") { + nvexec::stream_context stream_ctx{}; - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([]() -> int { return 42; }) // - | ex::let_value([=](int val) { - if (is_on_gpu()) { - if (val == 42) { - flags.set(); - } - } - return ex::just(); - }); - stdexec::sync_wait(std::move(snd)); + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | ex::let_value([=]() { return ex::just(is_on_gpu()); }); + const auto [result] = stdexec::sync_wait(std::move(snd)).value(); - REQUIRE(flags_storage.all_set_once()); -} + REQUIRE(result == 1); + } -TEST_CASE( - "nvexec let_value accepts multiple values on GPU", - "[cuda][stream][adaptors][let_value]") { - nvexec::stream_context stream_ctx{}; + TEST_CASE( + "nvexec let_value can preceed a sender without values", + "[cuda][stream][adaptors][let_value]") { + nvexec::stream_context stream_ctx{}; - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); + flags_storage_t<2> flags_storage{}; + auto flags = flags_storage.get(); - auto snd = ex::transfer_just(stream_ctx.get_scheduler(), 42, 4.2) // - | ex::let_value([=](int i, double d) { - if (is_on_gpu()) { - if (i == 42 && d == 4.2) { - flags.set(); + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | ex::let_value([flags] { + if (is_on_gpu()) { + flags.set(0); } - } - return ex::just(); - }); - stdexec::sync_wait(std::move(snd)); - REQUIRE(flags_storage.all_set_once()); -} + return ex::just(); + }) + | a_sender([flags] { + if (is_on_gpu()) { + flags.set(1); + } + }); + stdexec::sync_wait(std::move(snd)); -TEST_CASE("nvexec let_value returns values on GPU", "[cuda][stream][adaptors][let_value]") { - nvexec::stream_context stream_ctx{}; + REQUIRE(flags_storage.all_set_once()); + } - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::let_value([=]() { return ex::just(is_on_gpu()); }); - const auto [result] = stdexec::sync_wait(std::move(snd)).value(); + TEST_CASE("nvexec let_value can succeed a sender", "[cuda][stream][adaptors][let_value]") { + nvexec::stream_context stream_ctx{}; + nvexec::stream_scheduler sch = stream_ctx.get_scheduler(); + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); - REQUIRE(result == 1); -} - -TEST_CASE( - "nvexec let_value can preceed a sender without values", - "[cuda][stream][adaptors][let_value]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t<2> flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::let_value([flags] { - if (is_on_gpu()) { - flags.set(0); - } - - return ex::just(); - }) - | a_sender([flags] { - if (is_on_gpu()) { - flags.set(1); - } - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} + auto snd = ex::schedule(sch) // + | a_sender([]() noexcept {}) | ex::let_value([=] { + if (is_on_gpu()) { + flags.set(); + } -TEST_CASE("nvexec let_value can succeed a sender", "[cuda][stream][adaptors][let_value]") { - nvexec::stream_context stream_ctx{}; - nvexec::stream_scheduler sch = stream_ctx.get_scheduler(); - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); + return ex::schedule(sch); + }); + stdexec::sync_wait(std::move(snd)); - auto snd = ex::schedule(sch) // - | a_sender([]() noexcept {}) | ex::let_value([=] { - if (is_on_gpu()) { - flags.set(); - } + REQUIRE(flags_storage.all_set_once()); + } - return ex::schedule(sch); - }); - stdexec::sync_wait(std::move(snd)); + TEST_CASE("nvexec let_value can read a property", "[cuda][stream][adaptors][let_value]") { + nvexec::stream_context stream_ctx{}; + nvexec::stream_scheduler sch = stream_ctx.get_scheduler(); + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); - REQUIRE(flags_storage.all_set_once()); -} + auto snd = ex::schedule(sch) // + | ex::let_value([] { return nvexec::get_stream(); }) + | ex::then([flags](cudaStream_t stream) { + if (is_on_gpu()) { + flags.set(); + } + return stream; + }); + auto [stream] = stdexec::sync_wait(std::move(snd)).value(); + static_assert(ex::same_as); -TEST_CASE("nvexec let_value can read a property", "[cuda][stream][adaptors][let_value]") { - nvexec::stream_context stream_ctx{}; - nvexec::stream_scheduler sch = stream_ctx.get_scheduler(); - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::schedule(sch) // - | ex::let_value([] { return nvexec::get_stream(); }) - | ex::then([flags](cudaStream_t stream) { - if (is_on_gpu()) { - flags.set(); - } - return stream; - }); - auto [stream] = stdexec::sync_wait(std::move(snd)).value(); - static_assert(ex::same_as); - - REQUIRE(flags_storage.all_set_once()); + REQUIRE(flags_storage.all_set_once()); + } } diff --git a/test/nvexec/monotonic_buffer.cpp b/test/nvexec/monotonic_buffer.cpp index c29bff5ce..4cc3bd3b5 100644 --- a/test/nvexec/monotonic_buffer.cpp +++ b/test/nvexec/monotonic_buffer.cpp @@ -4,83 +4,86 @@ #include "nvexec/detail/memory.cuh" #include "tracer_resource.h" -TEST_CASE("monotonic buffer releases storage", "[cuda][stream][memory][monotonic buffer]") { - tracer_resource resource{}; +namespace { - { - nvdetail::monotonic_buffer_resource buffer{1024, &resource}; - - void* ptr_1 = buffer.allocate(128, 8); - void* ptr_2 = buffer.allocate(256, 16); - REQUIRE(ptr_1 != nullptr); - REQUIRE(ptr_2 != nullptr); - REQUIRE(ptr_1 != ptr_2); - REQUIRE(1 == resource.allocations.size()); - - buffer.deallocate(ptr_1, 128, 8); - buffer.deallocate(ptr_2, 256, 16); - REQUIRE(1 == resource.allocations.size()); - } + TEST_CASE("monotonic buffer releases storage", "[cuda][stream][memory][monotonic buffer]") { + tracer_resource resource{}; - REQUIRE(0 == resource.allocations.size()); -} + { + nvdetail::monotonic_buffer_resource buffer{1024, &resource}; -TEST_CASE( - "monotonic buffer keeps track of new allocations", - "[cuda][stream][memory][monotonic buffer]") { - tracer_resource resource{}; + void* ptr_1 = buffer.allocate(128, 8); + void* ptr_2 = buffer.allocate(256, 16); + REQUIRE(ptr_1 != nullptr); + REQUIRE(ptr_2 != nullptr); + REQUIRE(ptr_1 != ptr_2); + REQUIRE(1 == resource.allocations.size()); - { - nvdetail::monotonic_buffer_resource buffer{1024, &resource}; + buffer.deallocate(ptr_1, 128, 8); + buffer.deallocate(ptr_2, 256, 16); + REQUIRE(1 == resource.allocations.size()); + } - void* ptr_1 = buffer.allocate(128, 8); - void* ptr_2 = buffer.allocate(256, 16); - void* ptr_3 = buffer.allocate(1024, 1); - void* ptr_4 = buffer.allocate(512, 2); - REQUIRE(ptr_1 != nullptr); - REQUIRE(ptr_2 != nullptr); - REQUIRE(ptr_3 != nullptr); - REQUIRE(ptr_4 != nullptr); - REQUIRE(ptr_1 != ptr_2); - REQUIRE(ptr_2 != ptr_3); - REQUIRE(ptr_3 != ptr_4); - REQUIRE(2 == resource.allocations.size()); - - buffer.deallocate(ptr_1, 128, 8); - buffer.deallocate(ptr_2, 256, 16); - buffer.deallocate(ptr_3, 1024, 1); - buffer.deallocate(ptr_4, 512, 2); - REQUIRE(2 == resource.allocations.size()); + REQUIRE(0 == resource.allocations.size()); } - REQUIRE(0 == resource.allocations.size()); -} - -TEST_CASE( - "monotonic buffer provides required allocations", - "[cuda][stream][memory][monotonic buffer]") { - tracer_resource resource{}; - nvdetail::monotonic_buffer_resource buffer{1024, &resource}; + TEST_CASE( + "monotonic buffer keeps track of new allocations", + "[cuda][stream][memory][monotonic buffer]") { + tracer_resource resource{}; + + { + nvdetail::monotonic_buffer_resource buffer{1024, &resource}; + + void* ptr_1 = buffer.allocate(128, 8); + void* ptr_2 = buffer.allocate(256, 16); + void* ptr_3 = buffer.allocate(1024, 1); + void* ptr_4 = buffer.allocate(512, 2); + REQUIRE(ptr_1 != nullptr); + REQUIRE(ptr_2 != nullptr); + REQUIRE(ptr_3 != nullptr); + REQUIRE(ptr_4 != nullptr); + REQUIRE(ptr_1 != ptr_2); + REQUIRE(ptr_2 != ptr_3); + REQUIRE(ptr_3 != ptr_4); + REQUIRE(2 == resource.allocations.size()); + + buffer.deallocate(ptr_1, 128, 8); + buffer.deallocate(ptr_2, 256, 16); + buffer.deallocate(ptr_3, 1024, 1); + buffer.deallocate(ptr_4, 512, 2); + REQUIRE(2 == resource.allocations.size()); + } + + REQUIRE(0 == resource.allocations.size()); + } - char* ptr_1 = reinterpret_cast(buffer.allocate(32, 8)); - char* ptr_2 = reinterpret_cast(buffer.allocate(32, 8)); + TEST_CASE( + "monotonic buffer provides required allocations", + "[cuda][stream][memory][monotonic buffer]") { + tracer_resource resource{}; + nvdetail::monotonic_buffer_resource buffer{1024, &resource}; - size_t distance = std::distance(ptr_1, ptr_2); - REQUIRE(distance == 32); + char* ptr_1 = reinterpret_cast(buffer.allocate(32, 8)); + char* ptr_2 = reinterpret_cast(buffer.allocate(32, 8)); - buffer.deallocate(ptr_1, 32, 8); - buffer.deallocate(ptr_2, 32, 16); -} + size_t distance = std::distance(ptr_1, ptr_2); + REQUIRE(distance == 32); -TEST_CASE( - "monotonic buffer provides required alignment", - "[cuda][stream][memory][monotonic buffer]") { - tracer_resource resource{}; - nvdetail::monotonic_buffer_resource buffer{2048, &resource}; + buffer.deallocate(ptr_1, 32, 8); + buffer.deallocate(ptr_2, 32, 16); + } - for (int alignment = 1; alignment < 512; alignment *= 2) { - void* ptr = buffer.allocate(32, alignment); - REQUIRE(reinterpret_cast(ptr) % alignment == 0); - buffer.deallocate(ptr, 32, alignment); + TEST_CASE( + "monotonic buffer provides required alignment", + "[cuda][stream][memory][monotonic buffer]") { + tracer_resource resource{}; + nvdetail::monotonic_buffer_resource buffer{2048, &resource}; + + for (int alignment = 1; alignment < 512; alignment *= 2) { + void* ptr = buffer.allocate(32, alignment); + REQUIRE(reinterpret_cast(ptr) % alignment == 0); + buffer.deallocate(ptr, 32, alignment); + } } } diff --git a/test/nvexec/reduce.cpp b/test/nvexec/reduce.cpp index be2e8a890..ce42721b6 100644 --- a/test/nvexec/reduce.cpp +++ b/test/nvexec/reduce.cpp @@ -12,84 +12,89 @@ namespace ex = stdexec; -TEST_CASE("nvexec reduce returns a sender with single input", "[cuda][stream][adaptors][reduce]") { - constexpr int N = 2048; - int input[N] = {}; - std::fill_n(input, N, 1); +namespace { - nvexec::stream_context stream{}; - auto snd = ex::transfer_just(stream.get_scheduler(), std::span{input}) | nvexec::reduce(0); + TEST_CASE( + "nvexec reduce returns a sender with single input", + "[cuda][stream][adaptors][reduce]") { + constexpr int N = 2048; + int input[N] = {}; + std::fill_n(input, N, 1); - STATIC_REQUIRE(ex::sender_of); + nvexec::stream_context stream{}; + auto snd = ex::transfer_just(stream.get_scheduler(), std::span{input}) | nvexec::reduce(0); - (void) snd; -} + STATIC_REQUIRE(ex::sender_of); -TEST_CASE("nvexec reduce returns a sender with two inputs", "[cuda][stream][adaptors][reduce]") { - constexpr int N = 2048; - int input[N] = {}; - std::fill_n(input, N, 1); + (void) snd; + } - nvexec::stream_context stream{}; - auto snd = ex::transfer_just(stream.get_scheduler(), std::span{input}) - | nvexec::reduce(0, cub::Difference{}); + TEST_CASE("nvexec reduce returns a sender with two inputs", "[cuda][stream][adaptors][reduce]") { + constexpr int N = 2048; + int input[N] = {}; + std::fill_n(input, N, 1); - STATIC_REQUIRE(ex::sender_of); + nvexec::stream_context stream{}; + auto snd = ex::transfer_just(stream.get_scheduler(), std::span{input}) + | nvexec::reduce(0, cub::Difference{}); - (void) snd; -} + STATIC_REQUIRE(ex::sender_of); -TEST_CASE("nvexec reduce uses sum as default", "[cuda][stream][adaptors][reduce]") { - constexpr int N = 2048; - constexpr int init = 42; + (void) snd; + } - thrust::device_vector input(N, 1); - int* first = thrust::raw_pointer_cast(input.data()); - int* last = thrust::raw_pointer_cast(input.data()) + input.size(); + TEST_CASE("nvexec reduce uses sum as default", "[cuda][stream][adaptors][reduce]") { + constexpr int N = 2048; + constexpr int init = 42; - nvexec::stream_context stream{}; - auto snd = ex::transfer_just(stream.get_scheduler(), std::span{first, last}) - | nvexec::reduce(init); + thrust::device_vector input(N, 1); + int* first = thrust::raw_pointer_cast(input.data()); + int* last = thrust::raw_pointer_cast(input.data()) + input.size(); - auto [result] = ex::sync_wait(std::move(snd)).value(); + nvexec::stream_context stream{}; + auto snd = ex::transfer_just(stream.get_scheduler(), std::span{first, last}) + | nvexec::reduce(init); - REQUIRE(result == N + init); -} + auto [result] = ex::sync_wait(std::move(snd)).value(); -TEST_CASE("nvexec reduce uses the passed function", "[cuda][stream][adaptors][reduce]") { - constexpr int N = 2048; - constexpr int init = 42; + REQUIRE(result == N + init); + } - thrust::device_vector input(N, 1); - int* first = thrust::raw_pointer_cast(input.data()); - int* last = thrust::raw_pointer_cast(input.data()) + input.size(); + TEST_CASE("nvexec reduce uses the passed function", "[cuda][stream][adaptors][reduce]") { + constexpr int N = 2048; + constexpr int init = 42; - nvexec::stream_context stream{}; - auto snd = ex::transfer_just(stream.get_scheduler(), std::span{first, last}) - | nvexec::reduce(init, cub::Min{}); + thrust::device_vector input(N, 1); + int* first = thrust::raw_pointer_cast(input.data()); + int* last = thrust::raw_pointer_cast(input.data()) + input.size(); - auto [result] = ex::sync_wait(std::move(snd)).value(); + nvexec::stream_context stream{}; + auto snd = ex::transfer_just(stream.get_scheduler(), std::span{first, last}) + | nvexec::reduce(init, cub::Min{}); - REQUIRE(result == 1); -} + auto [result] = ex::sync_wait(std::move(snd)).value(); + + REQUIRE(result == 1); + } -TEST_CASE("nvexec reduce executes on GPU", "[cuda][stream][adaptors][reduce]") { - constexpr int N = 2048; - constexpr int init = 42; + TEST_CASE("nvexec reduce executes on GPU", "[cuda][stream][adaptors][reduce]") { + constexpr int N = 2048; + constexpr int init = 42; - thrust::device_vector input(N, 1); - int* first = thrust::raw_pointer_cast(input.data()); - int* last = thrust::raw_pointer_cast(input.data()) + input.size(); + thrust::device_vector input(N, 1); + int* first = thrust::raw_pointer_cast(input.data()); + int* last = thrust::raw_pointer_cast(input.data()) + input.size(); - auto is_on_gpu = [](const int left, const int right) { - return nvexec::is_on_gpu() ? left + right : 0; - }; + auto is_on_gpu = [](const int left, const int right) { + return nvexec::is_on_gpu() ? left + right : 0; + }; - nvexec::stream_context stream{}; - auto snd = ex::transfer_just(stream.get_scheduler(), std::span{first, last}) - | nvexec::reduce(init, is_on_gpu); + nvexec::stream_context stream{}; + auto snd = ex::transfer_just(stream.get_scheduler(), std::span{first, last}) + | nvexec::reduce(init, is_on_gpu); - auto [result] = ex::sync_wait(std::move(snd)).value(); + auto [result] = ex::sync_wait(std::move(snd)).value(); - REQUIRE(result == N + init); + REQUIRE(result == N + init); + } } diff --git a/test/nvexec/split.cpp b/test/nvexec/split.cpp index 558942124..0ca20fcfe 100644 --- a/test/nvexec/split.cpp +++ b/test/nvexec/split.cpp @@ -8,85 +8,88 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE("nvexec split returns a sender", "[cuda][stream][adaptors][split]") { - nvexec::stream_context stream_ctx{}; - auto snd = ex::split(ex::schedule(stream_ctx.get_scheduler())); - STATIC_REQUIRE(ex::sender); - (void) snd; -} - -TEST_CASE("nvexec split works", "[cuda][stream][adaptors][split]") { - nvexec::stream_context stream_ctx{}; - - auto fork = ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([=] { return is_on_gpu(); }) | ex::split(); - - auto b1 = fork | ex::then([](bool on_gpu) { return on_gpu * 24; }); - auto b2 = fork | ex::then([](bool on_gpu) { return on_gpu * 42; }); +namespace { - auto [v1] = stdexec::sync_wait(std::move(b1)).value(); - auto [v2] = stdexec::sync_wait(std::move(b2)).value(); - - REQUIRE(v1 == 24); - REQUIRE(v2 == 42); -} + TEST_CASE("nvexec split returns a sender", "[cuda][stream][adaptors][split]") { + nvexec::stream_context stream_ctx{}; + auto snd = ex::split(ex::schedule(stream_ctx.get_scheduler())); + STATIC_REQUIRE(ex::sender); + (void) snd; + } -TEST_CASE("nvexec split can preceed a sender without values", "[cuda][stream][adaptors][split]") { - nvexec::stream_context stream_ctx{}; + TEST_CASE("nvexec split works", "[cuda][stream][adaptors][split]") { + nvexec::stream_context stream_ctx{}; - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); + auto fork = ex::schedule(stream_ctx.get_scheduler()) // + | ex::then([=] { return is_on_gpu(); }) | ex::split(); - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::split() // - | a_sender([=]() noexcept { - if (is_on_gpu()) { - flags.set(); - } - }); + auto b1 = fork | ex::then([](bool on_gpu) { return on_gpu * 24; }); + auto b2 = fork | ex::then([](bool on_gpu) { return on_gpu * 42; }); - stdexec::sync_wait(std::move(snd)); + auto [v1] = stdexec::sync_wait(std::move(b1)).value(); + auto [v2] = stdexec::sync_wait(std::move(b2)).value(); - REQUIRE(flags_storage.all_set_once()); -} + REQUIRE(v1 == 24); + REQUIRE(v2 == 42); + } -TEST_CASE("nvexec split can succeed a sender", "[cuda][stream][adaptors][split]") { - SECTION("without values") { + TEST_CASE("nvexec split can preceed a sender without values", "[cuda][stream][adaptors][split]") { nvexec::stream_context stream_ctx{}; - flags_storage_t<2> flags_storage{}; + + flags_storage_t flags_storage{}; auto flags = flags_storage.get(); auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | a_sender([flags] { + | ex::split() // + | a_sender([=]() noexcept { if (is_on_gpu()) { - flags.set(1); - } - }) - | ex::split() // - | ex::then([flags] { - if (is_on_gpu()) { - flags.set(0); + flags.set(); } }); + stdexec::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } - SECTION("with values") { - nvexec::stream_context stream_ctx{}; - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | a_sender([]() -> bool { return is_on_gpu(); }) | ex::split() - | ex::then([flags](bool a_sender_was_on_gpu) { - if (a_sender_was_on_gpu && is_on_gpu()) { - flags.set(); - } - }); - stdexec::sync_wait(std::move(snd)).value(); - - REQUIRE(flags_storage.all_set_once()); + TEST_CASE("nvexec split can succeed a sender", "[cuda][stream][adaptors][split]") { + SECTION("without values") { + nvexec::stream_context stream_ctx{}; + flags_storage_t<2> flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | a_sender([flags] { + if (is_on_gpu()) { + flags.set(1); + } + }) + | ex::split() // + | ex::then([flags] { + if (is_on_gpu()) { + flags.set(0); + } + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + SECTION("with values") { + nvexec::stream_context stream_ctx{}; + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | a_sender([]() -> bool { return is_on_gpu(); }) | ex::split() + | ex::then([flags](bool a_sender_was_on_gpu) { + if (a_sender_was_on_gpu && is_on_gpu()) { + flags.set(); + } + }); + stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(flags_storage.all_set_once()); + } } } diff --git a/test/nvexec/start_detached.cpp b/test/nvexec/start_detached.cpp index 27b70ebfa..1d0443da6 100644 --- a/test/nvexec/start_detached.cpp +++ b/test/nvexec/start_detached.cpp @@ -12,47 +12,50 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE("nvexec start_detached doesn't block", "[cuda][stream][consumers][start_detached]") { - if (const char* env = std::getenv("CUDA_LAUNCH_BLOCKING")) { - if (std::strlen(env) >= 1 && env[0] == '1') { - return; // This test is unable to run when the launch is blocking - } - } - - nvexec::stream_context stream_ctx{}; +namespace { - int* host_flag{}; - int* device_flag{}; - THROW_ON_CUDA_ERROR(cudaMallocHost(&host_flag, sizeof(int))); - THROW_ON_CUDA_ERROR(cudaMallocHost(&device_flag, sizeof(int))); - *host_flag = *device_flag = 0; - - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([=] { - if (is_on_gpu()) { - cuda::atomic_ref host_flag_ref(*host_flag); - cuda::atomic_ref device_flag_ref(*device_flag); + TEST_CASE("nvexec start_detached doesn't block", "[cuda][stream][consumers][start_detached]") { + if (const char* env = std::getenv("CUDA_LAUNCH_BLOCKING")) { + if (std::strlen(env) >= 1 && env[0] == '1') { + return; // This test is unable to run when the launch is blocking + } + } - int iteration{1}; - while (host_flag_ref.load(cuda::memory_order_relaxed) == 0) { - iteration++; + nvexec::stream_context stream_ctx{}; + + int* host_flag{}; + int* device_flag{}; + THROW_ON_CUDA_ERROR(cudaMallocHost(&host_flag, sizeof(int))); + THROW_ON_CUDA_ERROR(cudaMallocHost(&device_flag, sizeof(int))); + *host_flag = *device_flag = 0; + + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | ex::then([=] { + if (is_on_gpu()) { + cuda::atomic_ref host_flag_ref(*host_flag); + cuda::atomic_ref device_flag_ref(*device_flag); + + int iteration{1}; + while (host_flag_ref.load(cuda::memory_order_relaxed) == 0) { + iteration++; + } + device_flag_ref.store(iteration, cuda::memory_order_relaxed); } - device_flag_ref.store(iteration, cuda::memory_order_relaxed); - } - }); + }); - // then won't complete until we set flag, so if the `start_detached` is blocking, we'll deadlock - ex::start_detached(std::move(snd)); + // then won't complete until we set flag, so if the `start_detached` is blocking, we'll deadlock + ex::start_detached(std::move(snd)); - cuda::atomic_ref host_flag_ref(*host_flag); - cuda::atomic_ref device_flag_ref(*device_flag); - host_flag_ref.store(1, cuda::memory_order_relaxed); + cuda::atomic_ref host_flag_ref(*host_flag); + cuda::atomic_ref device_flag_ref(*device_flag); + host_flag_ref.store(1, cuda::memory_order_relaxed); - while (device_flag_ref.load(cuda::memory_order_relaxed) == 0) - ; + while (device_flag_ref.load(cuda::memory_order_relaxed) == 0) + ; - REQUIRE(device_flag_ref.load(cuda::memory_order_relaxed) > 0); + REQUIRE(device_flag_ref.load(cuda::memory_order_relaxed) > 0); - THROW_ON_CUDA_ERROR(cudaFreeHost(host_flag)); - THROW_ON_CUDA_ERROR(cudaFreeHost(device_flag)); + THROW_ON_CUDA_ERROR(cudaFreeHost(host_flag)); + THROW_ON_CUDA_ERROR(cudaFreeHost(device_flag)); + } } diff --git a/test/nvexec/synchronized_pool.cpp b/test/nvexec/synchronized_pool.cpp index 665d310f3..4993b3525 100644 --- a/test/nvexec/synchronized_pool.cpp +++ b/test/nvexec/synchronized_pool.cpp @@ -4,34 +4,14 @@ #include "nvexec/detail/memory.cuh" #include "tracer_resource.h" -TEST_CASE("synchronized pool releases storage", "[cuda][stream][memory][synchronized pool]") { - tracer_resource resource{}; +namespace { - { - nvdetail::synchronized_pool_resource pool{&resource}; - - void* ptr_1 = pool.allocate(128, 8); - void* ptr_2 = pool.allocate(256, 16); - REQUIRE(ptr_1 != nullptr); - REQUIRE(ptr_2 != nullptr); - REQUIRE(ptr_1 != ptr_2); - REQUIRE(2 == resource.allocations.size()); - - pool.deallocate(ptr_2, 256, 16); - pool.deallocate(ptr_1, 128, 8); - REQUIRE(2 == resource.allocations.size()); - } - - REQUIRE(0 == resource.allocations.size()); -} + TEST_CASE("synchronized pool releases storage", "[cuda][stream][memory][synchronized pool]") { + tracer_resource resource{}; -TEST_CASE("synchronized pool caches allocations", "[cuda][stream][memory][synchronized pool]") { - tracer_resource resource{}; + { + nvdetail::synchronized_pool_resource pool{&resource}; - { - nvdetail::synchronized_pool_resource pool{&resource}; - - for (int i = 0; i < 10; i++) { void* ptr_1 = pool.allocate(128, 8); void* ptr_2 = pool.allocate(256, 16); REQUIRE(ptr_1 != nullptr); @@ -44,44 +24,67 @@ TEST_CASE("synchronized pool caches allocations", "[cuda][stream][memory][synchr REQUIRE(2 == resource.allocations.size()); } - REQUIRE(2 == resource.allocations.size()); + REQUIRE(0 == resource.allocations.size()); } - REQUIRE(0 == resource.allocations.size()); -} + TEST_CASE("synchronized pool caches allocations", "[cuda][stream][memory][synchronized pool]") { + tracer_resource resource{}; -TEST_CASE( - "synchronized pool doesn't touch allocated memory", - "[cuda][stream][memory][synchronized pool]") { - tracer_resource resource{}; - nvdetail::synchronized_pool_resource pool{&resource}; + { + nvdetail::synchronized_pool_resource pool{&resource}; - for (int n = 32; n < 512; n *= 2) { - int bytes = n * sizeof(int); - int alignment = alignof(int); + for (int i = 0; i < 10; i++) { + void* ptr_1 = pool.allocate(128, 8); + void* ptr_2 = pool.allocate(256, 16); + REQUIRE(ptr_1 != nullptr); + REQUIRE(ptr_2 != nullptr); + REQUIRE(ptr_1 != ptr_2); + REQUIRE(2 == resource.allocations.size()); - int* ptr = reinterpret_cast(pool.allocate(bytes, alignment)); - std::iota(ptr, ptr + n, n); - pool.deallocate(ptr, 128, 8); + pool.deallocate(ptr_2, 256, 16); + pool.deallocate(ptr_1, 128, 8); + REQUIRE(2 == resource.allocations.size()); + } - ptr = reinterpret_cast(pool.allocate(bytes, alignment)); - for (int i = 0; i < n; i++) { - REQUIRE(ptr[i] == i + n); + REQUIRE(2 == resource.allocations.size()); } - pool.deallocate(ptr, 128, 8); + + REQUIRE(0 == resource.allocations.size()); } -} -TEST_CASE( - "synchronized pool provides required alignment", - "[cuda][stream][memory][synchronized pool]") { - tracer_resource resource{}; - nvdetail::synchronized_pool_resource pool{&resource}; - - for (int alignment = 1; alignment < 512; alignment *= 2) { - void* ptr = pool.allocate(32, alignment); - INFO("Alignment: " << alignment); - REQUIRE(reinterpret_cast(ptr) % alignment == 0); - pool.deallocate(ptr, 32, alignment); + TEST_CASE( + "synchronized pool doesn't touch allocated memory", + "[cuda][stream][memory][synchronized pool]") { + tracer_resource resource{}; + nvdetail::synchronized_pool_resource pool{&resource}; + + for (int n = 32; n < 512; n *= 2) { + int bytes = n * sizeof(int); + int alignment = alignof(int); + + int* ptr = reinterpret_cast(pool.allocate(bytes, alignment)); + std::iota(ptr, ptr + n, n); + pool.deallocate(ptr, 128, 8); + + ptr = reinterpret_cast(pool.allocate(bytes, alignment)); + for (int i = 0; i < n; i++) { + REQUIRE(ptr[i] == i + n); + } + pool.deallocate(ptr, 128, 8); + } } -} \ No newline at end of file + + TEST_CASE( + "synchronized pool provides required alignment", + "[cuda][stream][memory][synchronized pool]") { + tracer_resource resource{}; + nvdetail::synchronized_pool_resource pool{&resource}; + + for (int alignment = 1; alignment < 512; alignment *= 2) { + void* ptr = pool.allocate(32, alignment); + INFO("Alignment: " << alignment); + REQUIRE(reinterpret_cast(ptr) % alignment == 0); + pool.deallocate(ptr, 32, alignment); + } + } +} diff --git a/test/nvexec/then.cpp b/test/nvexec/then.cpp index 94080ee47..160665f24 100644 --- a/test/nvexec/then.cpp +++ b/test/nvexec/then.cpp @@ -9,121 +9,63 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE("nvexec then returns a sender", "[cuda][stream][adaptors][then]") { - nvexec::stream_context stream_ctx{}; - auto snd = ex::then(ex::schedule(stream_ctx.get_scheduler()), [] {}); - STATIC_REQUIRE(ex::sender); - (void) snd; -} - -TEST_CASE("nvexec then executes on GPU", "[cuda][stream][adaptors][then]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([=] { - if (is_on_gpu()) { - flags.set(); - } - }); - stdexec::sync_wait(std::move(snd)); +namespace { - REQUIRE(flags_storage.all_set_once()); -} + TEST_CASE("nvexec then returns a sender", "[cuda][stream][adaptors][then]") { + nvexec::stream_context stream_ctx{}; + auto snd = ex::then(ex::schedule(stream_ctx.get_scheduler()), [] {}); + STATIC_REQUIRE(ex::sender); + (void) snd; + } -TEST_CASE("nvexec then accepts values on GPU", "[cuda][stream][adaptors][then]") { - nvexec::stream_context stream_ctx{}; + TEST_CASE("nvexec then executes on GPU", "[cuda][stream][adaptors][then]") { + nvexec::stream_context stream_ctx{}; - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); - auto snd = ex::transfer_just(stream_ctx.get_scheduler(), 42) // - | ex::then([=](int val) { - if (is_on_gpu()) { - if (val == 42) { + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | ex::then([=] { + if (is_on_gpu()) { flags.set(); } - } - }); - stdexec::sync_wait(std::move(snd)); + }); + stdexec::sync_wait(std::move(snd)); - REQUIRE(flags_storage.all_set_once()); -} + REQUIRE(flags_storage.all_set_once()); + } -TEST_CASE("nvexec then accepts multiple values on GPU", "[cuda][stream][adaptors][then]") { - nvexec::stream_context stream_ctx{}; + TEST_CASE("nvexec then accepts values on GPU", "[cuda][stream][adaptors][then]") { + nvexec::stream_context stream_ctx{}; - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); - auto snd = ex::transfer_just(stream_ctx.get_scheduler(), 42, 4.2) // - | ex::then([=](int i, double d) { - if (is_on_gpu()) { - if (i == 42 && d == 4.2) { - flags.set(); + auto snd = ex::transfer_just(stream_ctx.get_scheduler(), 42) // + | ex::then([=](int val) { + if (is_on_gpu()) { + if (val == 42) { + flags.set(); + } } - } - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} - -TEST_CASE("nvexec then returns values on GPU", "[cuda][stream][adaptors][then]") { - nvexec::stream_context stream_ctx{}; - - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([=]() -> int { - if (is_on_gpu()) { - return 42; - } - - return 0; - }); - const auto [result] = stdexec::sync_wait(std::move(snd)).value(); - - REQUIRE(result == 42); -} + }); + stdexec::sync_wait(std::move(snd)); -TEST_CASE("nvexec then can preceed a sender without values", "[cuda][stream][adaptors][then]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t<2> flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([flags] { - if (is_on_gpu()) { - flags.set(0); - } - }) - | a_sender([flags] { - if (is_on_gpu()) { - flags.set(1); - } - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} + REQUIRE(flags_storage.all_set_once()); + } -TEST_CASE("nvexec then can succeed a sender", "[cuda][stream][adaptors][then]") { - SECTION("without values") { + TEST_CASE("nvexec then accepts multiple values on GPU", "[cuda][stream][adaptors][then]") { nvexec::stream_context stream_ctx{}; - flags_storage_t<2> flags_storage{}; + + flags_storage_t flags_storage{}; auto flags = flags_storage.get(); - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | a_sender([flags] { + auto snd = ex::transfer_just(stream_ctx.get_scheduler(), 42, 4.2) // + | ex::then([=](int i, double d) { if (is_on_gpu()) { - flags.set(1); - } - }) - | ex::then([flags] { - if (is_on_gpu()) { - flags.set(0); + if (i == 42 && d == 4.2) { + flags.set(); + } } }); stdexec::sync_wait(std::move(snd)); @@ -131,35 +73,37 @@ TEST_CASE("nvexec then can succeed a sender", "[cuda][stream][adaptors][then]") REQUIRE(flags_storage.all_set_once()); } - SECTION("with values") { + TEST_CASE("nvexec then returns values on GPU", "[cuda][stream][adaptors][then]") { nvexec::stream_context stream_ctx{}; - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | a_sender([]() -> bool { return is_on_gpu(); }) - | ex::then([flags](bool a_sender_was_on_gpu) { - if (a_sender_was_on_gpu && is_on_gpu()) { - flags.set(); + | ex::then([=]() -> int { + if (is_on_gpu()) { + return 42; } + + return 0; }); - stdexec::sync_wait(std::move(snd)).value(); + const auto [result] = stdexec::sync_wait(std::move(snd)).value(); - REQUIRE(flags_storage.all_set_once()); + REQUIRE(result == 42); } -} -TEST_CASE("nvexec then can succeed a receiverless sender", "[cuda][stream][adaptors][then]") { - SECTION("without values") { + TEST_CASE("nvexec then can preceed a sender without values", "[cuda][stream][adaptors][then]") { nvexec::stream_context stream_ctx{}; - flags_storage_t flags_storage{}; + + flags_storage_t<2> flags_storage{}; auto flags = flags_storage.get(); auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | a_sender() // | ex::then([flags] { if (is_on_gpu()) { - flags.set(); + flags.set(0); + } + }) + | a_sender([flags] { + if (is_on_gpu()) { + flags.set(1); } }); stdexec::sync_wait(std::move(snd)); @@ -167,136 +111,197 @@ TEST_CASE("nvexec then can succeed a receiverless sender", "[cuda][stream][adapt REQUIRE(flags_storage.all_set_once()); } - SECTION("with values") { + TEST_CASE("nvexec then can succeed a sender", "[cuda][stream][adaptors][then]") { + SECTION("without values") { + nvexec::stream_context stream_ctx{}; + flags_storage_t<2> flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | a_sender([flags] { + if (is_on_gpu()) { + flags.set(1); + } + }) + | ex::then([flags] { + if (is_on_gpu()) { + flags.set(0); + } + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + SECTION("with values") { + nvexec::stream_context stream_ctx{}; + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | a_sender([]() -> bool { return is_on_gpu(); }) + | ex::then([flags](bool a_sender_was_on_gpu) { + if (a_sender_was_on_gpu && is_on_gpu()) { + flags.set(); + } + }); + stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(flags_storage.all_set_once()); + } + } + + TEST_CASE("nvexec then can succeed a receiverless sender", "[cuda][stream][adaptors][then]") { + SECTION("without values") { + nvexec::stream_context stream_ctx{}; + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | a_sender() // + | ex::then([flags] { + if (is_on_gpu()) { + flags.set(); + } + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + SECTION("with values") { + nvexec::stream_context stream_ctx{}; + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::schedule(stream_ctx.get_scheduler()) // + | ex::then([]() -> bool { return is_on_gpu(); }) | a_sender() + | ex::then([flags](bool a_sender_was_on_gpu) { + if (a_sender_was_on_gpu && is_on_gpu()) { + flags.set(); + } + }); + stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(flags_storage.all_set_once()); + } + } + + TEST_CASE( + "nvexec then can return values of non-trivial types", + "[cuda][stream][adaptors][then]") { nvexec::stream_context stream_ctx{}; flags_storage_t flags_storage{}; auto flags = flags_storage.get(); auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([]() -> bool { return is_on_gpu(); }) | a_sender() - | ex::then([flags](bool a_sender_was_on_gpu) { - if (a_sender_was_on_gpu && is_on_gpu()) { + | ex::then([]() -> move_only_t { return move_only_t{42}; }) + | ex::then([flags](move_only_t &&val) { + if (val.contains(42)) { flags.set(); } }); - stdexec::sync_wait(std::move(snd)).value(); + stdexec::sync_wait(std::move(snd)); REQUIRE(flags_storage.all_set_once()); } -} - -TEST_CASE("nvexec then can return values of non-trivial types", "[cuda][stream][adaptors][then]") { - nvexec::stream_context stream_ctx{}; - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([]() -> move_only_t { return move_only_t{42}; }) - | ex::then([flags](move_only_t &&val) { - if (val.contains(42)) { - flags.set(); - } - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} - -class tracer_storage_t { - int h_counter_storage{}; - int *h_counter_{}; - int *d_counter_{}; - - public: - tracer_storage_t() - : h_counter_{&h_counter_storage} { - cudaMalloc(&d_counter_, sizeof(int)); - cudaMemset(d_counter_, 0, sizeof(int)); - } - - ~tracer_storage_t() { - cudaFree(d_counter_); - } - class handle_t { + class tracer_storage_t { + int h_counter_storage{}; int *h_counter_{}; int *d_counter_{}; - handle_t(int *h_counter, int *d_counter) - : h_counter_{h_counter} - , d_counter_{d_counter} { + public: + tracer_storage_t() + : h_counter_{&h_counter_storage} { + cudaMalloc(&d_counter_, sizeof(int)); + cudaMemset(d_counter_, 0, sizeof(int)); } - __host__ __device__ void diff(int val) { - cuda::std::atomic_ref ref{*(is_on_gpu() ? d_counter_ : h_counter_)}; - ref.fetch_add(val, cuda::std::memory_order_relaxed); + ~tracer_storage_t() { + cudaFree(d_counter_); } - public: - __host__ __device__ void more() { - diff(+1); - } + class handle_t { + int *h_counter_{}; + int *d_counter_{}; - __host__ __device__ void fewer() { - diff(-1); - } + handle_t(int *h_counter, int *d_counter) + : h_counter_{h_counter} + , d_counter_{d_counter} { + } - __host__ int alive() { - int d_counter{}; - cudaMemcpy(&d_counter, d_counter_, sizeof(int), cudaMemcpyDeviceToHost); - return *h_counter_ + d_counter; - } + __host__ __device__ void diff(int val) { + cuda::std::atomic_ref ref{*(is_on_gpu() ? d_counter_ : h_counter_)}; + ref.fetch_add(val, cuda::std::memory_order_relaxed); + } - friend tracer_storage_t; - }; + public: + __host__ __device__ void more() { + diff(+1); + } - handle_t get() { - return handle_t{h_counter_, d_counter_}; - } -}; + __host__ __device__ void fewer() { + diff(-1); + } -class tracer_t { - tracer_storage_t::handle_t handle_; + __host__ int alive() { + int d_counter{}; + cudaMemcpy(&d_counter, d_counter_, sizeof(int), cudaMemcpyDeviceToHost); + return *h_counter_ + d_counter; + } - void print(const char *msg) { - if (is_on_gpu()) { - printf("gpu: %s\n", msg); - } else { - printf("cpu: %s\n", msg); + friend tracer_storage_t; + }; + + handle_t get() { + return handle_t{h_counter_, d_counter_}; } - } + }; - public: - tracer_t() = delete; - tracer_t(const tracer_t &other) = delete; + class tracer_t { + tracer_storage_t::handle_t handle_; - __host__ __device__ tracer_t(tracer_storage_t::handle_t handle) - : handle_(handle) { - handle_.more(); - } + void print(const char *msg) { + if (is_on_gpu()) { + printf("gpu: %s\n", msg); + } else { + printf("cpu: %s\n", msg); + } + } - __host__ __device__ tracer_t(tracer_t &&other) - : handle_(other.handle_) { - handle_.more(); - } + public: + tracer_t() = delete; + tracer_t(const tracer_t &other) = delete; - __host__ __device__ ~tracer_t() { - handle_.fewer(); - } -}; + __host__ __device__ tracer_t(tracer_storage_t::handle_t handle) + : handle_(handle) { + handle_.more(); + } -TEST_CASE("nvexec then destructs temporary storage", "[cuda][stream][adaptors][then]") { - nvexec::stream_context stream_ctx{}; + __host__ __device__ tracer_t(tracer_t &&other) + : handle_(other.handle_) { + handle_.more(); + } - tracer_storage_t storage; - tracer_storage_t::handle_t handle = storage.get(); + __host__ __device__ ~tracer_t() { + handle_.fewer(); + } + }; - { - auto snd = ex::schedule(stream_ctx.get_scheduler()) - | ex::then([handle]() -> tracer_t { return tracer_t{handle}; }) - | ex::then([](tracer_t &&tracer) {}); - stdexec::sync_wait(std::move(snd)); - } + TEST_CASE("nvexec then destructs temporary storage", "[cuda][stream][adaptors][then]") { + nvexec::stream_context stream_ctx{}; + + tracer_storage_t storage; + tracer_storage_t::handle_t handle = storage.get(); - REQUIRE(handle.alive() == 0); + { + auto snd = ex::schedule(stream_ctx.get_scheduler()) + | ex::then([handle]() -> tracer_t { return tracer_t{handle}; }) + | ex::then([](tracer_t &&tracer) {}); + stdexec::sync_wait(std::move(snd)); + } + + REQUIRE(handle.alive() == 0); + } } diff --git a/test/nvexec/tracer_resource.h b/test/nvexec/tracer_resource.h index 74b6d7036..1188730a3 100644 --- a/test/nvexec/tracer_resource.h +++ b/test/nvexec/tracer_resource.h @@ -8,41 +8,44 @@ namespace nvdetail = nvexec::STDEXEC_STREAM_DETAIL_NS; -struct tracer_resource : public std::pmr::memory_resource { - struct allocation_info_t { - void* ptr; - size_t bytes; - size_t alignment; - - bool operator==(const allocation_info_t& other) const noexcept { - return ptr == other.ptr && bytes == other.bytes && alignment == other.alignment; +namespace { + + struct tracer_resource : public std::pmr::memory_resource { + struct allocation_info_t { + void* ptr; + size_t bytes; + size_t alignment; + + bool operator==(const allocation_info_t& other) const noexcept { + return ptr == other.ptr && bytes == other.bytes && alignment == other.alignment; + } + }; + + std::vector allocations; + + void* do_allocate(size_t bytes, size_t alignment) override { + INFO("Allocate: " << bytes << " bytes, " << alignment << " alignment"); + void* ptr = ::operator new[](bytes, std::align_val_t(alignment)); + allocations.push_back(allocation_info_t{ptr, bytes, alignment}); + return ptr; } - }; - - std::vector allocations; - - void* do_allocate(size_t bytes, size_t alignment) override { - INFO("Allocate: " << bytes << " bytes, " << alignment << " alignment"); - void* ptr = ::operator new[](bytes, std::align_val_t(alignment)); - allocations.push_back(allocation_info_t{ptr, bytes, alignment}); - return ptr; - } - void do_deallocate(void* ptr, size_t bytes, size_t alignment) override { - INFO("Deallocate: " << bytes << " bytes, " << alignment << " alignment"); + void do_deallocate(void* ptr, size_t bytes, size_t alignment) override { + INFO("Deallocate: " << bytes << " bytes, " << alignment << " alignment"); - auto it = std::find( - allocations.begin(), allocations.end(), allocation_info_t{ptr, bytes, alignment}); + auto it = std::find( + allocations.begin(), allocations.end(), allocation_info_t{ptr, bytes, alignment}); - REQUIRE(it != allocations.end()); + REQUIRE(it != allocations.end()); - if (it != allocations.end()) { - allocations.erase(it); - ::operator delete[](ptr, std::align_val_t(alignment)); + if (it != allocations.end()) { + allocations.erase(it); + ::operator delete[](ptr, std::align_val_t(alignment)); + } } - } - bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { - return this == &other; - } -}; \ No newline at end of file + bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override { + return this == &other; + } + }; +} \ No newline at end of file diff --git a/test/nvexec/transfer.cpp b/test/nvexec/transfer.cpp index a56360ee0..66c2077ec 100644 --- a/test/nvexec/transfer.cpp +++ b/test/nvexec/transfer.cpp @@ -9,127 +9,130 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE( - "nvexec transfer to stream context returns a sender", - "[cuda][stream][adaptors][transfer]") { - nvexec::stream_context stream_ctx{}; - exec::inline_scheduler cpu{}; - nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); - - auto snd = ex::schedule(cpu) | ex::transfer(gpu); - STATIC_REQUIRE(ex::sender); - (void) snd; -} - -TEST_CASE( - "nvexec transfer from stream context returns a sender", - "[cuda][stream][adaptors][transfer]") { - nvexec::stream_context stream_ctx{}; - - exec::inline_scheduler cpu{}; - nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); - - auto snd = ex::schedule(gpu) | ex::transfer(cpu); - STATIC_REQUIRE(ex::sender); - (void) snd; -} - -TEST_CASE("nvexec transfer changes context to GPU", "[cuda][stream][adaptors][transfer]") { - nvexec::stream_context stream_ctx{}; - - exec::inline_scheduler cpu{}; - nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); - - auto snd = ex::schedule(cpu) // - | ex::then([=] { - if (!is_on_gpu()) { - return 1; - } - return 0; - }) - | ex::transfer(gpu) // - | ex::then([=](int val) -> int { - if (is_on_gpu() && val == 1) { - return 2; - } - return 0; - }); - const auto [result] = stdexec::sync_wait(std::move(snd)).value(); - - REQUIRE(result == 2); -} - -TEST_CASE("nvexec transfer changes context from GPU", "[cuda][stream][adaptors][transfer]") { - nvexec::stream_context stream_ctx{}; - - exec::inline_scheduler cpu{}; - nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); - - auto snd = ex::schedule(gpu) // - | ex::then([=] { - if (is_on_gpu()) { - return 1; - } - return 0; - }) - | ex::transfer(cpu) // - | ex::then([=](int val) -> int { - if (!is_on_gpu() && val == 1) { - return 2; - } - return 0; - }); - const auto [result] = stdexec::sync_wait(std::move(snd)).value(); - - REQUIRE(result == 2); -} - -TEST_CASE("nvexec transfer_just changes context to GPU", "[cuda][stream][adaptors][transfer]") { - nvexec::stream_context stream_ctx{}; - nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); - - auto snd = ex::transfer_just(gpu, 42) // - | ex::then([=](auto i) { - if (is_on_gpu() && i == 42) { - return true; - } - return false; - }); - const auto [result] = stdexec::sync_wait(std::move(snd)).value(); - - REQUIRE(result == true); -} - -TEST_CASE("nvexec transfer_just supports move-only types", "[cuda][stream][adaptors][transfer]") { - nvexec::stream_context stream_ctx{}; - nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); - - auto snd = ex::transfer_just(gpu, move_only_t{42}) // - | ex::then([=](move_only_t&& val) noexcept { - if (is_on_gpu() && val.contains(42)) { - return true; - } - return false; - }); - const auto [result] = stdexec::sync_wait(std::move(snd)).value(); - - REQUIRE(result == true); -} - -TEST_CASE("nvexec transfer supports move-only types", "[cuda][stream][adaptors][transfer]") { - nvexec::stream_context stream_ctx{}; - - exec::inline_scheduler cpu{}; - nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); - - auto snd = ex::schedule(gpu) | ex::then([] { return move_only_t{42}; }) | ex::transfer(cpu) - | ex::then([=](move_only_t val) noexcept { - if (!is_on_gpu() && val.contains(42)) { - return true; - } - return false; - }); - const auto [result] = stdexec::sync_wait(std::move(snd)).value(); - - REQUIRE(result == true); +namespace { + + TEST_CASE( + "nvexec transfer to stream context returns a sender", + "[cuda][stream][adaptors][transfer]") { + nvexec::stream_context stream_ctx{}; + exec::inline_scheduler cpu{}; + nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); + + auto snd = ex::schedule(cpu) | ex::transfer(gpu); + STATIC_REQUIRE(ex::sender); + (void) snd; + } + + TEST_CASE( + "nvexec transfer from stream context returns a sender", + "[cuda][stream][adaptors][transfer]") { + nvexec::stream_context stream_ctx{}; + + exec::inline_scheduler cpu{}; + nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); + + auto snd = ex::schedule(gpu) | ex::transfer(cpu); + STATIC_REQUIRE(ex::sender); + (void) snd; + } + + TEST_CASE("nvexec transfer changes context to GPU", "[cuda][stream][adaptors][transfer]") { + nvexec::stream_context stream_ctx{}; + + exec::inline_scheduler cpu{}; + nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); + + auto snd = ex::schedule(cpu) // + | ex::then([=] { + if (!is_on_gpu()) { + return 1; + } + return 0; + }) + | ex::transfer(gpu) // + | ex::then([=](int val) -> int { + if (is_on_gpu() && val == 1) { + return 2; + } + return 0; + }); + const auto [result] = stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(result == 2); + } + + TEST_CASE("nvexec transfer changes context from GPU", "[cuda][stream][adaptors][transfer]") { + nvexec::stream_context stream_ctx{}; + + exec::inline_scheduler cpu{}; + nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); + + auto snd = ex::schedule(gpu) // + | ex::then([=] { + if (is_on_gpu()) { + return 1; + } + return 0; + }) + | ex::transfer(cpu) // + | ex::then([=](int val) -> int { + if (!is_on_gpu() && val == 1) { + return 2; + } + return 0; + }); + const auto [result] = stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(result == 2); + } + + TEST_CASE("nvexec transfer_just changes context to GPU", "[cuda][stream][adaptors][transfer]") { + nvexec::stream_context stream_ctx{}; + nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); + + auto snd = ex::transfer_just(gpu, 42) // + | ex::then([=](auto i) { + if (is_on_gpu() && i == 42) { + return true; + } + return false; + }); + const auto [result] = stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(result == true); + } + + TEST_CASE("nvexec transfer_just supports move-only types", "[cuda][stream][adaptors][transfer]") { + nvexec::stream_context stream_ctx{}; + nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); + + auto snd = ex::transfer_just(gpu, move_only_t{42}) // + | ex::then([=](move_only_t&& val) noexcept { + if (is_on_gpu() && val.contains(42)) { + return true; + } + return false; + }); + const auto [result] = stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(result == true); + } + + TEST_CASE("nvexec transfer supports move-only types", "[cuda][stream][adaptors][transfer]") { + nvexec::stream_context stream_ctx{}; + + exec::inline_scheduler cpu{}; + nvexec::stream_scheduler gpu = stream_ctx.get_scheduler(); + + auto snd = ex::schedule(gpu) | ex::then([] { return move_only_t{42}; }) | ex::transfer(cpu) + | ex::then([=](move_only_t val) noexcept { + if (!is_on_gpu() && val.contains(42)) { + return true; + } + return false; + }); + const auto [result] = stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(result == true); + } } diff --git a/test/nvexec/transfer_when_all.cpp b/test/nvexec/transfer_when_all.cpp index e9482ae05..07ff9ea75 100644 --- a/test/nvexec/transfer_when_all.cpp +++ b/test/nvexec/transfer_when_all.cpp @@ -24,102 +24,106 @@ namespace ex = stdexec; -TEST_CASE( - "nvexec transfer_when_all returns a sender", - "[cuda][stream][adaptors][transfer_when_all]") { - nvexec::stream_context stream_ctx{}; - auto gpu = stream_ctx.get_scheduler(); - auto snd = ex::transfer_when_all(gpu, ex::just(3), ex::just(0.1415)); - static_assert(ex::sender); - (void) snd; -} +namespace { -TEST_CASE( - "nvexec transfer_when_all with environment returns a sender", - "[cuda][stream][adaptors][transfer_when_all]") { - nvexec::stream_context stream_ctx{}; - auto gpu = stream_ctx.get_scheduler(); - auto snd = ex::transfer_when_all(gpu, ex::just(3), ex::just(0.1415)); - static_assert(ex::sender_in); - (void) snd; -} + TEST_CASE( + "nvexec transfer_when_all returns a sender", + "[cuda][stream][adaptors][transfer_when_all]") { + nvexec::stream_context stream_ctx{}; + auto gpu = stream_ctx.get_scheduler(); + auto snd = ex::transfer_when_all(gpu, ex::just(3), ex::just(0.1415)); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE( - "nvexec transfer_when_all with no senders", - "[cuda][stream][adaptors][transfer_when_all]") { - nvexec::stream_context stream_ctx{}; - auto gpu = stream_ctx.get_scheduler(); - auto snd = ex::transfer_when_all(gpu); - wait_for_value(std::move(snd)); -} + TEST_CASE( + "nvexec transfer_when_all with environment returns a sender", + "[cuda][stream][adaptors][transfer_when_all]") { + nvexec::stream_context stream_ctx{}; + auto gpu = stream_ctx.get_scheduler(); + auto snd = ex::transfer_when_all(gpu, ex::just(3), ex::just(0.1415)); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("nvexec transfer_when_all one sender", "[cuda][stream][adaptors][transfer_when_all]") { - nvexec::stream_context stream_ctx{}; - auto gpu = stream_ctx.get_scheduler(); - auto snd = ex::transfer_when_all(gpu, ex::just(3.1415)); - wait_for_value(std::move(snd), 3.1415); -} + TEST_CASE( + "nvexec transfer_when_all with no senders", + "[cuda][stream][adaptors][transfer_when_all]") { + nvexec::stream_context stream_ctx{}; + auto gpu = stream_ctx.get_scheduler(); + auto snd = ex::transfer_when_all(gpu); + wait_for_value(std::move(snd)); + } -TEST_CASE("nvexec transfer_when_all two senders", "[cuda][stream][adaptors][transfer_when_all]") { - nvexec::stream_context stream_ctx{}; - auto gpu = stream_ctx.get_scheduler(); - auto snd1 = ex::transfer_when_all(gpu, ex::just(3), ex::just(0.1415)); - auto snd2 = std::move(snd1) | ex::then([](int x, double y) { return x + y; }); - wait_for_value(std::move(snd2), 3.1415); -} + TEST_CASE("nvexec transfer_when_all one sender", "[cuda][stream][adaptors][transfer_when_all]") { + nvexec::stream_context stream_ctx{}; + auto gpu = stream_ctx.get_scheduler(); + auto snd = ex::transfer_when_all(gpu, ex::just(3.1415)); + wait_for_value(std::move(snd), 3.1415); + } -TEST_CASE( - "nvexec transfer_when_all two senders on same scheduler", - "[cuda][stream][adaptors][transfer_when_all]") { - nvexec::stream_context stream_ctx{}; - auto gpu = stream_ctx.get_scheduler(); - auto snd1 = ex::transfer_when_all(gpu, ex::transfer_just(gpu, 3), ex::transfer_just(gpu, 0.1415)); - auto snd2 = std::move(snd1) | ex::then([](int x, double y) { return x + y; }); - wait_for_value(std::move(snd2), 3.1415); -} + TEST_CASE("nvexec transfer_when_all two senders", "[cuda][stream][adaptors][transfer_when_all]") { + nvexec::stream_context stream_ctx{}; + auto gpu = stream_ctx.get_scheduler(); + auto snd1 = ex::transfer_when_all(gpu, ex::just(3), ex::just(0.1415)); + auto snd2 = std::move(snd1) | ex::then([](int x, double y) { return x + y; }); + wait_for_value(std::move(snd2), 3.1415); + } -TEST_CASE( - "nvexec transfer_when_all_with_variant returns a sender", - "[cuda][stream][adaptors][transfer_when_all_with_variant]") { - nvexec::stream_context stream_ctx{}; - auto gpu = stream_ctx.get_scheduler(); - auto snd = ex::transfer_when_all_with_variant(gpu, ex::just(3), ex::just(0.1415)); - static_assert(ex::sender); - (void) snd; -} + TEST_CASE( + "nvexec transfer_when_all two senders on same scheduler", + "[cuda][stream][adaptors][transfer_when_all]") { + nvexec::stream_context stream_ctx{}; + auto gpu = stream_ctx.get_scheduler(); + auto snd1 = ex::transfer_when_all( + gpu, ex::transfer_just(gpu, 3), ex::transfer_just(gpu, 0.1415)); + auto snd2 = std::move(snd1) | ex::then([](int x, double y) { return x + y; }); + wait_for_value(std::move(snd2), 3.1415); + } -TEST_CASE( - "nvexec transfer_when_all_with_variant with environment returns a sender", - "[cuda][stream][adaptors][transfer_when_all_with_variant]") { - nvexec::stream_context stream_ctx{}; - auto gpu = stream_ctx.get_scheduler(); - auto snd = ex::transfer_when_all_with_variant(gpu, ex::just(3), ex::just(0.1415)); - static_assert(ex::sender_in); - (void) snd; -} + TEST_CASE( + "nvexec transfer_when_all_with_variant returns a sender", + "[cuda][stream][adaptors][transfer_when_all_with_variant]") { + nvexec::stream_context stream_ctx{}; + auto gpu = stream_ctx.get_scheduler(); + auto snd = ex::transfer_when_all_with_variant(gpu, ex::just(3), ex::just(0.1415)); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE( - "nvexec transfer_when_all_with_variant two senders", - "[cuda][stream][adaptors][transfer_when_all_with_variant]") { - nvexec::stream_context stream_ctx{}; - auto gpu = stream_ctx.get_scheduler(); - auto snd1 = ex::transfer_when_all_with_variant(gpu, ex::just(3), ex::just(0.1415)); - auto snd2 = std::move(snd1) | ex::then([](auto&&, auto&&) { return 42; }); - wait_for_value(std::move(snd2), 42); -} + TEST_CASE( + "nvexec transfer_when_all_with_variant with environment returns a sender", + "[cuda][stream][adaptors][transfer_when_all_with_variant]") { + nvexec::stream_context stream_ctx{}; + auto gpu = stream_ctx.get_scheduler(); + auto snd = ex::transfer_when_all_with_variant(gpu, ex::just(3), ex::just(0.1415)); + static_assert(ex::sender_in); + (void) snd; + } + + TEST_CASE( + "nvexec transfer_when_all_with_variant two senders", + "[cuda][stream][adaptors][transfer_when_all_with_variant]") { + nvexec::stream_context stream_ctx{}; + auto gpu = stream_ctx.get_scheduler(); + auto snd1 = ex::transfer_when_all_with_variant(gpu, ex::just(3), ex::just(0.1415)); + auto snd2 = std::move(snd1) | ex::then([](auto&&, auto&&) { return 42; }); + wait_for_value(std::move(snd2), 42); + } -TEST_CASE( - "nvexec transfer_when_all_with_variant basic example", - "[cuda][stream][adaptors][transfer_when_all_with_variant]") { - nvexec::stream_context stream_ctx{}; - auto gpu = stream_ctx.get_scheduler(); - ex::sender auto snd = ex::transfer_when_all_with_variant( // - gpu, // - ex::just(2), // - ex::just(3.14) // - ); - wait_for_value( - std::move(snd), // - std::variant>{2}, // - std::variant>{3.14}); + TEST_CASE( + "nvexec transfer_when_all_with_variant basic example", + "[cuda][stream][adaptors][transfer_when_all_with_variant]") { + nvexec::stream_context stream_ctx{}; + auto gpu = stream_ctx.get_scheduler(); + ex::sender auto snd = ex::transfer_when_all_with_variant( // + gpu, // + ex::just(2), // + ex::just(3.14) // + ); + wait_for_value( + std::move(snd), // + std::variant>{2}, // + std::variant>{3.14}); + } } diff --git a/test/nvexec/upon_error.cpp b/test/nvexec/upon_error.cpp index eb5823949..fc378bb84 100644 --- a/test/nvexec/upon_error.cpp +++ b/test/nvexec/upon_error.cpp @@ -9,72 +9,75 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE("nvexec upon_error returns a sender", "[cuda][stream][adaptors][upon_error]") { - nvexec::stream_context stream_ctx{}; - - auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) - | ex::upon_error([](int) {}); - STATIC_REQUIRE(ex::sender); - (void) snd; -} - -TEST_CASE("nvexec upon_error executes on GPU", "[cuda][stream][adaptors][upon_error]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) - | ex::upon_error([=](int err) { - if (is_on_gpu() && err == 42) { - flags.set(); - } - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} - -TEST_CASE( - "upon_error can preceed a sender without values", - "[cuda][stream][adaptors][upon_error]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t<2> flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) - | ex::upon_error([=](int err) { - if (is_on_gpu() && err == 42) { - flags.set(0); - } - }) - | a_sender([=]() noexcept { - if (is_on_gpu()) { - flags.set(1); - } - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} - -TEST_CASE( - "upon_error can succeed a sender without values", - "[cuda][stream][adaptors][upon_error]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) - | a_sender([=]() noexcept {}) // - | ex::upon_error([=](int err) noexcept { - if (is_on_gpu() && err == 42) { - flags.set(); - } - }); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); +namespace { + + TEST_CASE("nvexec upon_error returns a sender", "[cuda][stream][adaptors][upon_error]") { + nvexec::stream_context stream_ctx{}; + + auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) + | ex::upon_error([](int) {}); + STATIC_REQUIRE(ex::sender); + (void) snd; + } + + TEST_CASE("nvexec upon_error executes on GPU", "[cuda][stream][adaptors][upon_error]") { + nvexec::stream_context stream_ctx{}; + + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) + | ex::upon_error([=](int err) { + if (is_on_gpu() && err == 42) { + flags.set(); + } + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + TEST_CASE( + "upon_error can preceed a sender without values", + "[cuda][stream][adaptors][upon_error]") { + nvexec::stream_context stream_ctx{}; + + flags_storage_t<2> flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) + | ex::upon_error([=](int err) { + if (is_on_gpu() && err == 42) { + flags.set(0); + } + }) + | a_sender([=]() noexcept { + if (is_on_gpu()) { + flags.set(1); + } + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + TEST_CASE( + "upon_error can succeed a sender without values", + "[cuda][stream][adaptors][upon_error]") { + nvexec::stream_context stream_ctx{}; + + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::just_error(42) | ex::transfer(stream_ctx.get_scheduler()) + | a_sender([=]() noexcept {}) // + | ex::upon_error([=](int err) noexcept { + if (is_on_gpu() && err == 42) { + flags.set(); + } + }); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } } diff --git a/test/nvexec/upon_stopped.cpp b/test/nvexec/upon_stopped.cpp index c9f11fcfc..20a5a8731 100644 --- a/test/nvexec/upon_stopped.cpp +++ b/test/nvexec/upon_stopped.cpp @@ -8,28 +8,31 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE("nvexec upon_stopped returns a sender", "[cuda][stream][adaptors][upon_stopped]") { - nvexec::stream_context stream_ctx{}; +namespace { - auto snd = ex::just_stopped() | ex::transfer(stream_ctx.get_scheduler()) - | ex::upon_stopped([] { return ex::just(); }); - STATIC_REQUIRE(ex::sender); - (void) snd; -} + TEST_CASE("nvexec upon_stopped returns a sender", "[cuda][stream][adaptors][upon_stopped]") { + nvexec::stream_context stream_ctx{}; + + auto snd = ex::just_stopped() | ex::transfer(stream_ctx.get_scheduler()) + | ex::upon_stopped([] { return ex::just(); }); + STATIC_REQUIRE(ex::sender); + (void) snd; + } -TEST_CASE("nvexec upon_stopped executes on GPU", "[cuda][stream][adaptors][upon_stopped]") { - nvexec::stream_context stream_ctx{}; + TEST_CASE("nvexec upon_stopped executes on GPU", "[cuda][stream][adaptors][upon_stopped]") { + nvexec::stream_context stream_ctx{}; - flags_storage_t flags_storage{}; - auto flags = flags_storage.get(); + flags_storage_t flags_storage{}; + auto flags = flags_storage.get(); - auto snd = ex::just_stopped() // - | ex::transfer(stream_ctx.get_scheduler()) | ex::upon_stopped([=] { - if (is_on_gpu()) { - flags.set(); - } - }); - stdexec::sync_wait(std::move(snd)); + auto snd = ex::just_stopped() // + | ex::transfer(stream_ctx.get_scheduler()) | ex::upon_stopped([=] { + if (is_on_gpu()) { + flags.set(); + } + }); + stdexec::sync_wait(std::move(snd)); - REQUIRE(flags_storage.all_set_once()); + REQUIRE(flags_storage.all_set_once()); + } } diff --git a/test/nvexec/variant.cpp b/test/nvexec/variant.cpp index cfdf97f58..7e6128f82 100644 --- a/test/nvexec/variant.cpp +++ b/test/nvexec/variant.cpp @@ -27,117 +27,120 @@ using nvexec::variant_t; using nvexec::visit; -TEST_CASE("nvexec variant max size is correct", "[cuda][stream][containers][variant]") { - STATIC_REQUIRE(variant_t::max_size == sizeof(double)); - STATIC_REQUIRE(variant_t::max_size == sizeof(double)); - STATIC_REQUIRE(variant_t::max_size == sizeof(double)); - STATIC_REQUIRE(variant_t::max_size == sizeof(int)); - STATIC_REQUIRE(variant_t::max_size == sizeof(char)); -} - -TEST_CASE("nvexec variant max alignment is correct", "[cuda][stream][containers][variant]") { - STATIC_REQUIRE(variant_t::max_size == std::alignment_of_v); - STATIC_REQUIRE(variant_t::max_size == std::alignment_of_v); - STATIC_REQUIRE(variant_t::max_size == std::alignment_of_v); - STATIC_REQUIRE(variant_t::max_size == std::alignment_of_v); - STATIC_REQUIRE(variant_t::max_size == std::alignment_of_v); -} - -TEST_CASE("nvexec variant size is correct", "[cuda][stream][containers][variant]") { - STATIC_REQUIRE(variant_t::size == 1); - STATIC_REQUIRE(variant_t::size == 2); - STATIC_REQUIRE(variant_t::size == 3); -} - -TEST_CASE("nvexec variant emplaces alternative from CPU", "[cuda][stream][containers][variant]") { - variant_t v; - REQUIRE(v.index_ == 0); - - v.emplace(4.2); - visit([](auto alt) { REQUIRE(alt == 4.2); }, v); - - v.emplace(42); - visit([](auto alt) { REQUIRE(alt == 42); }, v); -} - -template -__global__ void kernel(V* v, T alt) { - v->template emplace(alt); -} - -TEST_CASE("nvexec variant emplaces alternative from GPU", "[cuda][stream][containers][variant]") { - using variant_t = variant_t; - thrust::universal_vector variant_storage(1); - variant_t* v = thrust::raw_pointer_cast(variant_storage.data()); - - REQUIRE(v->index_ == 0); - - kernel<<<1, 1>>>(v, 4.2); - THROW_ON_CUDA_ERROR(cudaDeviceSynchronize()); - - visit([](auto alt) { REQUIRE(alt == 4.2); }, *v); - - kernel<<<1, 1>>>(v, 42); - THROW_ON_CUDA_ERROR(cudaDeviceSynchronize()); - - visit([](auto alt) { REQUIRE(alt == 42); }, *v); -} - -TEST_CASE("nvexec variant works with cuda tuple", "[cuda][stream][containers][variant]") { - variant_t, cuda::std::tuple> v; - REQUIRE(v.index_ == 0); - - v.emplace>(42, 4.2); - visit( - [](auto& tuple) { - cuda::std::apply( - [](auto i, auto d) { - REQUIRE(i == 42); - REQUIRE(d == 4.2); - }, - tuple); - }, - v); - - v.emplace>('f', 4); - visit( - [](auto& tuple) { - cuda::std::apply( - [](auto c, auto i) { - REQUIRE(c == 'f'); - REQUIRE(i == 4); - }, - tuple); - }, - v); -} - -TEST_CASE("nvexec variant internal index bypass works", "[cuda][stream][containers][variant]") { - variant_t, cuda::std::tuple> v; - - v.emplace>(42, 4.2); - visit( - [](auto& tuple) { - cuda::std::apply( - [](auto i, auto d) { - REQUIRE(i == 42); - REQUIRE(d == 4.2); - }, - tuple); - }, - v, - 0); - - v.emplace>('f', 4); - visit( - [](auto& tuple) { - cuda::std::apply( - [](auto c, auto i) { - REQUIRE(c == 'f'); - REQUIRE(i == 4); - }, - tuple); - }, - v, - 1); +namespace { + + TEST_CASE("nvexec variant max size is correct", "[cuda][stream][containers][variant]") { + STATIC_REQUIRE(variant_t::max_size == sizeof(double)); + STATIC_REQUIRE(variant_t::max_size == sizeof(double)); + STATIC_REQUIRE(variant_t::max_size == sizeof(double)); + STATIC_REQUIRE(variant_t::max_size == sizeof(int)); + STATIC_REQUIRE(variant_t::max_size == sizeof(char)); + } + + TEST_CASE("nvexec variant max alignment is correct", "[cuda][stream][containers][variant]") { + STATIC_REQUIRE(variant_t::max_size == std::alignment_of_v); + STATIC_REQUIRE(variant_t::max_size == std::alignment_of_v); + STATIC_REQUIRE(variant_t::max_size == std::alignment_of_v); + STATIC_REQUIRE(variant_t::max_size == std::alignment_of_v); + STATIC_REQUIRE(variant_t::max_size == std::alignment_of_v); + } + + TEST_CASE("nvexec variant size is correct", "[cuda][stream][containers][variant]") { + STATIC_REQUIRE(variant_t::size == 1); + STATIC_REQUIRE(variant_t::size == 2); + STATIC_REQUIRE(variant_t::size == 3); + } + + TEST_CASE("nvexec variant emplaces alternative from CPU", "[cuda][stream][containers][variant]") { + variant_t v; + REQUIRE(v.index_ == 0); + + v.emplace(4.2); + visit([](auto alt) { REQUIRE(alt == 4.2); }, v); + + v.emplace(42); + visit([](auto alt) { REQUIRE(alt == 42); }, v); + } + + template + __global__ void kernel(V* v, T alt) { + v->template emplace(alt); + } + + TEST_CASE("nvexec variant emplaces alternative from GPU", "[cuda][stream][containers][variant]") { + using variant_t = variant_t; + thrust::universal_vector variant_storage(1); + variant_t* v = thrust::raw_pointer_cast(variant_storage.data()); + + REQUIRE(v->index_ == 0); + + kernel<<<1, 1>>>(v, 4.2); + THROW_ON_CUDA_ERROR(cudaDeviceSynchronize()); + + visit([](auto alt) { REQUIRE(alt == 4.2); }, *v); + + kernel<<<1, 1>>>(v, 42); + THROW_ON_CUDA_ERROR(cudaDeviceSynchronize()); + + visit([](auto alt) { REQUIRE(alt == 42); }, *v); + } + + TEST_CASE("nvexec variant works with cuda tuple", "[cuda][stream][containers][variant]") { + variant_t, cuda::std::tuple> v; + REQUIRE(v.index_ == 0); + + v.emplace>(42, 4.2); + visit( + [](auto& tuple) { + cuda::std::apply( + [](auto i, auto d) { + REQUIRE(i == 42); + REQUIRE(d == 4.2); + }, + tuple); + }, + v); + + v.emplace>('f', 4); + visit( + [](auto& tuple) { + cuda::std::apply( + [](auto c, auto i) { + REQUIRE(c == 'f'); + REQUIRE(i == 4); + }, + tuple); + }, + v); + } + + TEST_CASE("nvexec variant internal index bypass works", "[cuda][stream][containers][variant]") { + variant_t, cuda::std::tuple> v; + + v.emplace>(42, 4.2); + visit( + [](auto& tuple) { + cuda::std::apply( + [](auto i, auto d) { + REQUIRE(i == 42); + REQUIRE(d == 4.2); + }, + tuple); + }, + v, + 0); + + v.emplace>('f', 4); + visit( + [](auto& tuple) { + cuda::std::apply( + [](auto c, auto i) { + REQUIRE(c == 'f'); + REQUIRE(i == 4); + }, + tuple); + }, + v, + 1); + } } diff --git a/test/nvexec/when_all.cpp b/test/nvexec/when_all.cpp index 1d662cfaf..c8ed99d85 100644 --- a/test/nvexec/when_all.cpp +++ b/test/nvexec/when_all.cpp @@ -9,77 +9,80 @@ namespace ex = stdexec; using nvexec::is_on_gpu; -TEST_CASE("nvexec when_all returns a sender", "[cuda][stream][adaptors][when_all]") { - nvexec::stream_context stream_ctx{}; - auto snd = ex::when_all( - ex::schedule(stream_ctx.get_scheduler()), ex::schedule(stream_ctx.get_scheduler())); - STATIC_REQUIRE(ex::sender); - (void) snd; -} - -TEST_CASE("nvexec when_all works", "[cuda][stream][adaptors][when_all]") { - nvexec::stream_context stream_ctx{}; - - flags_storage_t<2> flags_storage{}; - auto flags = flags_storage.get(); - - auto snd = ex::when_all( - ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([=] { - if (is_on_gpu()) { - flags.set(0); - } - }), - ex::schedule(stream_ctx.get_scheduler()) // - | ex::then([=] { - if (is_on_gpu()) { - flags.set(1); - } - })); - stdexec::sync_wait(std::move(snd)); - - REQUIRE(flags_storage.all_set_once()); -} - -TEST_CASE("nvexec when_all returns values", "[cuda][stream][adaptors][when_all]") { - nvexec::stream_context stream_ctx{}; - - auto snd = ex::when_all( - ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 24; }), - ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 42; })); - auto [v1, v2] = stdexec::sync_wait(std::move(snd)).value(); - - REQUIRE(v1 == 24); - REQUIRE(v2 == 42); -} - -TEST_CASE("nvexec when_all with many senders", "[cuda][stream][adaptors][when_all]") { - nvexec::stream_context stream_ctx{}; - - auto snd = ex::when_all( - ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 1; }), - ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 2; }), - ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 3; }), - ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 4; }), - ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 5; })); - auto [v1, v2, v3, v4, v5] = stdexec::sync_wait(std::move(snd)).value(); - - REQUIRE(v1 == 1); - REQUIRE(v2 == 2); - REQUIRE(v3 == 3); - REQUIRE(v4 == 4); - REQUIRE(v5 == 5); -} - -TEST_CASE("nvexec when_all works with unknown senders", "[cuda][stream][adaptors][when_all]") { - nvexec::stream_context stream_ctx{}; - auto sch = stream_ctx.get_scheduler(); - - auto snd = ex::when_all( - ex::schedule(sch) | ex::then([]() -> int { return is_on_gpu() * 24; }), - ex::schedule(sch) | a_sender([]() -> int { return is_on_gpu() * 42; })); - auto [v1, v2] = stdexec::sync_wait(std::move(snd)).value(); - - REQUIRE(v1 == 24); - REQUIRE(v2 == 42); +namespace { + + TEST_CASE("nvexec when_all returns a sender", "[cuda][stream][adaptors][when_all]") { + nvexec::stream_context stream_ctx{}; + auto snd = ex::when_all( + ex::schedule(stream_ctx.get_scheduler()), ex::schedule(stream_ctx.get_scheduler())); + STATIC_REQUIRE(ex::sender); + (void) snd; + } + + TEST_CASE("nvexec when_all works", "[cuda][stream][adaptors][when_all]") { + nvexec::stream_context stream_ctx{}; + + flags_storage_t<2> flags_storage{}; + auto flags = flags_storage.get(); + + auto snd = ex::when_all( + ex::schedule(stream_ctx.get_scheduler()) // + | ex::then([=] { + if (is_on_gpu()) { + flags.set(0); + } + }), + ex::schedule(stream_ctx.get_scheduler()) // + | ex::then([=] { + if (is_on_gpu()) { + flags.set(1); + } + })); + stdexec::sync_wait(std::move(snd)); + + REQUIRE(flags_storage.all_set_once()); + } + + TEST_CASE("nvexec when_all returns values", "[cuda][stream][adaptors][when_all]") { + nvexec::stream_context stream_ctx{}; + + auto snd = ex::when_all( + ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 24; }), + ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 42; })); + auto [v1, v2] = stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(v1 == 24); + REQUIRE(v2 == 42); + } + + TEST_CASE("nvexec when_all with many senders", "[cuda][stream][adaptors][when_all]") { + nvexec::stream_context stream_ctx{}; + + auto snd = ex::when_all( + ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 1; }), + ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 2; }), + ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 3; }), + ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 4; }), + ex::schedule(stream_ctx.get_scheduler()) | ex::then([] { return is_on_gpu() * 5; })); + auto [v1, v2, v3, v4, v5] = stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(v1 == 1); + REQUIRE(v2 == 2); + REQUIRE(v3 == 3); + REQUIRE(v4 == 4); + REQUIRE(v5 == 5); + } + + TEST_CASE("nvexec when_all works with unknown senders", "[cuda][stream][adaptors][when_all]") { + nvexec::stream_context stream_ctx{}; + auto sch = stream_ctx.get_scheduler(); + + auto snd = ex::when_all( + ex::schedule(sch) | ex::then([]() -> int { return is_on_gpu() * 24; }), + ex::schedule(sch) | a_sender([]() -> int { return is_on_gpu() * 42; })); + auto [v1, v2] = stdexec::sync_wait(std::move(snd)).value(); + + REQUIRE(v1 == 24); + REQUIRE(v2 == 42); + } } diff --git a/test/stdexec/algos/adaptors/test_bulk.cpp b/test/stdexec/algos/adaptors/test_bulk.cpp index c0b8960bc..02e898eeb 100644 --- a/test/stdexec/algos/adaptors/test_bulk.cpp +++ b/test/stdexec/algos/adaptors/test_bulk.cpp @@ -27,304 +27,307 @@ namespace ex = stdexec; -template -void function(Shape i) { - Counter[i]++; -} - -template -struct function_object_t { - int* Counter; - - void operator()(Shape i) { +namespace { + template + void function(Shape i) { Counter[i]++; } -}; - -TEST_CASE("bulk returns a sender", "[adaptors][bulk]") { - auto snd = ex::bulk(ex::just(19), 8, [](int idx, int val) {}); - static_assert(ex::sender); - (void) snd; -} -TEST_CASE("bulk with environment returns a sender", "[adaptors][bulk]") { - auto snd = ex::bulk(ex::just(19), 8, [](int idx, int val) {}); - static_assert(ex::sender_in); - (void) snd; -} + template + struct function_object_t { + int* Counter; -TEST_CASE("bulk can be piped", "[adaptors][bulk]") { - ex::sender auto snd = ex::just() | ex::bulk(42, [](int i) {}); - (void) snd; -} + void operator()(Shape i) { + Counter[i]++; + } + }; -TEST_CASE("bulk keeps values_type from input sender", "[adaptors][bulk]") { - constexpr int n = 42; - check_val_types>>(ex::just() | ex::bulk(n, [](int) {})); - check_val_types>>(ex::just(4.2) | ex::bulk(n, [](int, double) {})); - check_val_types>>( - ex::just(4.2, std::string{}) | ex::bulk(n, [](int, double, std::string) {})); -} + TEST_CASE("bulk returns a sender", "[adaptors][bulk]") { + auto snd = ex::bulk(ex::just(19), 8, [](int idx, int val) {}); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE("bulk keeps error_types from input sender", "[adaptors][bulk]") { - constexpr int n = 42; - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{43}; - - check_err_types>( // - ex::transfer_just(sched1) | ex::bulk(n, [](int) noexcept {})); - check_err_types>( // - ex::transfer_just(sched2) | ex::bulk(n, [](int) noexcept {})); - check_err_types>( // - ex::just_error(n) | ex::bulk(n, [](int) noexcept {})); - check_err_types>( // - ex::transfer_just(sched3) | ex::bulk(n, [](int) noexcept {})); - check_err_types>( // - ex::transfer_just(sched3) | ex::bulk(n, [](int) { throw std::logic_error{"err"}; })); -} + TEST_CASE("bulk with environment returns a sender", "[adaptors][bulk]") { + auto snd = ex::bulk(ex::just(19), 8, [](int idx, int val) {}); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("bulk can be used with a function", "[adaptors][bulk]") { - constexpr int n = 9; - static int counter[n]{}; - std::fill_n(counter, n, 0); + TEST_CASE("bulk can be piped", "[adaptors][bulk]") { + ex::sender auto snd = ex::just() | ex::bulk(42, [](int i) {}); + (void) snd; + } - ex::sender auto snd = ex::just() | ex::bulk(n, function); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); + TEST_CASE("bulk keeps values_type from input sender", "[adaptors][bulk]") { + constexpr int n = 42; + check_val_types>>(ex::just() | ex::bulk(n, [](int) {})); + check_val_types>>( + ex::just(4.2) | ex::bulk(n, [](int, double) {})); + check_val_types>>( + ex::just(4.2, std::string{}) | ex::bulk(n, [](int, double, std::string) {})); + } - for (int i = 0; i < n; i++) { - CHECK(counter[i] == 1); + TEST_CASE("bulk keeps error_types from input sender", "[adaptors][bulk]") { + constexpr int n = 42; + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{43}; + + check_err_types>( // + ex::transfer_just(sched1) | ex::bulk(n, [](int) noexcept {})); + check_err_types>( // + ex::transfer_just(sched2) | ex::bulk(n, [](int) noexcept {})); + check_err_types>( // + ex::just_error(n) | ex::bulk(n, [](int) noexcept {})); + check_err_types>( // + ex::transfer_just(sched3) | ex::bulk(n, [](int) noexcept {})); + check_err_types>( // + ex::transfer_just(sched3) | ex::bulk(n, [](int) { throw std::logic_error{"err"}; })); } -} -TEST_CASE("bulk can be used with a function object", "[adaptors][bulk]") { - constexpr int n = 9; - int counter[n]{0}; - function_object_t fn{counter}; + TEST_CASE("bulk can be used with a function", "[adaptors][bulk]") { + constexpr int n = 9; + static int counter[n]{}; + std::fill_n(counter, n, 0); - ex::sender auto snd = ex::just() | ex::bulk(n, fn); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); + ex::sender auto snd = ex::just() | ex::bulk(n, function); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); - for (int i = 0; i < n; i++) { - CHECK(counter[i] == 1); + for (int i = 0; i < n; i++) { + CHECK(counter[i] == 1); + } } -} -TEST_CASE("bulk can be used with a lambda", "[adaptors][bulk]") { - constexpr int n = 9; - int counter[n]{0}; + TEST_CASE("bulk can be used with a function object", "[adaptors][bulk]") { + constexpr int n = 9; + int counter[n]{0}; + function_object_t fn{counter}; - ex::sender auto snd = ex::just() | ex::bulk(n, [&](int i) { counter[i]++; }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); + ex::sender auto snd = ex::just() | ex::bulk(n, fn); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); - for (int i = 0; i < n; i++) { - CHECK(counter[i] == 1); + for (int i = 0; i < n; i++) { + CHECK(counter[i] == 1); + } } -} -TEST_CASE("bulk forwards values", "[adaptors][bulk]") { - constexpr int n = 9; - constexpr int magic_number = 42; - int counter[n]{0}; - - auto snd = ex::just(magic_number) // - | ex::bulk(n, [&](int i, int val) { - if (val == magic_number) { - counter[i]++; - } - }); - auto op = ex::connect(std::move(snd), expect_value_receiver{magic_number}); - ex::start(op); - - for (int i = 0; i < n; i++) { - CHECK(counter[i] == 1); + TEST_CASE("bulk can be used with a lambda", "[adaptors][bulk]") { + constexpr int n = 9; + int counter[n]{0}; + + ex::sender auto snd = ex::just() | ex::bulk(n, [&](int i) { counter[i]++; }); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + + for (int i = 0; i < n; i++) { + CHECK(counter[i] == 1); + } } -} -TEST_CASE("bulk forwards values that can be taken by reference", "[adaptors][bulk]") { - constexpr int n = 9; - std::vector vals(n, 0); - std::vector vals_expected(n); - std::iota(vals_expected.begin(), vals_expected.end(), 0); + TEST_CASE("bulk forwards values", "[adaptors][bulk]") { + constexpr int n = 9; + constexpr int magic_number = 42; + int counter[n]{0}; + + auto snd = ex::just(magic_number) // + | ex::bulk(n, [&](int i, int val) { + if (val == magic_number) { + counter[i]++; + } + }); + auto op = ex::connect(std::move(snd), expect_value_receiver{magic_number}); + ex::start(op); + + for (int i = 0; i < n; i++) { + CHECK(counter[i] == 1); + } + } - auto snd = ex::just(std::move(vals)) // - | ex::bulk(n, [&](int i, std::vector& vals) { vals[i] = i; }); - auto op = ex::connect(std::move(snd), expect_value_receiver{vals_expected}); - ex::start(op); -} + TEST_CASE("bulk forwards values that can be taken by reference", "[adaptors][bulk]") { + constexpr int n = 9; + std::vector vals(n, 0); + std::vector vals_expected(n); + std::iota(vals_expected.begin(), vals_expected.end(), 0); + + auto snd = ex::just(std::move(vals)) // + | ex::bulk(n, [&](int i, std::vector& vals) { vals[i] = i; }); + auto op = ex::connect(std::move(snd), expect_value_receiver{vals_expected}); + ex::start(op); + } -TEST_CASE("bulk cannot be used to change the value type", "[adaptors][bulk]") { - constexpr int magic_number = 42; - constexpr int n = 2; + TEST_CASE("bulk cannot be used to change the value type", "[adaptors][bulk]") { + constexpr int magic_number = 42; + constexpr int n = 2; - auto snd = ex::just(magic_number) - | ex::bulk(n, [](int i, int) { return function_object_t{nullptr}; }); + auto snd = ex::just(magic_number) + | ex::bulk(n, [](int i, int) { return function_object_t{nullptr}; }); - auto op = ex::connect(std::move(snd), expect_value_receiver{magic_number}); - ex::start(op); -} + auto op = ex::connect(std::move(snd), expect_value_receiver{magic_number}); + ex::start(op); + } -TEST_CASE("bulk can throw, and set_error will be called", "[adaptors][bulk]") { - constexpr int n = 2; + TEST_CASE("bulk can throw, and set_error will be called", "[adaptors][bulk]") { + constexpr int n = 2; - auto snd = ex::just() // - | ex::bulk(n, [](int i) -> int { throw std::logic_error{"err"}; }); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); -} + auto snd = ex::just() // + | ex::bulk(n, [](int i) -> int { throw std::logic_error{"err"}; }); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + } -TEST_CASE("bulk function is not called on error", "[adaptors][bulk]") { - constexpr int n = 2; - int called{}; + TEST_CASE("bulk function is not called on error", "[adaptors][bulk]") { + constexpr int n = 2; + int called{}; - auto snd = ex::just_error(std::string{"err"}) | ex::bulk(n, [&called](int) { called++; }); - auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"err"}}); - ex::start(op); -} + auto snd = ex::just_error(std::string{"err"}) | ex::bulk(n, [&called](int) { called++; }); + auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"err"}}); + ex::start(op); + } -TEST_CASE("bulk function in not called on stop", "[adaptors][bulk]") { - constexpr int n = 2; - int called{}; + TEST_CASE("bulk function in not called on stop", "[adaptors][bulk]") { + constexpr int n = 2; + int called{}; - auto snd = ex::just_stopped() | ex::bulk(n, [&called](int) { called++; }); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); -} + auto snd = ex::just_stopped() | ex::bulk(n, [&called](int) { called++; }); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + } -TEST_CASE("bulk works with static thread pool", "[adaptors][bulk]") { - exec::static_thread_pool pool{4}; - ex::scheduler auto sch = pool.get_scheduler(); + TEST_CASE("bulk works with static thread pool", "[adaptors][bulk]") { + exec::static_thread_pool pool{4}; + ex::scheduler auto sch = pool.get_scheduler(); - SECTION("Without values in the set_value channel") { - for (int n = 0; n < 9; n++) { - std::vector counter(n, 42); + SECTION("Without values in the set_value channel") { + for (int n = 0; n < 9; n++) { + std::vector counter(n, 42); - auto snd = ex::transfer_just(sch) | ex::bulk(n, [&counter](int idx) { counter[idx] = 0; }) - | ex::bulk(n, [&counter](int idx) { counter[idx]++; }); - stdexec::sync_wait(std::move(snd)); + auto snd = ex::transfer_just(sch) | ex::bulk(n, [&counter](int idx) { counter[idx] = 0; }) + | ex::bulk(n, [&counter](int idx) { counter[idx]++; }); + stdexec::sync_wait(std::move(snd)); - const std::size_t actual = std::count(counter.begin(), counter.end(), 1); - const std::size_t expected = n; + const std::size_t actual = std::count(counter.begin(), counter.end(), 1); + const std::size_t expected = n; - CHECK(expected == actual); + CHECK(expected == actual); + } } - } - - SECTION("With values in the set_value channel") { - for (int n = 0; n < 9; n++) { - std::vector counter(n, 42); - auto snd = ex::transfer_just(sch, 42) - | ex::bulk( - n, - [&counter](int idx, int val) { + SECTION("With values in the set_value channel") { + for (int n = 0; n < 9; n++) { + std::vector counter(n, 42); + + auto snd = ex::transfer_just(sch, 42) + | ex::bulk( + n, + [&counter](int idx, int val) { + if (val == 42) { + counter[idx] = 0; + } + }) + | ex::bulk(n, [&counter](int idx, int val) { if (val == 42) { - counter[idx] = 0; + counter[idx]++; } - }) - | ex::bulk(n, [&counter](int idx, int val) { - if (val == 42) { - counter[idx]++; - } - }); - auto [val] = stdexec::sync_wait(std::move(snd)).value(); + }); + auto [val] = stdexec::sync_wait(std::move(snd)).value(); - CHECK(val == 42); + CHECK(val == 42); - const std::size_t actual = std::count(counter.begin(), counter.end(), 1); - const std::size_t expected = n; + const std::size_t actual = std::count(counter.begin(), counter.end(), 1); + const std::size_t expected = n; - CHECK(expected == actual); + CHECK(expected == actual); + } } - } - SECTION("With values in the set_value channel that can be taken by reference") { - for (int n = 0; n < 9; n++) { - std::vector vals(n, 0); - std::vector vals_expected(n); - std::iota(vals_expected.begin(), vals_expected.end(), 1); + SECTION("With values in the set_value channel that can be taken by reference") { + for (int n = 0; n < 9; n++) { + std::vector vals(n, 0); + std::vector vals_expected(n); + std::iota(vals_expected.begin(), vals_expected.end(), 1); - auto snd = ex::transfer_just(sch, std::move(vals)) - | ex::bulk(n, [](int idx, std::vector& vals) { vals[idx] = idx; }) - | ex::bulk(n, [](int idx, std::vector& vals) { ++vals[idx]; }); - auto [vals_actual] = stdexec::sync_wait(std::move(snd)).value(); + auto snd = ex::transfer_just(sch, std::move(vals)) + | ex::bulk(n, [](int idx, std::vector& vals) { vals[idx] = idx; }) + | ex::bulk(n, [](int idx, std::vector& vals) { ++vals[idx]; }); + auto [vals_actual] = stdexec::sync_wait(std::move(snd)).value(); - CHECK(vals_actual == vals_expected); + CHECK(vals_actual == vals_expected); + } } - } - SECTION("With exception") { - constexpr int n = 9; - auto snd = ex::transfer_just(sch) - | ex::bulk(n, [](int idx) { throw std::runtime_error("bulk"); }); + SECTION("With exception") { + constexpr int n = 9; + auto snd = ex::transfer_just(sch) + | ex::bulk(n, [](int idx) { throw std::runtime_error("bulk"); }); - CHECK_THROWS_AS(stdexec::sync_wait(std::move(snd)), std::runtime_error); - } + CHECK_THROWS_AS(stdexec::sync_wait(std::move(snd)), std::runtime_error); + } - SECTION("With concurrent enqueueing") { - constexpr int n = 4; - std::vector counters_1(n, 0); - std::vector counters_2(n, 0); + SECTION("With concurrent enqueueing") { + constexpr int n = 4; + std::vector counters_1(n, 0); + std::vector counters_2(n, 0); - stdexec::sender auto snd = stdexec::when_all( - stdexec::schedule(sch) | stdexec::bulk(n, [&](int id) { counters_1[id]++; }), - stdexec::schedule(sch) | stdexec::bulk(n, [&](int id) { counters_2[id]++; })); + stdexec::sender auto snd = stdexec::when_all( + stdexec::schedule(sch) | stdexec::bulk(n, [&](int id) { counters_1[id]++; }), + stdexec::schedule(sch) | stdexec::bulk(n, [&](int id) { counters_2[id]++; })); - stdexec::sync_wait(std::move(snd)); + stdexec::sync_wait(std::move(snd)); - CHECK(std::count(counters_1.begin(), counters_1.end(), 1) == n); - CHECK(std::count(counters_2.begin(), counters_2.end(), 1) == n); + CHECK(std::count(counters_1.begin(), counters_1.end(), 1) == n); + CHECK(std::count(counters_2.begin(), counters_2.end(), 1) == n); + } } -} -TEST_CASE("eager customization of bulk works with static thread pool", "[adaptors][bulk]") { - exec::static_thread_pool pool{4}; - ex::scheduler auto sch = pool.get_scheduler(); + TEST_CASE("eager customization of bulk works with static thread pool", "[adaptors][bulk]") { + exec::static_thread_pool pool{4}; + ex::scheduler auto sch = pool.get_scheduler(); - SECTION("Without values in the set_value channel") { - std::vector tids(42); + SECTION("Without values in the set_value channel") { + std::vector tids(42); - auto fun = [&tids](int idx) { - tids[idx] = std::this_thread::get_id(); - std::this_thread::sleep_for(std::chrono::milliseconds{10}); - }; + auto fun = [&tids](int idx) { + tids[idx] = std::this_thread::get_id(); + std::this_thread::sleep_for(std::chrono::milliseconds{10}); + }; - auto snd = ex::just() // - | ex::transfer(sch) | ex::bulk((int) tids.size(), fun); - CHECK(&snd.pool_ == &pool); - stdexec::sync_wait(std::move(snd)); + auto snd = ex::just() // + | ex::transfer(sch) | ex::bulk((int) tids.size(), fun); + CHECK(&snd.pool_ == &pool); + stdexec::sync_wait(std::move(snd)); - // All the work should not have run on the same thread - const std::size_t actual = std::count(tids.begin(), tids.end(), tids[0]); - const std::size_t wrong = tids.size(); + // All the work should not have run on the same thread + const std::size_t actual = std::count(tids.begin(), tids.end(), tids[0]); + const std::size_t wrong = tids.size(); - CHECK(actual != wrong); + CHECK(actual != wrong); + } } -} -TEST_CASE("lazy customization of bulk works with static thread pool", "[adaptors][bulk]") { - exec::static_thread_pool pool{4}; - ex::scheduler auto sch = pool.get_scheduler(); + TEST_CASE("lazy customization of bulk works with static thread pool", "[adaptors][bulk]") { + exec::static_thread_pool pool{4}; + ex::scheduler auto sch = pool.get_scheduler(); - SECTION("Without values in the set_value channel") { - std::vector tids(42); + SECTION("Without values in the set_value channel") { + std::vector tids(42); - auto fun = [&tids](int idx) { - tids[idx] = std::this_thread::get_id(); - std::this_thread::sleep_for(std::chrono::milliseconds{10}); - }; + auto fun = [&tids](int idx) { + tids[idx] = std::this_thread::get_id(); + std::this_thread::sleep_for(std::chrono::milliseconds{10}); + }; - auto snd = ex::just() // - | ex::bulk((int) tids.size(), fun); - stdexec::sync_wait(stdexec::on(sch, std::move(snd))); + auto snd = ex::just() // + | ex::bulk((int) tids.size(), fun); + stdexec::sync_wait(stdexec::on(sch, std::move(snd))); - // All the work should not have run on the same thread - const std::size_t actual = std::count(tids.begin(), tids.end(), tids[0]); - const std::size_t wrong = tids.size(); + // All the work should not have run on the same thread + const std::size_t actual = std::count(tids.begin(), tids.end(), tids[0]); + const std::size_t wrong = tids.size(); - CHECK(actual != wrong); + CHECK(actual != wrong); + } } } diff --git a/test/stdexec/algos/adaptors/test_ensure_started.cpp b/test/stdexec/algos/adaptors/test_ensure_started.cpp index f219476ab..06d687d3e 100644 --- a/test/stdexec/algos/adaptors/test_ensure_started.cpp +++ b/test/stdexec/algos/adaptors/test_ensure_started.cpp @@ -27,282 +27,285 @@ namespace ex = stdexec; using exec::async_scope; -TEST_CASE("ensure_started returns a sender", "[adaptors][ensure_started]") { - auto snd = ex::ensure_started(ex::just(19)); - static_assert(ex::sender); - (void) snd; -} - -static const auto env = exec::make_env(exec::with(ex::get_scheduler, inline_scheduler{})); +namespace { -TEST_CASE("ensure_started with environment returns a sender", "[adaptors][ensure_started]") { - auto snd = ex::ensure_started(ex::just(19), env); - static_assert(ex::sender_in); - (void) snd; -} + TEST_CASE("ensure_started returns a sender", "[adaptors][ensure_started]") { + auto snd = ex::ensure_started(ex::just(19)); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE("ensure_started void value early", "[adaptors][ensure_started]") { - bool called{false}; - auto snd1 = ex::just() | ex::then([&] { called = true; }); - CHECK_FALSE(called); - auto snd = ex::ensure_started(std::move(snd1)); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); -} + static const auto env = exec::make_env(exec::with(ex::get_scheduler, inline_scheduler{})); -TEST_CASE("ensure_started void value late", "[adaptors][ensure_started]") { - impulse_scheduler sch; - bool called{false}; - auto snd1 = ex::on(sch, ex::just()) | ex::then([&] { called = true; }); - CHECK_FALSE(called); - auto snd = ex::ensure_started(std::move(snd1)); - CHECK_FALSE(called); - // execute the next scheduled item - sch.start_next(); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); -} + TEST_CASE("ensure_started with environment returns a sender", "[adaptors][ensure_started]") { + auto snd = ex::ensure_started(ex::just(19), env); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("ensure_started single value early", "[adaptors][ensure_started]") { - bool called{false}; - auto snd1 = ex::just() // - | ex::then([&] { - called = true; - return 42; - }); - CHECK_FALSE(called); - auto snd = ex::ensure_started(std::move(snd1)); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_value_receiver{42}); - ex::start(op); -} + TEST_CASE("ensure_started void value early", "[adaptors][ensure_started]") { + bool called{false}; + auto snd1 = ex::just() | ex::then([&] { called = true; }); + CHECK_FALSE(called); + auto snd = ex::ensure_started(std::move(snd1)); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + } -TEST_CASE("ensure_started single value late", "[adaptors][ensure_started]") { - impulse_scheduler sch; - bool called{false}; - auto snd1 = ex::on(sch, ex::just()) // - | ex::then([&] { - called = true; - return 42; - }); - CHECK_FALSE(called); - auto snd = ex::ensure_started(std::move(snd1)); - CHECK_FALSE(called); - // execute the next scheduled item - sch.start_next(); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_value_receiver{42}); - ex::start(op); -} + TEST_CASE("ensure_started void value late", "[adaptors][ensure_started]") { + impulse_scheduler sch; + bool called{false}; + auto snd1 = ex::on(sch, ex::just()) | ex::then([&] { called = true; }); + CHECK_FALSE(called); + auto snd = ex::ensure_started(std::move(snd1)); + CHECK_FALSE(called); + // execute the next scheduled item + sch.start_next(); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + } -TEST_CASE("ensure_started multiple values early", "[adaptors][ensure_started]") { - bool called{false}; - auto snd1 = ex::let_value(ex::just(), [&] { - called = true; - return ex::just(42, movable{56}); - }); - CHECK_FALSE(called); - auto snd = ex::ensure_started(std::move(snd1)); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_value_receiver{42, movable{56}}); - ex::start(op); -} + TEST_CASE("ensure_started single value early", "[adaptors][ensure_started]") { + bool called{false}; + auto snd1 = ex::just() // + | ex::then([&] { + called = true; + return 42; + }); + CHECK_FALSE(called); + auto snd = ex::ensure_started(std::move(snd1)); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_value_receiver{42}); + ex::start(op); + } -TEST_CASE("ensure_started multiple values late", "[adaptors][ensure_started]") { - impulse_scheduler sch; - bool called{false}; - auto snd1 = ex::let_value(ex::on(sch, ex::just()), [&] { - called = true; - return ex::just(42, movable{56}); - }); - CHECK_FALSE(called); - auto snd = ex::ensure_started(std::move(snd1)); - CHECK_FALSE(called); - // execute the next scheduled item - sch.start_next(); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_value_receiver{42, movable{56}}); - ex::start(op); -} + TEST_CASE("ensure_started single value late", "[adaptors][ensure_started]") { + impulse_scheduler sch; + bool called{false}; + auto snd1 = ex::on(sch, ex::just()) // + | ex::then([&] { + called = true; + return 42; + }); + CHECK_FALSE(called); + auto snd = ex::ensure_started(std::move(snd1)); + CHECK_FALSE(called); + // execute the next scheduled item + sch.start_next(); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_value_receiver{42}); + ex::start(op); + } -TEST_CASE("ensure_started error early", "[adaptors][ensure_started]") { - bool called{false}; - auto snd1 = ex::let_value(ex::just(), [&] { - called = true; - return ex::just_error(42); - }); - CHECK_FALSE(called); - auto snd = ex::ensure_started(std::move(snd1)); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_error_receiver{42}); - ex::start(op); -} + TEST_CASE("ensure_started multiple values early", "[adaptors][ensure_started]") { + bool called{false}; + auto snd1 = ex::let_value(ex::just(), [&] { + called = true; + return ex::just(42, movable{56}); + }); + CHECK_FALSE(called); + auto snd = ex::ensure_started(std::move(snd1)); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_value_receiver{42, movable{56}}); + ex::start(op); + } -TEST_CASE("ensure_started error late", "[adaptors][ensure_started]") { - impulse_scheduler sch; - bool called{false}; - auto snd1 = ex::let_value(ex::on(sch, ex::just()), [&] { - called = true; - return ex::just_error(42); - }); - CHECK_FALSE(called); - auto snd = ex::ensure_started(std::move(snd1)); - CHECK_FALSE(called); - // execute the next scheduled item - sch.start_next(); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_error_receiver{42}); - ex::start(op); -} + TEST_CASE("ensure_started multiple values late", "[adaptors][ensure_started]") { + impulse_scheduler sch; + bool called{false}; + auto snd1 = ex::let_value(ex::on(sch, ex::just()), [&] { + called = true; + return ex::just(42, movable{56}); + }); + CHECK_FALSE(called); + auto snd = ex::ensure_started(std::move(snd1)); + CHECK_FALSE(called); + // execute the next scheduled item + sch.start_next(); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_value_receiver{42, movable{56}}); + ex::start(op); + } -TEST_CASE("ensure_started stopped early", "[adaptors][ensure_started]") { - bool called{false}; - auto snd1 = ex::let_value(ex::just(), [&] { - called = true; - return ex::just_stopped(); - }); - CHECK_FALSE(called); - auto snd = ex::ensure_started(std::move(snd1)); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); -} + TEST_CASE("ensure_started error early", "[adaptors][ensure_started]") { + bool called{false}; + auto snd1 = ex::let_value(ex::just(), [&] { + called = true; + return ex::just_error(42); + }); + CHECK_FALSE(called); + auto snd = ex::ensure_started(std::move(snd1)); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_error_receiver{42}); + ex::start(op); + } -TEST_CASE("ensure_started stopped late", "[adaptors][ensure_started]") { - impulse_scheduler sch; - bool called{false}; - auto snd1 = ex::let_value(ex::on(sch, ex::just()), [&] { - called = true; - return ex::just_stopped(); - }); - CHECK_FALSE(called); - auto snd = ex::ensure_started(std::move(snd1)); - CHECK_FALSE(called); - // execute the next scheduled item - sch.start_next(); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); -} + TEST_CASE("ensure_started error late", "[adaptors][ensure_started]") { + impulse_scheduler sch; + bool called{false}; + auto snd1 = ex::let_value(ex::on(sch, ex::just()), [&] { + called = true; + return ex::just_error(42); + }); + CHECK_FALSE(called); + auto snd = ex::ensure_started(std::move(snd1)); + CHECK_FALSE(called); + // execute the next scheduled item + sch.start_next(); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_error_receiver{42}); + ex::start(op); + } -TEST_CASE( - "stopping ensure_started before the source completes calls set_stopped", - "[adaptors][ensure_started]") { - stdexec::in_place_stop_source stop_source; - impulse_scheduler sch; - bool called{false}; - auto snd = ex::on(sch, ex::just(19)) - | exec::write(exec::with(ex::get_stop_token, stop_source.get_token())) - | ex::ensure_started(); - auto op = ex::connect(std::move(snd), expect_stopped_receiver_ex{called}); - ex::start(op); - // request stop before the source yields a value - stop_source.request_stop(); - // make the source yield the value - sch.start_next(); - CHECK(called); -} + TEST_CASE("ensure_started stopped early", "[adaptors][ensure_started]") { + bool called{false}; + auto snd1 = ex::let_value(ex::just(), [&] { + called = true; + return ex::just_stopped(); + }); + CHECK_FALSE(called); + auto snd = ex::ensure_started(std::move(snd1)); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + } -TEST_CASE( - "stopping ensure_started before the lazy opstate is started calls set_stopped", - "[adaptors][ensure_started]") { - stdexec::in_place_stop_source stop_source; - impulse_scheduler sch; - int count = 0; - bool called{false}; - auto snd = ex::let_value( - ex::just() | ex::then([&] { ++count; }), [=] { return ex::on(sch, ex::just(19)); }) - | exec::write(exec::with(ex::get_stop_token, stop_source.get_token())) - | ex::ensure_started(); - CHECK(count == 1); - auto op = ex::connect(std::move(snd), expect_stopped_receiver_ex{called}); - // request stop before the source yields a value - stop_source.request_stop(); - ex::start(op); - // make the source yield the value - sch.start_next(); - CHECK(called); -} + TEST_CASE("ensure_started stopped late", "[adaptors][ensure_started]") { + impulse_scheduler sch; + bool called{false}; + auto snd1 = ex::let_value(ex::on(sch, ex::just()), [&] { + called = true; + return ex::just_stopped(); + }); + CHECK_FALSE(called); + auto snd = ex::ensure_started(std::move(snd1)); + CHECK_FALSE(called); + // execute the next scheduled item + sch.start_next(); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + } -TEST_CASE( - "stopping ensure_started after the task has already completed doesn't change the result", - "[adaptors][ensure_started]") { - stdexec::in_place_stop_source stop_source; - int count = 0; - auto snd = ex::just() // - | ex::then([&] { - ++count; - return 42; - }) - | exec::write(exec::with(ex::get_stop_token, stop_source.get_token())) - | ex::ensure_started(); - CHECK(count == 1); - auto op = ex::connect(std::move(snd), expect_value_receiver{42}); - // request stop before the source yields a value - stop_source.request_stop(); - ex::start(op); -} + TEST_CASE( + "stopping ensure_started before the source completes calls set_stopped", + "[adaptors][ensure_started]") { + stdexec::in_place_stop_source stop_source; + impulse_scheduler sch; + bool called{false}; + auto snd = ex::on(sch, ex::just(19)) + | exec::write(exec::with(ex::get_stop_token, stop_source.get_token())) + | ex::ensure_started(); + auto op = ex::connect(std::move(snd), expect_stopped_receiver_ex{called}); + ex::start(op); + // request stop before the source yields a value + stop_source.request_stop(); + // make the source yield the value + sch.start_next(); + CHECK(called); + } -TEST_CASE( - "Dropping the sender without connecting it calls set_stopped", - "[adaptors][ensure_started]") { - impulse_scheduler sch; - bool called = false; - { - auto snd = ex::on(sch, ex::just()) | ex::upon_stopped([&] { called = true; }) + TEST_CASE( + "stopping ensure_started before the lazy opstate is started calls set_stopped", + "[adaptors][ensure_started]") { + stdexec::in_place_stop_source stop_source; + impulse_scheduler sch; + int count = 0; + bool called{false}; + auto snd = ex::let_value( + ex::just() | ex::then([&] { ++count; }), [=] { return ex::on(sch, ex::just(19)); }) + | exec::write(exec::with(ex::get_stop_token, stop_source.get_token())) | ex::ensure_started(); + CHECK(count == 1); + auto op = ex::connect(std::move(snd), expect_stopped_receiver_ex{called}); + // request stop before the source yields a value + stop_source.request_stop(); + ex::start(op); + // make the source yield the value + sch.start_next(); + CHECK(called); } - // make the source yield the value - sch.start_next(); - CHECK(called); -} -TEST_CASE( - "Dropping the opstate without starting it calls set_stopped", - "[adaptors][ensure_started]") { - impulse_scheduler sch; - int state = -1; - bool called = false; - { - auto snd = ex::on(sch, ex::just()) | ex::upon_stopped([&] { called = true; }) + TEST_CASE( + "stopping ensure_started after the task has already completed doesn't change the result", + "[adaptors][ensure_started]") { + stdexec::in_place_stop_source stop_source; + int count = 0; + auto snd = ex::just() // + | ex::then([&] { + ++count; + return 42; + }) + | exec::write(exec::with(ex::get_stop_token, stop_source.get_token())) | ex::ensure_started(); - auto op = ex::connect(std::move(snd), logging_receiver{state}); + CHECK(count == 1); + auto op = ex::connect(std::move(snd), expect_value_receiver{42}); + // request stop before the source yields a value + stop_source.request_stop(); + ex::start(op); } - // make the source yield the value - sch.start_next(); - CHECK(called); - // make sure the logging_receiver was never called - CHECK(state == -1); -} -TEST_CASE("Repeated ensure_started compiles", "[adaptors][ensure_started]") { - bool called{false}; - auto snd1 = ex::just() | ex::then([&] { called = true; }); - CHECK_FALSE(called); - auto snd2 = ex::ensure_started(std::move(snd1)); - auto snd = ex::ensure_started(std::move(snd2)); - STATIC_REQUIRE(ex::same_as); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); -} + TEST_CASE( + "Dropping the sender without connecting it calls set_stopped", + "[adaptors][ensure_started]") { + impulse_scheduler sch; + bool called = false; + { + auto snd = ex::on(sch, ex::just()) | ex::upon_stopped([&] { called = true; }) + | ex::ensure_started(); + } + // make the source yield the value + sch.start_next(); + CHECK(called); + } -TEST_CASE("ensure_started with move only input sender", "[adaptors][ensure_started]") { - bool called{false}; - auto snd1 = ex::just(movable(42)) | ex::then([&](movable &&) { called = true; }); - CHECK_FALSE(called); - auto snd = ex::ensure_started(std::move(snd1)); - CHECK(called); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); -} + TEST_CASE( + "Dropping the opstate without starting it calls set_stopped", + "[adaptors][ensure_started]") { + impulse_scheduler sch; + int state = -1; + bool called = false; + { + auto snd = ex::on(sch, ex::just()) | ex::upon_stopped([&] { called = true; }) + | ex::ensure_started(); + auto op = ex::connect(std::move(snd), logging_receiver{state}); + } + // make the source yield the value + sch.start_next(); + CHECK(called); + // make sure the logging_receiver was never called + CHECK(state == -1); + } -TEST_CASE( - "Repeated ensure_started with operator| does not return reference to temporary", - "[adaptors][ensure_started]") { - auto snd = ex::ensure_started(ex::just()) | ex::ensure_started(); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); + TEST_CASE("Repeated ensure_started compiles", "[adaptors][ensure_started]") { + bool called{false}; + auto snd1 = ex::just() | ex::then([&] { called = true; }); + CHECK_FALSE(called); + auto snd2 = ex::ensure_started(std::move(snd1)); + auto snd = ex::ensure_started(std::move(snd2)); + STATIC_REQUIRE(ex::same_as); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + } + + TEST_CASE("ensure_started with move only input sender", "[adaptors][ensure_started]") { + bool called{false}; + auto snd1 = ex::just(movable(42)) | ex::then([&](movable &&) { called = true; }); + CHECK_FALSE(called); + auto snd = ex::ensure_started(std::move(snd1)); + CHECK(called); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + } + + TEST_CASE( + "Repeated ensure_started with operator| does not return reference to temporary", + "[adaptors][ensure_started]") { + auto snd = ex::ensure_started(ex::just()) | ex::ensure_started(); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + } } diff --git a/test/stdexec/algos/adaptors/test_into_variant.cpp b/test/stdexec/algos/adaptors/test_into_variant.cpp index 38c203184..4e71526e9 100644 --- a/test/stdexec/algos/adaptors/test_into_variant.cpp +++ b/test/stdexec/algos/adaptors/test_into_variant.cpp @@ -23,120 +23,125 @@ namespace ex = stdexec; -TEST_CASE("into_variant returns a sender", "[adaptors][into_variant]") { - auto snd = ex::into_variant(ex::just(11)); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("into_variant with environment returns a sender", "[adaptors][into_variant]") { - auto snd = ex::into_variant(ex::just(11)); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("into_variant simple example", "[adaptors][into_variant]") { - bool called{false}; - auto snd = ex::into_variant(ex::just(11)) // - | ex::then([&](std::variant> x) { - called = true; - CHECK(std::get<0>(std::get<0>(x)) == 11); - }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); - CHECK(called); -} - -TEST_CASE("into_variant returning void can we waited on", "[adaptors][into_variant]") { - ex::sender auto snd = ex::just(11) | ex::into_variant(); - wait_for_value(std::move(snd), std::variant>{11}); -} - -TEST_CASE( - "into_variant with senders that sends multiple values at once", - "[adaptors][into_variant]") { - ex::sender auto snd = ex::just(3, 0.1415) | ex::into_variant(); - wait_for_value(std::move(snd), std::variant>{std::make_tuple(3, 0.1415)}); -} - -TEST_CASE("into_variant with senders that have multiple alternatives", "[adaptors][into_variant]") { - ex::sender auto in_snd = - fallible_just{13} // - | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"err"}); }); - check_val_types, type_array>>(in_snd); - - ex::sender auto snd = std::move(in_snd) | ex::into_variant(); - wait_for_value( - std::move(snd), std::variant, std::tuple>{std::make_tuple(13)}); -} - -TEST_CASE("into_variant can be used with just_error", "[adaptors][into_variant]") { - ex::sender auto snd = ex::just_error(std::string{"err"}) // - | ex::into_variant(); - auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"err"}}); - ex::start(op); -} - -TEST_CASE("into_variant can be used with just_stopped", "[adaptors][into_variant]") { - ex::sender auto snd = ex::just_stopped() | ex::into_variant(); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); -} - -TEST_CASE("into_variant forwards errors", "[adaptors][into_variant]") { - error_scheduler sched; - ex::sender auto snd = ex::transfer_just(sched, 13) | ex::into_variant(); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); -} - -TEST_CASE("into_variant forwards cancellation", "[adaptors][into_variant]") { - stopped_scheduler sched; - ex::sender auto snd = ex::transfer_just(sched, 13) | ex::into_variant(); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); -} - -TEST_CASE( - "into_variant has the values_type corresponding to the given values", - "[adaptors][into_variant]") { - check_val_types>>>>( - ex::just() | ex::into_variant()); - check_val_types>>>>( - ex::just(23) | ex::into_variant()); - check_val_types>>>>( - ex::just(3.1415) | ex::into_variant()); - - check_val_types>>>>( - ex::just(3, 0.1415) | ex::into_variant()); - - check_val_types, std::tuple>>>>( - fallible_just{13} // - | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"err"}); }) // - // sender here can send either `int` or `std::string` - | ex::into_variant()); -} - -TEST_CASE("into_variant keeps error_types from input sender", "[adaptors][into_variant]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - - check_err_types>( // - ex::transfer_just(sched1) | ex::into_variant()); - check_err_types>( // - ex::transfer_just(sched2) | ex::into_variant()); - check_err_types>( // - ex::just_error(-1) | ex::into_variant()); -} - -TEST_CASE("into_variant keeps sends_stopped from input sender", "[adaptors][into_variant]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - - check_sends_stopped( // - ex::transfer_just(sched1) | ex::into_variant()); - check_sends_stopped( // - ex::transfer_just(sched2) | ex::into_variant()); - check_sends_stopped( // - ex::just_stopped() | ex::into_variant()); +namespace { + TEST_CASE("into_variant returns a sender", "[adaptors][into_variant]") { + auto snd = ex::into_variant(ex::just(11)); + static_assert(ex::sender); + (void) snd; + } + + TEST_CASE("into_variant with environment returns a sender", "[adaptors][into_variant]") { + auto snd = ex::into_variant(ex::just(11)); + static_assert(ex::sender_in); + (void) snd; + } + + TEST_CASE("into_variant simple example", "[adaptors][into_variant]") { + bool called{false}; + auto snd = ex::into_variant(ex::just(11)) // + | ex::then([&](std::variant> x) { + called = true; + CHECK(std::get<0>(std::get<0>(x)) == 11); + }); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + CHECK(called); + } + + TEST_CASE("into_variant returning void can we waited on", "[adaptors][into_variant]") { + ex::sender auto snd = ex::just(11) | ex::into_variant(); + wait_for_value(std::move(snd), std::variant>{11}); + } + + TEST_CASE( + "into_variant with senders that sends multiple values at once", + "[adaptors][into_variant]") { + ex::sender auto snd = ex::just(3, 0.1415) | ex::into_variant(); + wait_for_value( + std::move(snd), std::variant>{std::make_tuple(3, 0.1415)}); + } + + TEST_CASE( + "into_variant with senders that have multiple alternatives", + "[adaptors][into_variant]") { + ex::sender auto in_snd = + fallible_just{13} // + | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"err"}); }); + check_val_types, type_array>>(in_snd); + + ex::sender auto snd = std::move(in_snd) | ex::into_variant(); + wait_for_value( + std::move(snd), std::variant, std::tuple>{std::make_tuple(13)}); + } + + TEST_CASE("into_variant can be used with just_error", "[adaptors][into_variant]") { + ex::sender auto snd = ex::just_error(std::string{"err"}) // + | ex::into_variant(); + auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"err"}}); + ex::start(op); + } + + TEST_CASE("into_variant can be used with just_stopped", "[adaptors][into_variant]") { + ex::sender auto snd = ex::just_stopped() | ex::into_variant(); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + } + + TEST_CASE("into_variant forwards errors", "[adaptors][into_variant]") { + error_scheduler sched; + ex::sender auto snd = ex::transfer_just(sched, 13) | ex::into_variant(); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + } + + TEST_CASE("into_variant forwards cancellation", "[adaptors][into_variant]") { + stopped_scheduler sched; + ex::sender auto snd = ex::transfer_just(sched, 13) | ex::into_variant(); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + } + + TEST_CASE( + "into_variant has the values_type corresponding to the given values", + "[adaptors][into_variant]") { + check_val_types>>>>( + ex::just() | ex::into_variant()); + check_val_types>>>>( + ex::just(23) | ex::into_variant()); + check_val_types>>>>( + ex::just(3.1415) | ex::into_variant()); + + check_val_types>>>>( + ex::just(3, 0.1415) | ex::into_variant()); + + check_val_types, std::tuple>>>>( + fallible_just{13} // + | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"err"}); }) // + // sender here can send either `int` or `std::string` + | ex::into_variant()); + } + + TEST_CASE("into_variant keeps error_types from input sender", "[adaptors][into_variant]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + + check_err_types>( // + ex::transfer_just(sched1) | ex::into_variant()); + check_err_types>( // + ex::transfer_just(sched2) | ex::into_variant()); + check_err_types>( // + ex::just_error(-1) | ex::into_variant()); + } + + TEST_CASE("into_variant keeps sends_stopped from input sender", "[adaptors][into_variant]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + + check_sends_stopped( // + ex::transfer_just(sched1) | ex::into_variant()); + check_sends_stopped( // + ex::transfer_just(sched2) | ex::into_variant()); + check_sends_stopped( // + ex::just_stopped() | ex::into_variant()); + } } diff --git a/test/stdexec/algos/adaptors/test_let_error.cpp b/test/stdexec/algos/adaptors/test_let_error.cpp index 6fe073d61..86488a26d 100644 --- a/test/stdexec/algos/adaptors/test_let_error.cpp +++ b/test/stdexec/algos/adaptors/test_let_error.cpp @@ -28,333 +28,336 @@ namespace ex = stdexec; using namespace std::chrono_literals; -TEST_CASE("let_error returns a sender", "[adaptors][let_error]") { - auto snd = ex::let_error(ex::just(), [](std::exception_ptr) { return ex::just(); }); - static_assert(ex::sender); - (void) snd; -} +namespace { -TEST_CASE("let_error with environment returns a sender", "[adaptors][let_error]") { - auto snd = ex::let_error(ex::just(), [](std::exception_ptr) { return ex::just(); }); - static_assert(ex::sender_in); - (void) snd; -} + TEST_CASE("let_error returns a sender", "[adaptors][let_error]") { + auto snd = ex::let_error(ex::just(), [](std::exception_ptr) { return ex::just(); }); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE("let_error simple example", "[adaptors][let_error]") { - bool called{false}; - auto snd = ex::let_error(ex::just_error(std::exception_ptr{}), [&](std::exception_ptr) { - called = true; - return ex::just(); - }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); - // The receiver checks that it's called - // we also check that the function was invoked - CHECK(called); -} + TEST_CASE("let_error with environment returns a sender", "[adaptors][let_error]") { + auto snd = ex::let_error(ex::just(), [](std::exception_ptr) { return ex::just(); }); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("let_error simple example reference", "[adaptors][let_error]") { - bool called{false}; - auto snd = ex::let_error( - ex::split(ex::just_error(std::exception_ptr{})), [&](std::exception_ptr) { + TEST_CASE("let_error simple example", "[adaptors][let_error]") { + bool called{false}; + auto snd = ex::let_error(ex::just_error(std::exception_ptr{}), [&](std::exception_ptr) { called = true; return ex::just(); }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); - // The receiver checks that it's called - // we also check that the function was invoked - CHECK(called); -} - -TEST_CASE("let_error can be piped", "[adaptors][let_error]") { - ex::sender auto snd = ex::just() | ex::let_error([](std::exception_ptr) { return ex::just(); }); - (void) snd; -} - -TEST_CASE( - "let_error returning void can be waited on (error annihilation)", - "[adaptors][let_error]") { - ex::sender auto snd = ex::just_error(std::exception_ptr{}) - | ex::let_error([](std::exception_ptr) { return ex::just(); }); - stdexec::sync_wait(std::move(snd)); -} - -TEST_CASE("let_error can be used to produce values (error to value)", "[adaptors][let_error]") { - ex::sender auto snd = - ex::just() // - | ex::then([] { - throw std::logic_error{"error description"}; - return std::string{"ok"}; - }) // - | ex::let_error([](std::exception_ptr eptr) { - try { - std::rethrow_exception(eptr); - } catch (const std::exception& e) { - return ex::just(std::string{e.what()}); - } - }); - wait_for_value(std::move(snd), std::string{"error description"}); - (void) snd; -} - -TEST_CASE("let_error can be used to transform errors", "[adaptors][let_error]") { - ex::sender auto snd = ex::just_error(1) // - | ex::let_error([](int error_code) { - char buf[20]; - std::snprintf(buf, 20, "%d", error_code); - throw std::logic_error(buf); - return ex::just_error(std::exception_ptr{}); // not reached - }); - - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); -} - -TEST_CASE("let_error can throw, and yield a different error type", "[adaptors][let_error]") { - auto snd = ex::just_error(13) // - | ex::let_error([](int x) { - if (x % 2 == 0) - throw std::logic_error{"err"}; - return ex::just_error(x); - }); - auto op = ex::connect(std::move(snd), expect_error_receiver{13}); - ex::start(op); -} - -TEST_CASE("let_error can be used with just_stopped", "[adaptors][let_error]") { - ex::sender auto snd = ex::just_stopped() // - | ex::let_error([](std::exception_ptr) { return ex::just(17); }); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); -} + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + // The receiver checks that it's called + // we also check that the function was invoked + CHECK(called); + } -TEST_CASE("let_error function is not called on regular flow", "[adaptors][let_error]") { - bool called{false}; - error_scheduler sched; - ex::sender auto snd = - ex::just() // - | ex::then([] { return 13; }) // - | ex::let_error([&](std::exception_ptr) { + TEST_CASE("let_error simple example reference", "[adaptors][let_error]") { + bool called{false}; + auto snd = ex::let_error( + ex::split(ex::just_error(std::exception_ptr{})), [&](std::exception_ptr) { called = true; - return ex::just(0); + return ex::just(); }); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - CHECK_FALSE(called); -} - -TEST_CASE("let_error function is not called when cancelled", "[adaptors][let_error]") { - bool called{false}; - stopped_scheduler sched; - ex::sender auto snd = ex::transfer_just(sched, 13) // - | ex::let_error([&](std::exception_ptr) { - called = true; - return ex::just(0); - }); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); - CHECK_FALSE(called); -} - -// Type that sets into a received boolean when the dtor is called -struct my_type { - bool* p_called_{nullptr}; - - explicit my_type(bool* p_called) - : p_called_(p_called) { + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + // The receiver checks that it's called + // we also check that the function was invoked + CHECK(called); } - my_type(my_type&& rhs) - : p_called_(rhs.p_called_) { - rhs.p_called_ = nullptr; + TEST_CASE("let_error can be piped", "[adaptors][let_error]") { + ex::sender auto snd = ex::just() | ex::let_error([](std::exception_ptr) { return ex::just(); }); + (void) snd; } - my_type& operator=(my_type&& rhs) { - if (p_called_) - *p_called_ = true; - p_called_ = rhs.p_called_; - rhs.p_called_ = nullptr; - return *this; + TEST_CASE( + "let_error returning void can be waited on (error annihilation)", + "[adaptors][let_error]") { + ex::sender auto snd = ex::just_error(std::exception_ptr{}) + | ex::let_error([](std::exception_ptr) { return ex::just(); }); + stdexec::sync_wait(std::move(snd)); } - ~my_type() { - if (p_called_) - *p_called_ = true; + TEST_CASE("let_error can be used to produce values (error to value)", "[adaptors][let_error]") { + ex::sender auto snd = + ex::just() // + | ex::then([] { + throw std::logic_error{"error description"}; + return std::string{"ok"}; + }) // + | ex::let_error([](std::exception_ptr eptr) { + try { + std::rethrow_exception(eptr); + } catch (const std::exception& e) { + return ex::just(std::string{e.what()}); + } + }); + wait_for_value(std::move(snd), std::string{"error description"}); + (void) snd; } -}; -TEST_CASE("let_error of just_error with custom type", "[adaptors][let_error]") { - bool param_destructed{false}; - ex::sender auto snd = ex::just_error(my_type(¶m_destructed)) // - | ex::let_error([&](const my_type& obj) { return ex::just(13); }); + TEST_CASE("let_error can be used to transform errors", "[adaptors][let_error]") { + ex::sender auto snd = ex::just_error(1) // + | ex::let_error([](int error_code) { + char buf[20]; + std::snprintf(buf, 20, "%d", error_code); + throw std::logic_error(buf); + return ex::just_error(std::exception_ptr{}); // not reached + }); - { - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - CHECK_FALSE(param_destructed); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); ex::start(op); - CHECK_FALSE(param_destructed); } - // the parameter is destructed once the operation_state object is destructed - CHECK(param_destructed); -} -TEST_CASE( - "let_error exposes a parameter that is destructed when the main operation is destructed ", - "[adaptors][let_error]") { - bool param_destructed{false}; - bool fun_called{false}; - impulse_scheduler sched; - - ex::sender auto s1 = ex::just_error(my_type(¶m_destructed)); - ex::sender auto snd = ex::just_error(my_type(¶m_destructed)) // - | ex::let_error([&](const my_type& obj) { - CHECK_FALSE(param_destructed); - fun_called = true; - return ex::transfer_just(sched, 13); - }); - int res{0}; - { - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); + TEST_CASE("let_error can throw, and yield a different error type", "[adaptors][let_error]") { + auto snd = ex::just_error(13) // + | ex::let_error([](int x) { + if (x % 2 == 0) + throw std::logic_error{"err"}; + return ex::just_error(x); + }); + auto op = ex::connect(std::move(snd), expect_error_receiver{13}); ex::start(op); - // The function is called immediately after starting the operation - CHECK(fun_called); - // As the returned sender didn't complete yet, the parameter must still be alive - CHECK_FALSE(param_destructed); - CHECK(res == 0); - - // Now, tell the scheduler to execute the final operation - sched.start_next(); + } - // As the main operation is still valid, the parameter is not yet destructed - CHECK_FALSE(param_destructed); + TEST_CASE("let_error can be used with just_stopped", "[adaptors][let_error]") { + ex::sender auto snd = ex::just_stopped() // + | ex::let_error([](std::exception_ptr) { return ex::just(17); }); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); } - // At this point everything can be destructed - CHECK(param_destructed); - CHECK(res == 13); -} + TEST_CASE("let_error function is not called on regular flow", "[adaptors][let_error]") { + bool called{false}; + error_scheduler sched; + ex::sender auto snd = + ex::just() // + | ex::then([] { return 13; }) // + | ex::let_error([&](std::exception_ptr) { + called = true; + return ex::just(0); + }); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + CHECK_FALSE(called); + } -struct int_err_transform { - using my_res_t = decltype(fallible_just{0}); + TEST_CASE("let_error function is not called when cancelled", "[adaptors][let_error]") { + bool called{false}; + stopped_scheduler sched; + ex::sender auto snd = ex::transfer_just(sched, 13) // + | ex::let_error([&](std::exception_ptr) { + called = true; + return ex::just(0); + }); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + CHECK_FALSE(called); + } - my_res_t operator()(std::exception_ptr ep) const { - std::rethrow_exception(ep); - return {}; + // Type that sets into a received boolean when the dtor is called + struct my_type { + bool* p_called_{nullptr}; + + explicit my_type(bool* p_called) + : p_called_(p_called) { + } + + my_type(my_type&& rhs) + : p_called_(rhs.p_called_) { + rhs.p_called_ = nullptr; + } + + my_type& operator=(my_type&& rhs) { + if (p_called_) + *p_called_ = true; + p_called_ = rhs.p_called_; + rhs.p_called_ = nullptr; + return *this; + } + + ~my_type() { + if (p_called_) + *p_called_ = true; + } + }; + + TEST_CASE("let_error of just_error with custom type", "[adaptors][let_error]") { + bool param_destructed{false}; + ex::sender auto snd = ex::just_error(my_type(¶m_destructed)) // + | ex::let_error([&](const my_type& obj) { return ex::just(13); }); + + { + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + CHECK_FALSE(param_destructed); + ex::start(op); + CHECK_FALSE(param_destructed); + } + // the parameter is destructed once the operation_state object is destructed + CHECK(param_destructed); } - my_res_t operator()(int x) const { - return fallible_just{x * 2 - 1}; + TEST_CASE( + "let_error exposes a parameter that is destructed when the main operation is destructed ", + "[adaptors][let_error]") { + bool param_destructed{false}; + bool fun_called{false}; + impulse_scheduler sched; + + ex::sender auto s1 = ex::just_error(my_type(¶m_destructed)); + ex::sender auto snd = ex::just_error(my_type(¶m_destructed)) // + | ex::let_error([&](const my_type& obj) { + CHECK_FALSE(param_destructed); + fun_called = true; + return ex::transfer_just(sched, 13); + }); + int res{0}; + { + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); + ex::start(op); + // The function is called immediately after starting the operation + CHECK(fun_called); + // As the returned sender didn't complete yet, the parameter must still be alive + CHECK_FALSE(param_destructed); + CHECK(res == 0); + + // Now, tell the scheduler to execute the final operation + sched.start_next(); + + // As the main operation is still valid, the parameter is not yet destructed + CHECK_FALSE(param_destructed); + } + + // At this point everything can be destructed + CHECK(param_destructed); + CHECK(res == 13); } -}; -TEST_CASE("let_error works when changing threads", "[adaptors][let_error]") { - exec::static_thread_pool pool{2}; - std::atomic called{false}; - { - // lunch some work on the thread pool - ex::sender auto snd = - ex::on(pool.get_scheduler(), ex::just_error(7)) // - | ex::let_error(int_err_transform{}) // - | ex::then([&](auto x) -> void { - CHECK(x == 13); - called.store(true); - }); - ex::start_detached(std::move(snd)); + struct int_err_transform { + using my_res_t = decltype(fallible_just{0}); + + my_res_t operator()(std::exception_ptr ep) const { + std::rethrow_exception(ep); + return {}; + } + + my_res_t operator()(int x) const { + return fallible_just{x * 2 - 1}; + } + }; + + TEST_CASE("let_error works when changing threads", "[adaptors][let_error]") { + exec::static_thread_pool pool{2}; + std::atomic called{false}; + { + // lunch some work on the thread pool + ex::sender auto snd = + ex::on(pool.get_scheduler(), ex::just_error(7)) // + | ex::let_error(int_err_transform{}) // + | ex::then([&](auto x) -> void { + CHECK(x == 13); + called.store(true); + }); + ex::start_detached(std::move(snd)); + } + // wait for the work to be executed, with timeout + // perform a poor-man's sync + // NOTE: it's a shame that the `join` method in static_thread_pool is not public + for (int i = 0; i < 1000 && !called.load(); i++) + std::this_thread::sleep_for(1ms); + // the work should be executed + REQUIRE(called); } - // wait for the work to be executed, with timeout - // perform a poor-man's sync - // NOTE: it's a shame that the `join` method in static_thread_pool is not public - for (int i = 0; i < 1000 && !called.load(); i++) - std::this_thread::sleep_for(1ms); - // the work should be executed - REQUIRE(called); -} -TEST_CASE( - "let_error has the values_type from the input sender if returning error", - "[adaptors][let_error]") { - check_val_types>>( - fallible_just{7} // - | ex::let_error([](std::exception_ptr) { return ex::just_error(0); })); - check_val_types>>( - fallible_just{3.14} // - | ex::let_error([](std::exception_ptr) { return ex::just_error(0); })); - check_val_types>>( - fallible_just{std::string{"hello"}} // - | ex::let_error([](std::exception_ptr) { return ex::just_error(0); })); -} + TEST_CASE( + "let_error has the values_type from the input sender if returning error", + "[adaptors][let_error]") { + check_val_types>>( + fallible_just{7} // + | ex::let_error([](std::exception_ptr) { return ex::just_error(0); })); + check_val_types>>( + fallible_just{3.14} // + | ex::let_error([](std::exception_ptr) { return ex::just_error(0); })); + check_val_types>>( + fallible_just{std::string{"hello"}} // + | ex::let_error([](std::exception_ptr) { return ex::just_error(0); })); + } -TEST_CASE( - "let_error adds to values_type the value types of the returned sender", - "[adaptors][let_error]") { - check_val_types>>( - fallible_just{1} // - | ex::let_error([](std::exception_ptr) { return ex::just(11); })); - check_val_types, type_array>>( - fallible_just{1} // - | ex::let_error([](std::exception_ptr) { return ex::just(3.14); })); - check_val_types, type_array>>( - fallible_just{1} // - | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"hello"}); })); -} + TEST_CASE( + "let_error adds to values_type the value types of the returned sender", + "[adaptors][let_error]") { + check_val_types>>( + fallible_just{1} // + | ex::let_error([](std::exception_ptr) { return ex::just(11); })); + check_val_types, type_array>>( + fallible_just{1} // + | ex::let_error([](std::exception_ptr) { return ex::just(3.14); })); + check_val_types, type_array>>( + fallible_just{1} // + | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"hello"}); })); + } -TEST_CASE( - "let_error overrides error_types from input sender (and adds std::exception_ptr)", - "[adaptors][let_error]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{43}; - - // Returning ex::just_error - check_err_types>( // - ex::transfer_just(sched1) // - | ex::let_error([](std::exception_ptr) { return ex::just_error(std::string{"err"}); })); - check_err_types>( // - ex::transfer_just(sched2) // - | ex::let_error([](std::exception_ptr) { return ex::just_error(std::string{"err"}); })); - check_err_types>( // - ex::transfer_just(sched3) // - | ex::let_error([](stdexec::__one_of auto) { - return ex::just_error(std::string{"err"}); - })); - - // Returning ex::just - check_err_types>( // - ex::transfer_just(sched1) // - | ex::let_error([](std::exception_ptr) { return ex::just(); })); - check_err_types>( // - ex::transfer_just(sched2) // - | ex::let_error([](std::exception_ptr) { return ex::just(); })); - check_err_types>( // - ex::transfer_just(sched3) // - | ex::let_error([](stdexec::__one_of auto) { return ex::just(); })); -} + TEST_CASE( + "let_error overrides error_types from input sender (and adds std::exception_ptr)", + "[adaptors][let_error]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{43}; + + // Returning ex::just_error + check_err_types>( // + ex::transfer_just(sched1) // + | ex::let_error([](std::exception_ptr) { return ex::just_error(std::string{"err"}); })); + check_err_types>( // + ex::transfer_just(sched2) // + | ex::let_error([](std::exception_ptr) { return ex::just_error(std::string{"err"}); })); + check_err_types>( // + ex::transfer_just(sched3) // + | ex::let_error([](stdexec::__one_of auto) { + return ex::just_error(std::string{"err"}); + })); + + // Returning ex::just + check_err_types>( // + ex::transfer_just(sched1) // + | ex::let_error([](std::exception_ptr) { return ex::just(); })); + check_err_types>( // + ex::transfer_just(sched2) // + | ex::let_error([](std::exception_ptr) { return ex::just(); })); + check_err_types>( // + ex::transfer_just(sched3) // + | ex::let_error([](stdexec::__one_of auto) { return ex::just(); })); + } -TEST_CASE("let_error keeps sends_stopped from input sender", "[adaptors][let_error]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - stopped_scheduler sched3{}; - - check_sends_stopped( // - ex::transfer_just(sched1) | ex::let_error([](std::exception_ptr) { return ex::just(); })); - check_sends_stopped( // - ex::transfer_just(sched2) | ex::let_error([](std::exception_ptr) { return ex::just(); })); - check_sends_stopped( // - ex::transfer_just(sched3) | ex::let_error([](std::exception_ptr) { return ex::just(); })); -} + TEST_CASE("let_error keeps sends_stopped from input sender", "[adaptors][let_error]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + stopped_scheduler sched3{}; + + check_sends_stopped( // + ex::transfer_just(sched1) | ex::let_error([](std::exception_ptr) { return ex::just(); })); + check_sends_stopped( // + ex::transfer_just(sched2) | ex::let_error([](std::exception_ptr) { return ex::just(); })); + check_sends_stopped( // + ex::transfer_just(sched3) | ex::let_error([](std::exception_ptr) { return ex::just(); })); + } -// Return a different sender when we invoke this custom defined on implementation -using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); + // Return a different sender when we invoke this custom defined on implementation + using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); -template -auto tag_invoke(ex::let_error_t, inline_scheduler sched, my_string_sender_t, Fun) { - return ex::just(std::string{"what error?"}); -} + template + auto tag_invoke(ex::let_error_t, inline_scheduler sched, my_string_sender_t, Fun) { + return ex::just(std::string{"what error?"}); + } -TEST_CASE("let_error can be customized", "[adaptors][let_error]") { - // The customization will return a different value - auto snd = ex::transfer_just(inline_scheduler{}, std::string{"hello"}) // - | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"err"}); }); - wait_for_value(std::move(snd), std::string{"what error?"}); + TEST_CASE("let_error can be customized", "[adaptors][let_error]") { + // The customization will return a different value + auto snd = ex::transfer_just(inline_scheduler{}, std::string{"hello"}) // + | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"err"}); }); + wait_for_value(std::move(snd), std::string{"what error?"}); + } } diff --git a/test/stdexec/algos/adaptors/test_let_stopped.cpp b/test/stdexec/algos/adaptors/test_let_stopped.cpp index 25278f821..c79e0a0aa 100644 --- a/test/stdexec/algos/adaptors/test_let_stopped.cpp +++ b/test/stdexec/algos/adaptors/test_let_stopped.cpp @@ -26,203 +26,206 @@ namespace ex = stdexec; using namespace std::chrono_literals; -TEST_CASE("let_stopped returns a sender", "[adaptors][let_stopped]") { - auto snd = ex::let_stopped(ex::just(), [] { return ex::just(); }); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("let_stopped with environment returns a sender", "[adaptors][let_stopped]") { - auto snd = ex::let_stopped(ex::just(), [] { return ex::just(); }); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("let_stopped simple example", "[adaptors][let_stopped]") { - bool called{false}; - auto snd = ex::let_stopped(ex::just_stopped(), [&] { - called = true; - return ex::just(); - }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); - // The receiver checks that it's called - // we also check that the function was invoked - CHECK(called); -} - -TEST_CASE("let_stopped can be piped", "[adaptors][let_stopped]") { - ex::sender auto snd = ex::just() | ex::let_stopped([] { return ex::just(); }); - (void) snd; -} - -TEST_CASE( - "let_stopped returning void can we waited on (cancel annihilation)", - "[adaptors][let_stopped]") { - ex::sender auto snd = ex::just_stopped() | ex::let_stopped([] { return ex::just(); }); - stdexec::sync_wait(std::move(snd)); -} - -TEST_CASE( - "let_stopped can be used to produce values (cancel to value)", - "[adaptors][let_stopped]") { - ex::sender auto snd = ex::just_stopped() // - | ex::let_stopped([] { return ex::just(std::string{"cancelled"}); }); - wait_for_value(std::move(snd), std::string{"cancelled"}); -} - -TEST_CASE("let_stopped can throw, calling set_error", "[adaptors][let_stopped]") { - auto snd = ex::just_stopped() // - | ex::let_stopped([] { - throw std::logic_error{"err"}; - return ex::just(1); - }); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); -} - -TEST_CASE("let_stopped can be used with just_error", "[adaptors][let_stopped]") { - ex::sender auto snd = ex::just_error(1) // - | ex::let_stopped([] { return ex::just(17); }); - auto op = ex::connect(std::move(snd), expect_error_receiver{1}); - ex::start(op); -} - -TEST_CASE("let_stopped function is not called on regular flow", "[adaptors][let_stopped]") { - bool called{false}; - error_scheduler sched; - ex::sender auto snd = ex::just(13) // - | ex::let_stopped([&] { - called = true; - return ex::just(0); - }); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - CHECK_FALSE(called); -} - -TEST_CASE("let_stopped function is not called on error flow", "[adaptors][let_stopped]") { - bool called{false}; - error_scheduler sched{42}; - ex::sender auto snd = ex::transfer_just(sched, 13) // - | ex::let_stopped([&] { - called = true; - return ex::just(0); - }); - auto op = ex::connect(std::move(snd), expect_error_receiver{42}); - ex::start(op); - CHECK_FALSE(called); -} - -TEST_CASE( - "let_stopped has the values_type from the input sender if returning error", - "[adaptors][let_stopped]") { - check_val_types>>( - ex::just(7) // - | ex::let_stopped([] { return ex::just_error(0); })); - check_val_types>>( - ex::just(3.14) // - | ex::let_stopped([] { return ex::just_error(0); })); - check_val_types>>( - ex::just(std::string{"hello"}) // - | ex::let_stopped([] { return ex::just_error(0); })); -} - -TEST_CASE( - "let_stopped adds to values_type the value types of the returned sender", - "[adaptors][let_stopped]") { - check_val_types>>( - ex::just(1) // - | ex::let_stopped([] { return ex::just(11); })); - check_val_types>>( - ex::just(1) // - | ex::let_stopped([] { return ex::just(3.14); })); - check_val_types>>( - ex::just(1) // - | ex::let_stopped([] { return ex::just(std::string{"hello"}); })); -} - -TEST_CASE( - "let_stopped has the error_type from the input sender if returning value", - "[adaptors][let_stopped]") { - check_err_types>( // - ex::just_error(7) // - | ex::let_stopped([] { return ex::just(0); })); - check_err_types>( // - ex::just_error(3.14) // - | ex::let_stopped([] { return ex::just(0); })); - check_err_types>( // - ex::just_error(std::string{"hello"}) // - | ex::let_stopped([] { return ex::just(0); })); -} - -TEST_CASE("let_stopped adds to error_type of the input sender", "[adaptors][let_stopped]") { - impulse_scheduler sched; - ex::sender auto in_snd = ex::transfer_just(sched, 11); - check_err_types>( // - in_snd // - | ex::let_stopped([] { return ex::just_error(0); })); - check_err_types>( // - in_snd // - | ex::let_stopped([] { return ex::just_error(3.14); })); - check_err_types>( // - in_snd // - | ex::let_stopped([] { return ex::just_error(std::string{"err"}); })); -} - -TEST_CASE("let_stopped can be used instead of stopped_as_error", "[adaptors][let_stopped]") { - impulse_scheduler sched; - ex::sender auto in_snd = ex::transfer_just(sched, 11); - check_val_types>>(in_snd); - check_err_types>(in_snd); - check_sends_stopped(in_snd); - - ex::sender auto snd = std::move(in_snd) | ex::let_stopped([] { return ex::just_error(-1); }); - - check_val_types>>(snd); - check_err_types>(snd); - check_sends_stopped(snd); -} - -TEST_CASE("let_stopped overrides sends_stopped from input sender", "[adaptors][let_stopped]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{43}; - - // Returning ex::just - check_sends_stopped( // - ex::transfer_just(sched1) // - | ex::let_stopped([] { return ex::just(); })); - check_sends_stopped( // - ex::transfer_just(sched2) // - | ex::let_stopped([] { return ex::just(); })); - check_sends_stopped( // - ex::transfer_just(sched3) // - | ex::let_stopped([] { return ex::just(); })); - - // Returning ex::just_stopped - check_sends_stopped( // - ex::transfer_just(sched1) // - | ex::let_stopped([] { return ex::just_stopped(); })); - check_sends_stopped( // - ex::transfer_just(sched2) // - | ex::let_stopped([] { return ex::just_stopped(); })); - check_sends_stopped( // - ex::transfer_just(sched3) // - | ex::let_stopped([] { return ex::just_stopped(); })); -} - -// Return a different sender when we invoke this custom defined on implementation -using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); - -template -auto tag_invoke(ex::let_stopped_t, inline_scheduler sched, my_string_sender_t, Fun) { - return ex::just(std::string{"Don't stop me now"}); -} - -TEST_CASE("let_stopped can be customized", "[adaptors][let_stopped]") { - // The customization will return a different value - auto snd = ex::transfer_just(inline_scheduler{}, std::string{"hello"}) // - | ex::let_stopped([] { return ex::just(std::string{"stopped"}); }); - wait_for_value(std::move(snd), std::string{"Don't stop me now"}); +namespace { + + TEST_CASE("let_stopped returns a sender", "[adaptors][let_stopped]") { + auto snd = ex::let_stopped(ex::just(), [] { return ex::just(); }); + static_assert(ex::sender); + (void) snd; + } + + TEST_CASE("let_stopped with environment returns a sender", "[adaptors][let_stopped]") { + auto snd = ex::let_stopped(ex::just(), [] { return ex::just(); }); + static_assert(ex::sender_in); + (void) snd; + } + + TEST_CASE("let_stopped simple example", "[adaptors][let_stopped]") { + bool called{false}; + auto snd = ex::let_stopped(ex::just_stopped(), [&] { + called = true; + return ex::just(); + }); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + // The receiver checks that it's called + // we also check that the function was invoked + CHECK(called); + } + + TEST_CASE("let_stopped can be piped", "[adaptors][let_stopped]") { + ex::sender auto snd = ex::just() | ex::let_stopped([] { return ex::just(); }); + (void) snd; + } + + TEST_CASE( + "let_stopped returning void can we waited on (cancel annihilation)", + "[adaptors][let_stopped]") { + ex::sender auto snd = ex::just_stopped() | ex::let_stopped([] { return ex::just(); }); + stdexec::sync_wait(std::move(snd)); + } + + TEST_CASE( + "let_stopped can be used to produce values (cancel to value)", + "[adaptors][let_stopped]") { + ex::sender auto snd = ex::just_stopped() // + | ex::let_stopped([] { return ex::just(std::string{"cancelled"}); }); + wait_for_value(std::move(snd), std::string{"cancelled"}); + } + + TEST_CASE("let_stopped can throw, calling set_error", "[adaptors][let_stopped]") { + auto snd = ex::just_stopped() // + | ex::let_stopped([] { + throw std::logic_error{"err"}; + return ex::just(1); + }); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + } + + TEST_CASE("let_stopped can be used with just_error", "[adaptors][let_stopped]") { + ex::sender auto snd = ex::just_error(1) // + | ex::let_stopped([] { return ex::just(17); }); + auto op = ex::connect(std::move(snd), expect_error_receiver{1}); + ex::start(op); + } + + TEST_CASE("let_stopped function is not called on regular flow", "[adaptors][let_stopped]") { + bool called{false}; + error_scheduler sched; + ex::sender auto snd = ex::just(13) // + | ex::let_stopped([&] { + called = true; + return ex::just(0); + }); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + CHECK_FALSE(called); + } + + TEST_CASE("let_stopped function is not called on error flow", "[adaptors][let_stopped]") { + bool called{false}; + error_scheduler sched{42}; + ex::sender auto snd = ex::transfer_just(sched, 13) // + | ex::let_stopped([&] { + called = true; + return ex::just(0); + }); + auto op = ex::connect(std::move(snd), expect_error_receiver{42}); + ex::start(op); + CHECK_FALSE(called); + } + + TEST_CASE( + "let_stopped has the values_type from the input sender if returning error", + "[adaptors][let_stopped]") { + check_val_types>>( + ex::just(7) // + | ex::let_stopped([] { return ex::just_error(0); })); + check_val_types>>( + ex::just(3.14) // + | ex::let_stopped([] { return ex::just_error(0); })); + check_val_types>>( + ex::just(std::string{"hello"}) // + | ex::let_stopped([] { return ex::just_error(0); })); + } + + TEST_CASE( + "let_stopped adds to values_type the value types of the returned sender", + "[adaptors][let_stopped]") { + check_val_types>>( + ex::just(1) // + | ex::let_stopped([] { return ex::just(11); })); + check_val_types>>( + ex::just(1) // + | ex::let_stopped([] { return ex::just(3.14); })); + check_val_types>>( + ex::just(1) // + | ex::let_stopped([] { return ex::just(std::string{"hello"}); })); + } + + TEST_CASE( + "let_stopped has the error_type from the input sender if returning value", + "[adaptors][let_stopped]") { + check_err_types>( // + ex::just_error(7) // + | ex::let_stopped([] { return ex::just(0); })); + check_err_types>( // + ex::just_error(3.14) // + | ex::let_stopped([] { return ex::just(0); })); + check_err_types>( // + ex::just_error(std::string{"hello"}) // + | ex::let_stopped([] { return ex::just(0); })); + } + + TEST_CASE("let_stopped adds to error_type of the input sender", "[adaptors][let_stopped]") { + impulse_scheduler sched; + ex::sender auto in_snd = ex::transfer_just(sched, 11); + check_err_types>( // + in_snd // + | ex::let_stopped([] { return ex::just_error(0); })); + check_err_types>( // + in_snd // + | ex::let_stopped([] { return ex::just_error(3.14); })); + check_err_types>( // + in_snd // + | ex::let_stopped([] { return ex::just_error(std::string{"err"}); })); + } + + TEST_CASE("let_stopped can be used instead of stopped_as_error", "[adaptors][let_stopped]") { + impulse_scheduler sched; + ex::sender auto in_snd = ex::transfer_just(sched, 11); + check_val_types>>(in_snd); + check_err_types>(in_snd); + check_sends_stopped(in_snd); + + ex::sender auto snd = std::move(in_snd) | ex::let_stopped([] { return ex::just_error(-1); }); + + check_val_types>>(snd); + check_err_types>(snd); + check_sends_stopped(snd); + } + + TEST_CASE("let_stopped overrides sends_stopped from input sender", "[adaptors][let_stopped]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{43}; + + // Returning ex::just + check_sends_stopped( // + ex::transfer_just(sched1) // + | ex::let_stopped([] { return ex::just(); })); + check_sends_stopped( // + ex::transfer_just(sched2) // + | ex::let_stopped([] { return ex::just(); })); + check_sends_stopped( // + ex::transfer_just(sched3) // + | ex::let_stopped([] { return ex::just(); })); + + // Returning ex::just_stopped + check_sends_stopped( // + ex::transfer_just(sched1) // + | ex::let_stopped([] { return ex::just_stopped(); })); + check_sends_stopped( // + ex::transfer_just(sched2) // + | ex::let_stopped([] { return ex::just_stopped(); })); + check_sends_stopped( // + ex::transfer_just(sched3) // + | ex::let_stopped([] { return ex::just_stopped(); })); + } + + // Return a different sender when we invoke this custom defined on implementation + using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); + + template + auto tag_invoke(ex::let_stopped_t, inline_scheduler sched, my_string_sender_t, Fun) { + return ex::just(std::string{"Don't stop me now"}); + } + + TEST_CASE("let_stopped can be customized", "[adaptors][let_stopped]") { + // The customization will return a different value + auto snd = ex::transfer_just(inline_scheduler{}, std::string{"hello"}) // + | ex::let_stopped([] { return ex::just(std::string{"stopped"}); }); + wait_for_value(std::move(snd), std::string{"Don't stop me now"}); + } } diff --git a/test/stdexec/algos/adaptors/test_let_value.cpp b/test/stdexec/algos/adaptors/test_let_value.cpp index a91861013..cfb7f8444 100644 --- a/test/stdexec/algos/adaptors/test_let_value.cpp +++ b/test/stdexec/algos/adaptors/test_let_value.cpp @@ -27,288 +27,293 @@ namespace ex = stdexec; using namespace std::chrono_literals; -TEST_CASE("let_value returns a sender", "[adaptors][let_value]") { - auto snd = ex::let_value(ex::just(), [] { return ex::just(); }); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("let_value with environment returns a sender", "[adaptors][let_value]") { - auto snd = ex::let_value(ex::just(), [] { return ex::just(); }); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("let_value simple example", "[adaptors][let_value]") { - bool called{false}; - auto snd = ex::let_value(ex::just(), [&] { - called = true; - return ex::just(); - }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); - // The receiver checks that it's called - // we also check that the function was invoked - CHECK(called); -} +namespace { -TEST_CASE("let_value can be piped", "[adaptors][let_value]") { - ex::sender auto snd = ex::just() | ex::let_value([] { return ex::just(); }); - (void) snd; -} + TEST_CASE("let_value returns a sender", "[adaptors][let_value]") { + auto snd = ex::let_value(ex::just(), [] { return ex::just(); }); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE("let_value returning void can we waited on", "[adaptors][let_value]") { - ex::sender auto snd = ex::just() | ex::let_value([] { return ex::just(); }); - stdexec::sync_wait(std::move(snd)); -} + TEST_CASE("let_value with environment returns a sender", "[adaptors][let_value]") { + auto snd = ex::let_value(ex::just(), [] { return ex::just(); }); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("let_value can be used to produce values", "[adaptors][let_value]") { - ex::sender auto snd = ex::just() | ex::let_value([] { return ex::just(13); }); - wait_for_value(std::move(snd), 13); -} + TEST_CASE("let_value simple example", "[adaptors][let_value]") { + bool called{false}; + auto snd = ex::let_value(ex::just(), [&] { + called = true; + return ex::just(); + }); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + // The receiver checks that it's called + // we also check that the function was invoked + CHECK(called); + } -TEST_CASE("let_value can be used to transform values", "[adaptors][let_value]") { - ex::sender auto snd = ex::just(13) | ex::let_value([](int& x) { return ex::just(x + 4); }); - wait_for_value(std::move(snd), 17); -} + TEST_CASE("let_value can be piped", "[adaptors][let_value]") { + ex::sender auto snd = ex::just() | ex::let_value([] { return ex::just(); }); + (void) snd; + } -TEST_CASE("let_value can be used with multiple parameters", "[adaptors][let_value]") { - auto snd = ex::just(3, 0.1415) | ex::let_value([](int& x, double y) { return ex::just(x + y); }); - wait_for_value(std::move(snd), 3.1415); -} + TEST_CASE("let_value returning void can we waited on", "[adaptors][let_value]") { + ex::sender auto snd = ex::just() | ex::let_value([] { return ex::just(); }); + stdexec::sync_wait(std::move(snd)); + } -TEST_CASE("let_value can be used to change the sender", "[adaptors][let_value]") { - ex::sender auto snd = ex::just(13) | ex::let_value([](int& x) { return ex::just_error(x + 4); }); - auto op = ex::connect(std::move(snd), expect_error_receiver{13 + 4}); - ex::start(op); -} + TEST_CASE("let_value can be used to produce values", "[adaptors][let_value]") { + ex::sender auto snd = ex::just() | ex::let_value([] { return ex::just(13); }); + wait_for_value(std::move(snd), 13); + } -bool is_prime(int x) { - if (x > 2 && (x % 2 == 0)) - return false; - int d = 3; - while (d * d < x) { - if (x % d == 0) - return false; - d += 2; + TEST_CASE("let_value can be used to transform values", "[adaptors][let_value]") { + ex::sender auto snd = ex::just(13) | ex::let_value([](int& x) { return ex::just(x + 4); }); + wait_for_value(std::move(snd), 17); } - return true; -} -TEST_CASE("let_value can be used for composition", "[adaptors][let_value]") { - bool called1{false}; - bool called2{false}; - bool called3{false}; - auto f1 = [&](int& x) { - called1 = true; - return ex::just(2 * x); - }; - auto f2 = [&](int& x) { - called2 = true; - return ex::just(x + 3); - }; - auto f3 = [&](int& x) { - called3 = true; - if (!is_prime(x)) - throw std::logic_error("not prime"); - return ex::just(x); - }; - ex::sender auto snd = - ex::just(13) // - | ex::let_value(f1) // - | ex::let_value(f2) // - | ex::let_value(f3) // - ; - wait_for_value(std::move(snd), 29); - CHECK(called1); - CHECK(called2); - CHECK(called3); -} + TEST_CASE("let_value can be used with multiple parameters", "[adaptors][let_value]") { + auto snd = ex::just(3, 0.1415) + | ex::let_value([](int& x, double y) { return ex::just(x + y); }); + wait_for_value(std::move(snd), 3.1415); + } -TEST_CASE("let_value can throw, and set_error will be called", "[adaptors][let_value]") { - auto snd = ex::just(13) // - | ex::let_value([](int& x) { - throw std::logic_error{"err"}; - return ex::just(x + 5); - }); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); -} + TEST_CASE("let_value can be used to change the sender", "[adaptors][let_value]") { + ex::sender auto snd = ex::just(13) + | ex::let_value([](int& x) { return ex::just_error(x + 4); }); + auto op = ex::connect(std::move(snd), expect_error_receiver{13 + 4}); + ex::start(op); + } -TEST_CASE("let_value can be used with just_error", "[adaptors][let_value]") { - ex::sender auto snd = ex::just_error(std::string{"err"}) // - | ex::let_value([]() { return ex::just(17); }); - auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"err"}}); - ex::start(op); -} + bool is_prime(int x) { + if (x > 2 && (x % 2 == 0)) + return false; + int d = 3; + while (d * d < x) { + if (x % d == 0) + return false; + d += 2; + } + return true; + } -TEST_CASE("let_value can be used with just_stopped", "[adaptors][let_value]") { - ex::sender auto snd = ex::just_stopped() | ex::let_value([]() { return ex::just(17); }); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); -} + TEST_CASE("let_value can be used for composition", "[adaptors][let_value]") { + bool called1{false}; + bool called2{false}; + bool called3{false}; + auto f1 = [&](int& x) { + called1 = true; + return ex::just(2 * x); + }; + auto f2 = [&](int& x) { + called2 = true; + return ex::just(x + 3); + }; + auto f3 = [&](int& x) { + called3 = true; + if (!is_prime(x)) + throw std::logic_error("not prime"); + return ex::just(x); + }; + ex::sender auto snd = + ex::just(13) // + | ex::let_value(f1) // + | ex::let_value(f2) // + | ex::let_value(f3) // + ; + wait_for_value(std::move(snd), 29); + CHECK(called1); + CHECK(called2); + CHECK(called3); + } -TEST_CASE("let_value function is not called on error", "[adaptors][let_value]") { - bool called{false}; - error_scheduler sched; - ex::sender auto snd = ex::transfer_just(sched, 13) // - | ex::let_value([&](int& x) { - called = true; - return ex::just(x + 5); - }); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); - CHECK_FALSE(called); -} + TEST_CASE("let_value can throw, and set_error will be called", "[adaptors][let_value]") { + auto snd = ex::just(13) // + | ex::let_value([](int& x) { + throw std::logic_error{"err"}; + return ex::just(x + 5); + }); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + } -TEST_CASE("let_value function is not called when cancelled", "[adaptors][let_value]") { - bool called{false}; - stopped_scheduler sched; - ex::sender auto snd = ex::transfer_just(sched, 13) // - | ex::let_value([&](int& x) { - called = true; - return ex::just(x + 5); - }); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); - CHECK_FALSE(called); -} + TEST_CASE("let_value can be used with just_error", "[adaptors][let_value]") { + ex::sender auto snd = ex::just_error(std::string{"err"}) // + | ex::let_value([]() { return ex::just(17); }); + auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"err"}}); + ex::start(op); + } -TEST_CASE( - "let_value exposes a parameter that is destructed when the main operation is destructed", - "[adaptors][let_value]") { + TEST_CASE("let_value can be used with just_stopped", "[adaptors][let_value]") { + ex::sender auto snd = ex::just_stopped() | ex::let_value([]() { return ex::just(17); }); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + } - // Type that sets into a received boolean when the dtor is called - struct my_type { - bool* p_called_{nullptr}; + TEST_CASE("let_value function is not called on error", "[adaptors][let_value]") { + bool called{false}; + error_scheduler sched; + ex::sender auto snd = ex::transfer_just(sched, 13) // + | ex::let_value([&](int& x) { + called = true; + return ex::just(x + 5); + }); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + CHECK_FALSE(called); + } - explicit my_type(bool* p_called) - : p_called_(p_called) { - } + TEST_CASE("let_value function is not called when cancelled", "[adaptors][let_value]") { + bool called{false}; + stopped_scheduler sched; + ex::sender auto snd = ex::transfer_just(sched, 13) // + | ex::let_value([&](int& x) { + called = true; + return ex::just(x + 5); + }); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + CHECK_FALSE(called); + } - my_type(my_type&& rhs) - : p_called_(rhs.p_called_) { - rhs.p_called_ = nullptr; + TEST_CASE( + "let_value exposes a parameter that is destructed when the main operation is destructed", + "[adaptors][let_value]") { + + // Type that sets into a received boolean when the dtor is called + struct my_type { + bool* p_called_{nullptr}; + + explicit my_type(bool* p_called) + : p_called_(p_called) { + } + + my_type(my_type&& rhs) + : p_called_(rhs.p_called_) { + rhs.p_called_ = nullptr; + } + + my_type& operator=(my_type&& rhs) { + if (p_called_) + *p_called_ = true; + p_called_ = rhs.p_called_; + rhs.p_called_ = nullptr; + return *this; + } + + ~my_type() { + if (p_called_) + *p_called_ = true; + } + }; + + bool param_destructed{false}; + bool fun_called{false}; + impulse_scheduler sched; + + ex::sender auto snd = ex::just(my_type(¶m_destructed)) // + | ex::let_value([&](const my_type& obj) { + CHECK_FALSE(param_destructed); + fun_called = true; + return ex::transfer_just(sched, 13); + }); + + { + int res{0}; + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); + ex::start(op); + // The function is called immediately after starting the operation + CHECK(fun_called); + // As the returned sender didn't complete yet, the parameter must still be alive + CHECK_FALSE(param_destructed); + CHECK(res == 0); + + // Now, tell the scheduler to execute the final operation + sched.start_next(); + + // The parameter is going to be destructed when the op is destructed; it should be valid now + CHECK_FALSE(param_destructed); + CHECK(res == 13); } - my_type& operator=(my_type&& rhs) { - if (p_called_) - *p_called_ = true; - p_called_ = rhs.p_called_; - rhs.p_called_ = nullptr; - return *this; - } + // At this point everything can be destructed + CHECK(param_destructed); + } - ~my_type() { - if (p_called_) - *p_called_ = true; + TEST_CASE("let_value works when changing threads", "[adaptors][let_value]") { + exec::static_thread_pool pool{2}; + std::atomic called{false}; + { + // lunch some work on the thread pool + ex::sender auto snd = + ex::transfer_just(pool.get_scheduler(), 7) // + | ex::let_value([](int& x) { return ex::just(x * 2 - 1); }) // + | ex::then([&](int x) { + CHECK(x == 13); + called.store(true); + }); + ex::start_detached(std::move(snd)); } - }; - - bool param_destructed{false}; - bool fun_called{false}; - impulse_scheduler sched; - - ex::sender auto snd = ex::just(my_type(¶m_destructed)) // - | ex::let_value([&](const my_type& obj) { - CHECK_FALSE(param_destructed); - fun_called = true; - return ex::transfer_just(sched, 13); - }); - - { - int res{0}; - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); - ex::start(op); - // The function is called immediately after starting the operation - CHECK(fun_called); - // As the returned sender didn't complete yet, the parameter must still be alive - CHECK_FALSE(param_destructed); - CHECK(res == 0); - - // Now, tell the scheduler to execute the final operation - sched.start_next(); - - // The parameter is going to be destructed when the op is destructed; it should be valid now - CHECK_FALSE(param_destructed); - CHECK(res == 13); + // wait for the work to be executed, with timeout + // perform a poor-man's sync + // NOTE: it's a shame that the `join` method in static_thread_pool is not public + for (int i = 0; i < 1000 && !called.load(); i++) + std::this_thread::sleep_for(1ms); + // the work should be executed + REQUIRE(called); } - // At this point everything can be destructed - CHECK(param_destructed); -} - -TEST_CASE("let_value works when changing threads", "[adaptors][let_value]") { - exec::static_thread_pool pool{2}; - std::atomic called{false}; - { - // lunch some work on the thread pool - ex::sender auto snd = - ex::transfer_just(pool.get_scheduler(), 7) // - | ex::let_value([](int& x) { return ex::just(x * 2 - 1); }) // - | ex::then([&](int x) { - CHECK(x == 13); - called.store(true); - }); - ex::start_detached(std::move(snd)); + TEST_CASE( + "let_value has the values_type corresponding to the given values", + "[adaptors][let_value]") { + check_val_types>>( + ex::just() | ex::let_value([] { return ex::just(7); })); + check_val_types>>( + ex::just() | ex::let_value([] { return ex::just(3.14); })); + check_val_types>>( + ex::just() | ex::let_value([] { return ex::just(std::string{"hello"}); })); } - // wait for the work to be executed, with timeout - // perform a poor-man's sync - // NOTE: it's a shame that the `join` method in static_thread_pool is not public - for (int i = 0; i < 1000 && !called.load(); i++) - std::this_thread::sleep_for(1ms); - // the work should be executed - REQUIRE(called); -} - -TEST_CASE( - "let_value has the values_type corresponding to the given values", - "[adaptors][let_value]") { - check_val_types>>( - ex::just() | ex::let_value([] { return ex::just(7); })); - check_val_types>>( - ex::just() | ex::let_value([] { return ex::just(3.14); })); - check_val_types>>( - ex::just() | ex::let_value([] { return ex::just(std::string{"hello"}); })); -} -TEST_CASE("let_value keeps error_types from input sender", "[adaptors][let_value]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{43}; - - check_err_types>( // - ex::transfer_just(sched1) | ex::let_value([] { return ex::just(); })); - check_err_types>( // - ex::transfer_just(sched2) | ex::let_value([] { return ex::just(); })); - check_err_types>( // - ex::transfer_just(sched3) | ex::let_value([] { return ex::just(); })); -} + TEST_CASE("let_value keeps error_types from input sender", "[adaptors][let_value]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{43}; + + check_err_types>( // + ex::transfer_just(sched1) | ex::let_value([] { return ex::just(); })); + check_err_types>( // + ex::transfer_just(sched2) | ex::let_value([] { return ex::just(); })); + check_err_types>( // + ex::transfer_just(sched3) | ex::let_value([] { return ex::just(); })); + } -TEST_CASE("let_value keeps sends_stopped from input sender", "[adaptors][let_value]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - stopped_scheduler sched3{}; - - check_sends_stopped( // - ex::transfer_just(sched1) | ex::let_value([] { return ex::just(); })); - check_sends_stopped( // - ex::transfer_just(sched2) | ex::let_value([] { return ex::just(); })); - check_sends_stopped( // - ex::transfer_just(sched3) | ex::let_value([] { return ex::just(); })); -} + TEST_CASE("let_value keeps sends_stopped from input sender", "[adaptors][let_value]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + stopped_scheduler sched3{}; + + check_sends_stopped( // + ex::transfer_just(sched1) | ex::let_value([] { return ex::just(); })); + check_sends_stopped( // + ex::transfer_just(sched2) | ex::let_value([] { return ex::just(); })); + check_sends_stopped( // + ex::transfer_just(sched3) | ex::let_value([] { return ex::just(); })); + } -// Return a different sender when we invoke this custom defined on implementation -using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); + // Return a different sender when we invoke this custom defined on implementation + using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); -template -auto tag_invoke(ex::let_value_t, inline_scheduler sched, my_string_sender_t, Fun) { - return ex::just(std::string{"hallo"}); -} + template + auto tag_invoke(ex::let_value_t, inline_scheduler sched, my_string_sender_t, Fun) { + return ex::just(std::string{"hallo"}); + } -TEST_CASE("let_value can be customized", "[adaptors][let_value]") { - // The customization will return a different value - auto snd = ex::transfer_just(inline_scheduler{}, std::string{"hello"}) // - | ex::let_value([](std::string& x) { return ex::just(x + ", world"); }); - wait_for_value(std::move(snd), std::string{"hallo"}); + TEST_CASE("let_value can be customized", "[adaptors][let_value]") { + // The customization will return a different value + auto snd = ex::transfer_just(inline_scheduler{}, std::string{"hello"}) // + | ex::let_value([](std::string& x) { return ex::just(x + ", world"); }); + wait_for_value(std::move(snd), std::string{"hallo"}); + } } diff --git a/test/stdexec/algos/adaptors/test_on.cpp b/test/stdexec/algos/adaptors/test_on.cpp index 45c1e1aa4..ff2585fd2 100644 --- a/test/stdexec/algos/adaptors/test_on.cpp +++ b/test/stdexec/algos/adaptors/test_on.cpp @@ -27,248 +27,259 @@ namespace ex = stdexec; using namespace std::chrono_literals; -TEST_CASE("on returns a sender", "[adaptors][on]") { - auto snd = ex::on(inline_scheduler{}, ex::just(13)); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("on with environment returns a sender", "[adaptors][on]") { - auto snd = ex::on(inline_scheduler{}, ex::just(13)); - static_assert(ex::sender_in); - (void) snd; -} +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-function") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunneeded-internal-declaration") -TEST_CASE("on simple example", "[adaptors][on]") { - auto snd = ex::on(inline_scheduler{}, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} +namespace { -TEST_CASE("on calls the receiver when the scheduler dictates", "[adaptors][on]") { - int recv_value{0}; - impulse_scheduler sched; - auto snd = ex::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); - ex::start(op); - // Up until this point, the scheduler didn't start any task; no effect expected - CHECK(recv_value == 0); - - // Tell the scheduler to start executing one task - sched.start_next(); - CHECK(recv_value == 13); -} + TEST_CASE("on returns a sender", "[adaptors][on]") { + auto snd = ex::on(inline_scheduler{}, ex::just(13)); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE("on calls the given sender when the scheduler dictates", "[adaptors][on]") { - bool called{false}; - auto snd_base = ex::just() // - | ex::then([&]() -> int { - called = true; - return 19; - }); - - int recv_value{0}; - impulse_scheduler sched; - auto snd = ex::on(sched, std::move(snd_base)); - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); - ex::start(op); - // Up until this point, the scheduler didn't start any task - // The base sender shouldn't be started - CHECK_FALSE(called); - - // Tell the scheduler to start executing one task - sched.start_next(); - - // Now the base sender is called, and a value is sent to the receiver - CHECK(called); - CHECK(recv_value == 19); -} + TEST_CASE("on with environment returns a sender", "[adaptors][on]") { + auto snd = ex::on(inline_scheduler{}, ex::just(13)); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("on works when changing threads", "[adaptors][on]") { - exec::static_thread_pool pool{2}; - std::atomic called{false}; - { - // lunch some work on the thread pool - ex::sender auto snd = ex::on(pool.get_scheduler(), ex::just()) // - | ex::then([&] { called.store(true); }); - ex::start_detached(std::move(snd)); + TEST_CASE("on simple example", "[adaptors][on]") { + auto snd = ex::on(inline_scheduler{}, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value } - // wait for the work to be executed, with timeout - // perform a poor-man's sync - // NOTE: it's a shame that the `join` method in static_thread_pool is not public - for (int i = 0; i < 1000 && !called.load(); i++) - std::this_thread::sleep_for(1ms); - // the work should be executed - REQUIRE(called); -} -TEST_CASE("on can be called with rvalue ref scheduler", "[adaptors][on]") { - auto snd = ex::on(inline_scheduler{}, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} + TEST_CASE("on calls the receiver when the scheduler dictates", "[adaptors][on]") { + int recv_value{0}; + impulse_scheduler sched; + auto snd = ex::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); + ex::start(op); + // Up until this point, the scheduler didn't start any task; no effect expected + CHECK(recv_value == 0); + + // Tell the scheduler to start executing one task + sched.start_next(); + CHECK(recv_value == 13); + } -TEST_CASE("on can be called with const ref scheduler", "[adaptors][on]") { - const inline_scheduler sched; - auto snd = ex::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} + TEST_CASE("on calls the given sender when the scheduler dictates", "[adaptors][on]") { + bool called{false}; + auto snd_base = ex::just() // + | ex::then([&]() -> int { + called = true; + return 19; + }); + + int recv_value{0}; + impulse_scheduler sched; + auto snd = ex::on(sched, std::move(snd_base)); + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); + ex::start(op); + // Up until this point, the scheduler didn't start any task + // The base sender shouldn't be started + CHECK_FALSE(called); + + // Tell the scheduler to start executing one task + sched.start_next(); + + // Now the base sender is called, and a value is sent to the receiver + CHECK(called); + CHECK(recv_value == 19); + } -TEST_CASE("on can be called with ref scheduler", "[adaptors][on]") { - inline_scheduler sched; - auto snd = ex::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} + TEST_CASE("on works when changing threads", "[adaptors][on]") { + exec::static_thread_pool pool{2}; + std::atomic called{false}; + { + // lunch some work on the thread pool + ex::sender auto snd = ex::on(pool.get_scheduler(), ex::just()) // + | ex::then([&] { called.store(true); }); + ex::start_detached(std::move(snd)); + } + // wait for the work to be executed, with timeout + // perform a poor-man's sync + // NOTE: it's a shame that the `join` method in static_thread_pool is not public + for (int i = 0; i < 1000 && !called.load(); i++) + std::this_thread::sleep_for(1ms); + // the work should be executed + REQUIRE(called); + } -TEST_CASE("on forwards set_error calls", "[adaptors][on]") { - error_scheduler sched{std::exception_ptr{}}; - auto snd = ex::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); - // The receiver checks if we receive an error -} + TEST_CASE("on can be called with rvalue ref scheduler", "[adaptors][on]") { + auto snd = ex::on(inline_scheduler{}, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } -TEST_CASE("on forwards set_error calls of other types", "[adaptors][on]") { - error_scheduler sched{std::string{"error"}}; - auto snd = ex::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"error"}}); - ex::start(op); - // The receiver checks if we receive an error -} + TEST_CASE("on can be called with const ref scheduler", "[adaptors][on]") { + const inline_scheduler sched; + auto snd = ex::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } -TEST_CASE("on forwards set_stopped calls", "[adaptors][on]") { - stopped_scheduler sched{}; - auto snd = ex::on(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); - // The receiver checks if we receive the stopped signal -} + TEST_CASE("on can be called with ref scheduler", "[adaptors][on]") { + inline_scheduler sched; + auto snd = ex::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } -TEST_CASE("on has the values_type corresponding to the given values", "[adaptors][on]") { - inline_scheduler sched{}; + TEST_CASE("on forwards set_error calls", "[adaptors][on]") { + error_scheduler sched{std::exception_ptr{}}; + auto snd = ex::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + // The receiver checks if we receive an error + } - check_val_types>>(ex::on(sched, ex::just(1))); - check_val_types>>(ex::on(sched, ex::just(3, 0.14))); - check_val_types>>( - ex::on(sched, ex::just(3, 0.14, std::string{"pi"}))); -} + TEST_CASE("on forwards set_error calls of other types", "[adaptors][on]") { + error_scheduler sched{std::string{"error"}}; + auto snd = ex::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"error"}}); + ex::start(op); + // The receiver checks if we receive an error + } -TEST_CASE("on keeps error_types from scheduler's sender", "[adaptors][on]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{43}; + TEST_CASE("on forwards set_stopped calls", "[adaptors][on]") { + stopped_scheduler sched{}; + auto snd = ex::on(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + // The receiver checks if we receive the stopped signal + } - check_err_types>(ex::on(sched1, ex::just(1))); - check_err_types>(ex::on(sched2, ex::just(2))); - check_err_types>(ex::on(sched3, ex::just(3))); -} + TEST_CASE("on has the values_type corresponding to the given values", "[adaptors][on]") { + inline_scheduler sched{}; -TEST_CASE("on keeps sends_stopped from scheduler's sender", "[adaptors][on]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - stopped_scheduler sched3{}; + check_val_types>>(ex::on(sched, ex::just(1))); + check_val_types>>(ex::on(sched, ex::just(3, 0.14))); + check_val_types>>( + ex::on(sched, ex::just(3, 0.14, std::string{"pi"}))); + } - check_sends_stopped(ex::on(sched1, ex::just(1))); - check_sends_stopped(ex::on(sched2, ex::just(2))); - check_sends_stopped(ex::on(sched3, ex::just(3))); -} + TEST_CASE("on keeps error_types from scheduler's sender", "[adaptors][on]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{43}; -// Return a different sender when we invoke this custom defined on implementation -using just_string_sender_t = decltype(ex::just(std::string{})); + check_err_types>(ex::on(sched1, ex::just(1))); + check_err_types>(ex::on(sched2, ex::just(2))); + check_err_types>(ex::on(sched3, ex::just(3))); + } -auto tag_invoke(decltype(ex::on), inline_scheduler sched, just_string_sender_t) { - return ex::just(std::string{"Hello, world!"}); -} + TEST_CASE("on keeps sends_stopped from scheduler's sender", "[adaptors][on]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + stopped_scheduler sched3{}; -TEST_CASE("on can be customized", "[adaptors][on]") { - // The customization will return a different value - auto snd = ex::on(inline_scheduler{}, ex::just(std::string{"world"})); - std::string res; - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); - ex::start(op); - REQUIRE(res == "Hello, world!"); -} + check_sends_stopped(ex::on(sched1, ex::just(1))); + check_sends_stopped(ex::on(sched2, ex::just(2))); + check_sends_stopped(ex::on(sched3, ex::just(3))); + } -struct move_checker { - bool valid; + // Return a different sender when we invoke this custom defined on implementation + using just_string_sender_t = decltype(ex::just(std::string{})); - move_checker() noexcept - : valid(true) { + auto tag_invoke(decltype(ex::on), inline_scheduler sched, just_string_sender_t) { + return ex::just(std::string{"Hello, world!"}); } - move_checker(const move_checker& other) noexcept { - REQUIRE(other.valid); - valid = true; + TEST_CASE("on can be customized", "[adaptors][on]") { + // The customization will return a different value + auto snd = ex::on(inline_scheduler{}, ex::just(std::string{"world"})); + std::string res; + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); + ex::start(op); + REQUIRE(res == "Hello, world!"); } - move_checker& operator=(const move_checker& other) noexcept { - REQUIRE(other.valid); - valid = true; - return *this; - } + struct move_checker { + bool valid; - move_checker(move_checker&& other) noexcept { - other.valid = false; - valid = true; - } + move_checker() noexcept + : valid(true) { + } - move_checker& operator=(move_checker&& other) noexcept { - other.valid = false; - valid = true; - return *this; - } -}; + move_checker(const move_checker& other) noexcept { + REQUIRE(other.valid); + valid = true; + } -struct move_checking_inline_scheduler { + move_checker& operator=(const move_checker& other) noexcept { + REQUIRE(other.valid); + valid = true; + return *this; + } - template - struct oper : immovable { - R recv_; + move_checker(move_checker&& other) noexcept { + other.valid = false; + valid = true; + } - friend void tag_invoke(ex::start_t, oper& self) noexcept { - ex::set_value((R&&) self.recv_); + move_checker& operator=(move_checker&& other) noexcept { + other.valid = false; + valid = true; + return *this; } }; - struct my_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures; + struct move_checking_inline_scheduler { template - friend oper tag_invoke(ex::connect_t, my_sender self, R&& r) { - return {{}, (R&&) r}; + struct oper : immovable { + R recv_; + + friend void tag_invoke(ex::start_t, oper& self) noexcept { + ex::set_value((R&&) self.recv_); + } + }; + + struct my_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures; + + template + friend oper tag_invoke(ex::connect_t, my_sender self, R&& r) { + return {{}, (R&&) r}; + } + + friend auto tag_invoke(ex::get_env_t, const my_sender&) noexcept + -> scheduler_env { + return {}; + } + }; + + friend my_sender tag_invoke(ex::schedule_t, move_checking_inline_scheduler) { + return {}; } - friend auto tag_invoke(ex::get_env_t, const my_sender&) noexcept - -> scheduler_env { - return {}; + friend bool + operator==(move_checking_inline_scheduler, move_checking_inline_scheduler) noexcept { + return true; } - }; - friend my_sender tag_invoke(ex::schedule_t, move_checking_inline_scheduler) { - return {}; - } + friend bool + operator!=(move_checking_inline_scheduler, move_checking_inline_scheduler) noexcept { + return false; + } - friend bool operator==(move_checking_inline_scheduler, move_checking_inline_scheduler) noexcept { - return true; - } + move_checker mc_; + }; - friend bool operator!=(move_checking_inline_scheduler, move_checking_inline_scheduler) noexcept { - return false; + TEST_CASE("on does not reference a moved-from scheduler", "[adaptors][on]") { + move_checking_inline_scheduler is; + ex::sender auto snd = ex::on(is, ex::just()) // + | ex::then([] {}); + ex::sync_wait(std::move(snd)); } - - move_checker mc_; -}; - -TEST_CASE("on does not reference a moved-from scheduler", "[adaptors][on]") { - move_checking_inline_scheduler is; - ex::sender auto snd = ex::on(is, ex::just()) // - | ex::then([] {}); - ex::sync_wait(std::move(snd)); } +STDEXEC_PRAGMA_POP() \ No newline at end of file diff --git a/test/stdexec/algos/adaptors/test_schedule_from.cpp b/test/stdexec/algos/adaptors/test_schedule_from.cpp index 9a6d79c36..d43854605 100644 --- a/test/stdexec/algos/adaptors/test_schedule_from.cpp +++ b/test/stdexec/algos/adaptors/test_schedule_from.cpp @@ -28,221 +28,228 @@ namespace ex = stdexec; using namespace std::chrono_literals; -TEST_CASE("schedule_from returns a sender", "[adaptors][schedule_from]") { - auto snd = ex::schedule_from(inline_scheduler{}, ex::just(13)); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("schedule_from with environment returns a sender", "[adaptors][schedule_from]") { - auto snd = ex::schedule_from(inline_scheduler{}, ex::just(13)); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("schedule_from simple example", "[adaptors][schedule_from]") { - auto snd = ex::schedule_from(inline_scheduler{}, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE( - "schedule_from calls the receiver when the scheduler dictates", - "[adaptors][schedule_from]") { - int recv_value{0}; - impulse_scheduler sched; - auto snd = ex::schedule_from(sched, ex::just(13)); - auto op = ex::connect(snd, expect_value_receiver_ex{recv_value}); - ex::start(op); - // Up until this point, the scheduler didn't start any task; no effect expected - CHECK(recv_value == 0); - - // Tell the scheduler to start executing one task - sched.start_next(); - CHECK(recv_value == 13); -} - -TEST_CASE( - "schedule_from calls the given sender when the scheduler dictates", - "[adaptors][schedule_from]") { - bool called{false}; - auto snd_base = ex::just() // - | ex::then([&]() -> int { - called = true; - return 19; - }); - - int recv_value{0}; - impulse_scheduler sched; - auto snd = ex::schedule_from(sched, std::move(snd_base)); - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); - ex::start(op); - // The sender is started, even if the scheduler hasn't yet triggered - CHECK(called); - // ... but didn't send the value to the receiver yet - CHECK(recv_value == 0); - - // Tell the scheduler to start executing one task - sched.start_next(); - - // Now the base sender is called, and a value is sent to the receiver - CHECK(called); - CHECK(recv_value == 19); -} - -TEST_CASE("schedule_from works when changing threads", "[adaptors][schedule_from]") { - exec::static_thread_pool pool{2}; - std::atomic called{false}; - { - // lunch some work on the thread pool - ex::sender auto snd = ex::schedule_from(pool.get_scheduler(), ex::just()) // - | ex::then([&] { called.store(true); }); - ex::start_detached(std::move(snd)); - } - // wait for the work to be executed, with timeout - // perform a poor-man's sync - // NOTE: it's a shame that the `join` method in static_thread_pool is not public - for (int i = 0; i < 1000 && !called.load(); i++) { - std::this_thread::sleep_for(1ms); - } - // the work should be executed - REQUIRE(called); -} - -struct non_default_constructible { - int x; - - non_default_constructible(int x) - : x(x) { - } - - friend bool - operator==(non_default_constructible const & lhs, non_default_constructible const & rhs) { - return lhs.x == rhs.x; - } -}; - -TEST_CASE("schedule_from can accept non-default constructible types", "[adaptors][schedule_from]") { - auto snd = ex::schedule_from(inline_scheduler{}, ex::just(non_default_constructible{13})); - auto op = ex::connect(std::move(snd), expect_value_receiver{non_default_constructible{13}}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("schedule_from can be called with rvalue ref scheduler", "[adaptors][schedule_from]") { - auto snd = ex::schedule_from(inline_scheduler{}, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("schedule_from can be called with const ref scheduler", "[adaptors][schedule_from]") { - const inline_scheduler sched; - auto snd = ex::schedule_from(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("schedule_from can be called with ref scheduler", "[adaptors][schedule_from]") { - inline_scheduler sched; - auto snd = ex::schedule_from(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("schedule_from forwards set_error calls", "[adaptors][schedule_from]") { - error_scheduler sched{std::exception_ptr{}}; - auto snd = ex::schedule_from(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); - // The receiver checks if we receive an error -} - -TEST_CASE("schedule_from forwards set_error calls of other types", "[adaptors][schedule_from]") { - error_scheduler sched{std::string{"error"}}; - auto snd = ex::schedule_from(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"error"}}); - ex::start(op); - // The receiver checks if we receive an error -} - -TEST_CASE("schedule_from forwards set_stopped calls", "[adaptors][schedule_from]") { - stopped_scheduler sched{}; - auto snd = ex::schedule_from(sched, ex::just(13)); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); - // The receiver checks if we receive the stopped signal -} - -TEST_CASE( - "schedule_from has the values_type corresponding to the given values", - "[adaptors][schedule_from]") { - inline_scheduler sched{}; - - check_val_types>>(ex::schedule_from(sched, ex::just(1))); - check_val_types>>( - ex::schedule_from(sched, ex::just(3, 0.14))); - check_val_types>>( - ex::schedule_from(sched, ex::just(3, 0.14, std::string{"pi"}))); -} - -TEST_CASE("schedule_from keeps error_types from scheduler's sender", "[adaptors][schedule_from]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{43}; - - check_err_types>(ex::schedule_from(sched1, ex::just(1))); - check_err_types>(ex::schedule_from(sched2, ex::just(2))); - check_err_types>(ex::schedule_from(sched3, ex::just(3))); -} - -TEST_CASE( - "schedule_from sends an exception_ptr if value types are potentially throwing when copied", - "[adaptors][schedule_from]") { - inline_scheduler sched{}; - - check_err_types>( - ex::schedule_from(sched, ex::just(potentially_throwing{}))); -} - -TEST_CASE( - "schedule_from keeps sends_stopped from scheduler's sender", - "[adaptors][schedule_from]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - stopped_scheduler sched3{}; - - check_sends_stopped(ex::schedule_from(sched1, ex::just(1))); - check_sends_stopped(ex::schedule_from(sched2, ex::just(2))); - check_sends_stopped(ex::schedule_from(sched3, ex::just(3))); -} - -using just_string_sender_t = decltype(ex::just(std::string{})); - -// Customization of schedule_from -// Return a different sender -auto tag_invoke(decltype(ex::schedule_from), inline_scheduler sched, just_string_sender_t) { - return ex::just(std::string{"hijacked"}); -} - -TEST_CASE("schedule_from can be customized", "[adaptors][schedule_from]") { - // The customization will return a different value - auto snd = ex::schedule_from(inline_scheduler{}, ex::just(std::string{"transfer"})); - auto op = ex::connect(std::move(snd), expect_value_receiver(std::string{"hijacked"})); - ex::start(op); -} - - -template -using any_sender_of = - typename exec::any_receiver_ref>::template any_sender<>; - -TEST_CASE("schedule_from can handle any_sender", "[adaptors][schedule_from]") { - auto snd = stdexec::schedule_from( - inline_scheduler{}, any_sender_of(ex::just(3))); - auto op = ex::connect(std::move(snd), expect_value_receiver(3)); - ex::start(op); -} +namespace { + + TEST_CASE("schedule_from returns a sender", "[adaptors][schedule_from]") { + auto snd = ex::schedule_from(inline_scheduler{}, ex::just(13)); + static_assert(ex::sender); + (void) snd; + } + + TEST_CASE("schedule_from with environment returns a sender", "[adaptors][schedule_from]") { + auto snd = ex::schedule_from(inline_scheduler{}, ex::just(13)); + static_assert(ex::sender_in); + (void) snd; + } + + TEST_CASE("schedule_from simple example", "[adaptors][schedule_from]") { + auto snd = ex::schedule_from(inline_scheduler{}, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE( + "schedule_from calls the receiver when the scheduler dictates", + "[adaptors][schedule_from]") { + int recv_value{0}; + impulse_scheduler sched; + auto snd = ex::schedule_from(sched, ex::just(13)); + auto op = ex::connect(snd, expect_value_receiver_ex{recv_value}); + ex::start(op); + // Up until this point, the scheduler didn't start any task; no effect expected + CHECK(recv_value == 0); + + // Tell the scheduler to start executing one task + sched.start_next(); + CHECK(recv_value == 13); + } + + TEST_CASE( + "schedule_from calls the given sender when the scheduler dictates", + "[adaptors][schedule_from]") { + bool called{false}; + auto snd_base = ex::just() // + | ex::then([&]() -> int { + called = true; + return 19; + }); + + int recv_value{0}; + impulse_scheduler sched; + auto snd = ex::schedule_from(sched, std::move(snd_base)); + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); + ex::start(op); + // The sender is started, even if the scheduler hasn't yet triggered + CHECK(called); + // ... but didn't send the value to the receiver yet + CHECK(recv_value == 0); + + // Tell the scheduler to start executing one task + sched.start_next(); + + // Now the base sender is called, and a value is sent to the receiver + CHECK(called); + CHECK(recv_value == 19); + } + + TEST_CASE("schedule_from works when changing threads", "[adaptors][schedule_from]") { + exec::static_thread_pool pool{2}; + std::atomic called{false}; + { + // lunch some work on the thread pool + ex::sender auto snd = ex::schedule_from(pool.get_scheduler(), ex::just()) // + | ex::then([&] { called.store(true); }); + ex::start_detached(std::move(snd)); + } + // wait for the work to be executed, with timeout + // perform a poor-man's sync + // NOTE: it's a shame that the `join` method in static_thread_pool is not public + for (int i = 0; i < 1000 && !called.load(); i++) { + std::this_thread::sleep_for(1ms); + } + // the work should be executed + REQUIRE(called); + } + + struct non_default_constructible { + int x; + + non_default_constructible(int x) + : x(x) { + } + + friend bool + operator==(non_default_constructible const & lhs, non_default_constructible const & rhs) { + return lhs.x == rhs.x; + } + }; + + TEST_CASE( + "schedule_from can accept non-default constructible types", + "[adaptors][schedule_from]") { + auto snd = ex::schedule_from(inline_scheduler{}, ex::just(non_default_constructible{13})); + auto op = ex::connect(std::move(snd), expect_value_receiver{non_default_constructible{13}}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("schedule_from can be called with rvalue ref scheduler", "[adaptors][schedule_from]") { + auto snd = ex::schedule_from(inline_scheduler{}, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("schedule_from can be called with const ref scheduler", "[adaptors][schedule_from]") { + const inline_scheduler sched; + auto snd = ex::schedule_from(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("schedule_from can be called with ref scheduler", "[adaptors][schedule_from]") { + inline_scheduler sched; + auto snd = ex::schedule_from(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("schedule_from forwards set_error calls", "[adaptors][schedule_from]") { + error_scheduler sched{std::exception_ptr{}}; + auto snd = ex::schedule_from(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + // The receiver checks if we receive an error + } + + TEST_CASE("schedule_from forwards set_error calls of other types", "[adaptors][schedule_from]") { + error_scheduler sched{std::string{"error"}}; + auto snd = ex::schedule_from(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"error"}}); + ex::start(op); + // The receiver checks if we receive an error + } + + TEST_CASE("schedule_from forwards set_stopped calls", "[adaptors][schedule_from]") { + stopped_scheduler sched{}; + auto snd = ex::schedule_from(sched, ex::just(13)); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + // The receiver checks if we receive the stopped signal + } + + TEST_CASE( + "schedule_from has the values_type corresponding to the given values", + "[adaptors][schedule_from]") { + inline_scheduler sched{}; + + check_val_types>>(ex::schedule_from(sched, ex::just(1))); + check_val_types>>( + ex::schedule_from(sched, ex::just(3, 0.14))); + check_val_types>>( + ex::schedule_from(sched, ex::just(3, 0.14, std::string{"pi"}))); + } + + TEST_CASE( + "schedule_from keeps error_types from scheduler's sender", + "[adaptors][schedule_from]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{43}; + + check_err_types>(ex::schedule_from(sched1, ex::just(1))); + check_err_types>(ex::schedule_from(sched2, ex::just(2))); + check_err_types>(ex::schedule_from(sched3, ex::just(3))); + } + + TEST_CASE( + "schedule_from sends an exception_ptr if value types are potentially throwing when copied", + "[adaptors][schedule_from]") { + inline_scheduler sched{}; + + check_err_types>( + ex::schedule_from(sched, ex::just(potentially_throwing{}))); + } + + TEST_CASE( + "schedule_from keeps sends_stopped from scheduler's sender", + "[adaptors][schedule_from]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + stopped_scheduler sched3{}; + + check_sends_stopped(ex::schedule_from(sched1, ex::just(1))); + check_sends_stopped(ex::schedule_from(sched2, ex::just(2))); + check_sends_stopped(ex::schedule_from(sched3, ex::just(3))); + } + + using just_string_sender_t = decltype(ex::just(std::string{})); + + // Customization of schedule_from + // Return a different sender + auto tag_invoke(decltype(ex::schedule_from), inline_scheduler sched, just_string_sender_t) { + return ex::just(std::string{"hijacked"}); + } + + TEST_CASE("schedule_from can be customized", "[adaptors][schedule_from]") { + // The customization will return a different value + auto snd = ex::schedule_from(inline_scheduler{}, ex::just(std::string{"transfer"})); + auto op = ex::connect(std::move(snd), expect_value_receiver(std::string{"hijacked"})); + ex::start(op); + } + + + template + using any_sender_of = + typename exec::any_receiver_ref>::template any_sender<>; + + TEST_CASE("schedule_from can handle any_sender", "[adaptors][schedule_from]") { + auto snd = stdexec::schedule_from( + inline_scheduler{}, any_sender_of(ex::just(3))); + auto op = ex::connect(std::move(snd), expect_value_receiver(3)); + ex::start(op); + } +} \ No newline at end of file diff --git a/test/stdexec/algos/adaptors/test_split.cpp b/test/stdexec/algos/adaptors/test_split.cpp index 46d7a055e..da7fa9152 100644 --- a/test/stdexec/algos/adaptors/test_split.cpp +++ b/test/stdexec/algos/adaptors/test_split.cpp @@ -28,473 +28,479 @@ namespace ex = stdexec; using namespace std::chrono_literals; -TEST_CASE("split returns a sender", "[adaptors][split]") { - auto snd = ex::split(ex::just(19)); - using Snd = decltype(snd); - static_assert(ex::enable_sender); - static_assert(ex::sender); - static_assert(ex::same_as, empty_env>); - (void) snd; -} - -TEST_CASE("split with environment returns a sender", "[adaptors][split]") { - auto snd = ex::split(ex::just(19)); - using Snd = decltype(snd); - static_assert(ex::enable_sender); - static_assert(ex::sender_in); - static_assert(ex::same_as, empty_env>); - (void) snd; -} +namespace { + + TEST_CASE("split returns a sender", "[adaptors][split]") { + auto snd = ex::split(ex::just(19)); + using Snd = decltype(snd); + static_assert(ex::enable_sender); + static_assert(ex::sender); + static_assert(ex::same_as, empty_env>); + (void) snd; + } -TEST_CASE("split simple example", "[adaptors][split]") { - auto snd = ex::split(ex::just(19)); - auto op1 = ex::connect(snd, expect_value_receiver{19}); - auto op2 = ex::connect(snd, expect_value_receiver{19}); - ex::start(op1); - ex::start(op2); - // The receiver will ensure that the right value is produced -} + TEST_CASE("split with environment returns a sender", "[adaptors][split]") { + auto snd = ex::split(ex::just(19)); + using Snd = decltype(snd); + static_assert(ex::enable_sender); + static_assert(ex::sender_in); + static_assert(ex::same_as, empty_env>); + (void) snd; + } -TEST_CASE("split executes predecessor sender once", "[adaptors][split]") { - SECTION("when parameters are passed") { - int counter{}; - auto snd = ex::split( - ex::just() // - | ex::then([&] { - counter++; - return counter; - })); - auto op1 = ex::connect(snd, expect_value_receiver{1}); - auto op2 = ex::connect(snd, expect_value_receiver{1}); - REQUIRE(counter == 0); + TEST_CASE("split simple example", "[adaptors][split]") { + auto snd = ex::split(ex::just(19)); + auto op1 = ex::connect(snd, expect_value_receiver{19}); + auto op2 = ex::connect(snd, expect_value_receiver{19}); ex::start(op1); - REQUIRE(counter == 1); ex::start(op2); // The receiver will ensure that the right value is produced + } - REQUIRE(counter == 1); + TEST_CASE("split executes predecessor sender once", "[adaptors][split]") { + SECTION("when parameters are passed") { + int counter{}; + auto snd = ex::split( + ex::just() // + | ex::then([&] { + counter++; + return counter; + })); + auto op1 = ex::connect(snd, expect_value_receiver{1}); + auto op2 = ex::connect(snd, expect_value_receiver{1}); + REQUIRE(counter == 0); + ex::start(op1); + REQUIRE(counter == 1); + ex::start(op2); + // The receiver will ensure that the right value is produced + + REQUIRE(counter == 1); + } + + SECTION("without parameters") { + int counter{}; + auto snd = ex::split(ex::just() | ex::then([&] { counter++; })); + stdexec::sync_wait(snd | ex::then([] {})); + stdexec::sync_wait(snd | ex::then([] {})); + REQUIRE(counter == 1); + } } - SECTION("without parameters") { - int counter{}; - auto snd = ex::split(ex::just() | ex::then([&] { counter++; })); - stdexec::sync_wait(snd | ex::then([] {})); - stdexec::sync_wait(snd | ex::then([] {})); - REQUIRE(counter == 1); + TEST_CASE("split passes lvalue references", "[adaptors][split]") { + auto split = ex::split(ex::just(42)); + using split_t = decltype(split); + using value_t = ex::value_types_of_t; + static_assert(std::is_same_v>>); + + auto then = split // + | ex::then([](const int& cval) { + int& val = const_cast(cval); + const int prev_val = val; + val /= 2; + return prev_val; + }); + + auto op1 = ex::connect(std::move(then), expect_value_receiver{42}); + auto op2 = ex::connect(split, expect_value_receiver{21}); + ex::start(op1); + ex::start(op2); + // The receiver will ensure that the right value is produced } -} -TEST_CASE("split passes lvalue references", "[adaptors][split]") { - auto split = ex::split(ex::just(42)); - using split_t = decltype(split); - using value_t = ex::value_types_of_t; - static_assert(std::is_same_v>>); - - auto then = split // - | ex::then([](const int& cval) { - int& val = const_cast(cval); - const int prev_val = val; - val /= 2; - return prev_val; - }); - - auto op1 = ex::connect(std::move(then), expect_value_receiver{42}); - auto op2 = ex::connect(split, expect_value_receiver{21}); - ex::start(op1); - ex::start(op2); - // The receiver will ensure that the right value is produced -} + TEST_CASE("split forwards errors", "[adaptors][split]") { + SECTION("of exception_ptr type") { + auto split = ex::split(ex::just_error(std::exception_ptr{})); + using split_t = decltype(split); + using error_t = ex::error_types_of_t; + static_assert(std::is_same_v>); -TEST_CASE("split forwards errors", "[adaptors][split]") { - SECTION("of exception_ptr type") { - auto split = ex::split(ex::just_error(std::exception_ptr{})); - using split_t = decltype(split); - using error_t = ex::error_types_of_t; - static_assert(std::is_same_v>); + auto op = ex::connect(split, expect_error_receiver{}); + ex::start(op); + // The receiver will ensure that the right value is produced + } - auto op = ex::connect(split, expect_error_receiver{}); - ex::start(op); - // The receiver will ensure that the right value is produced + SECTION("of any type") { + auto split = ex::split(ex::just_error(42)); + using split_t = decltype(split); + using error_t = ex::error_types_of_t; + static_assert(std::is_same_v>); + + auto op = ex::connect(split, expect_error_receiver{}); + ex::start(op); + } } - SECTION("of any type") { - auto split = ex::split(ex::just_error(42)); + TEST_CASE("split forwards stop signal", "[adaptors][split]") { + auto split = ex::split(ex::just_stopped()); using split_t = decltype(split); - using error_t = ex::error_types_of_t; - static_assert(std::is_same_v>); + static_assert(ex::sends_stopped); - auto op = ex::connect(split, expect_error_receiver{}); + auto op = ex::connect(split, expect_stopped_receiver{}); ex::start(op); + // The receiver will ensure that the right value is produced } -} - -TEST_CASE("split forwards stop signal", "[adaptors][split]") { - auto split = ex::split(ex::just_stopped()); - using split_t = decltype(split); - static_assert(ex::sends_stopped); - auto op = ex::connect(split, expect_stopped_receiver{}); - ex::start(op); - // The receiver will ensure that the right value is produced -} - -TEST_CASE("split forwards external stop signal (1)", "[adaptors][split]") { - stdexec::in_place_stop_source ssource; - bool called = false; - int counter{}; - auto split = ex::split(ex::just() | ex::then([&] { called = true; })); - auto sndr = exec::write( - ex::upon_stopped( - split, - [&] { - ++counter; - return 42; - }), - exec::with(ex::get_stop_token, ssource.get_token())); - auto op1 = ex::connect(sndr, expect_value_receiver{42}); - auto op2 = ex::connect(std::move(sndr), expect_value_receiver{42}); - ssource.request_stop(); - REQUIRE(counter == 0); - ex::start(op1); - REQUIRE(counter == 1); - REQUIRE(!called); - ex::start(op2); - REQUIRE(counter == 2); -} - -TEST_CASE("split forwards external stop signal (2)", "[adaptors][split]") { - stdexec::in_place_stop_source ssource; - bool called = false; - int counter{}; - auto split = ex::split( - ex::just() // - | ex::then([&] { - called = true; - return 7; - })); - auto sndr = exec::write( - ex::upon_stopped( - split, - [&] { - ++counter; - return 42; - }), - exec::with(ex::get_stop_token, ssource.get_token())); - auto op1 = ex::connect(sndr, expect_value_receiver{7}); - auto op2 = ex::connect(sndr, expect_value_receiver{7}); - REQUIRE(counter == 0); - ex::start(op1); // operation starts and finishes. - REQUIRE(counter == 0); - REQUIRE(called); - ssource.request_stop(); - ex::start(op2); // operation is done, result is delivered. - REQUIRE(counter == 0); -} + TEST_CASE("split forwards external stop signal (1)", "[adaptors][split]") { + stdexec::in_place_stop_source ssource; + bool called = false; + int counter{}; + auto split = ex::split(ex::just() | ex::then([&] { called = true; })); + auto sndr = exec::write( + ex::upon_stopped( + split, + [&] { + ++counter; + return 42; + }), + exec::with(ex::get_stop_token, ssource.get_token())); + auto op1 = ex::connect(sndr, expect_value_receiver{42}); + auto op2 = ex::connect(std::move(sndr), expect_value_receiver{42}); + ssource.request_stop(); + REQUIRE(counter == 0); + ex::start(op1); + REQUIRE(counter == 1); + REQUIRE(!called); + ex::start(op2); + REQUIRE(counter == 2); + } -TEST_CASE("split forwards external stop signal (3)", "[adaptors][split]") { - impulse_scheduler sched; - stdexec::in_place_stop_source ssource; - bool called = false; - int counter{}; - auto split = ex::split(ex::on( - sched, - ex::just() // + TEST_CASE("split forwards external stop signal (2)", "[adaptors][split]") { + stdexec::in_place_stop_source ssource; + bool called = false; + int counter{}; + auto split = ex::split( + ex::just() // | ex::then([&] { called = true; return 7; - }))); - auto sndr = exec::write( - ex::upon_stopped( - split, - [&] { - ++counter; - return 42; - }), - exec::with(ex::get_stop_token, ssource.get_token())); - auto op1 = ex::connect(sndr, expect_value_receiver{42}); - auto op2 = ex::connect(sndr, expect_value_receiver{42}); - REQUIRE(counter == 0); - ex::start(op1); // puts a unit of work on the impulse_scheduler and - // op1 into the list of waiting operations. - REQUIRE(counter == 0); - REQUIRE(!called); - ssource.request_stop(); - ex::start(op2); // puts op2 in the list of waiting operations. - REQUIRE(counter == 0); - REQUIRE(!called); - sched.start_next(); // Impulse scheduler notices stop has been requested - // and completes op1 with "stopped", which notifies - // all waiting states. - REQUIRE(counter == 2); - REQUIRE(!called); -} + })); + auto sndr = exec::write( + ex::upon_stopped( + split, + [&] { + ++counter; + return 42; + }), + exec::with(ex::get_stop_token, ssource.get_token())); + auto op1 = ex::connect(sndr, expect_value_receiver{7}); + auto op2 = ex::connect(sndr, expect_value_receiver{7}); + REQUIRE(counter == 0); + ex::start(op1); // operation starts and finishes. + REQUIRE(counter == 0); + REQUIRE(called); + ssource.request_stop(); + ex::start(op2); // operation is done, result is delivered. + REQUIRE(counter == 0); + } -TEST_CASE("split forwards external stop signal (4)", "[adaptors][split]") { - impulse_scheduler sched; - stdexec::in_place_stop_source ssource; - bool called = false; - int counter{}; - auto split = ex::split( - ex::just() // - | ex::then([&] { - called = true; - return 7; - })); - auto sndr1 = ex::on( - sched, - ex::upon_stopped(exec::write(split, exec::with(ex::get_stop_token, ssource.get_token())), [&] { - ++counter; - return 42; - })); - auto sndr2 = exec::write( - ex::on( + TEST_CASE("split forwards external stop signal (3)", "[adaptors][split]") { + impulse_scheduler sched; + stdexec::in_place_stop_source ssource; + bool called = false; + int counter{}; + auto split = ex::split(ex::on( sched, + ex::just() // + | ex::then([&] { + called = true; + return 7; + }))); + auto sndr = exec::write( ex::upon_stopped( split, [&] { ++counter; return 42; - })), - exec::with(ex::get_stop_token, ssource.get_token())); - auto op1 = ex::connect(sndr1, expect_value_receiver{7}); - auto op2 = ex::connect(sndr2, expect_stopped_receiver{}); - REQUIRE(counter == 0); - ex::start(op1); // puts a unit of work on the impulse_scheduler. - REQUIRE(counter == 0); - REQUIRE(!called); - sched.start_next(); // Impulse scheduler starts split sender, which - // completes with 7. - REQUIRE(counter == 0); - REQUIRE(called); - ex::start(op2); // puts another unit of work on the impulse_scheduler - REQUIRE(counter == 0); - ssource.request_stop(); - sched.start_next(); // Impulse scheduler notices stop has been - // requested and "stops" the work. - REQUIRE(counter == 0); -} - -TEST_CASE("split forwards results from a different thread", "[adaptors][split]") { - exec::static_thread_pool pool{1}; - auto split = ex::schedule(pool.get_scheduler()) // - | ex::then([] { - std::this_thread::sleep_for(1ms); - return 42; - }) - | ex::split(); - - auto [val] = stdexec::sync_wait(split).value(); - REQUIRE(val == 42); -} - -template -struct undef; - -TEST_CASE("split is thread-safe", "[adaptors][split]") { - exec::static_thread_pool pool{1}; + }), + exec::with(ex::get_stop_token, ssource.get_token())); + auto op1 = ex::connect(sndr, expect_value_receiver{42}); + auto op2 = ex::connect(sndr, expect_value_receiver{42}); + REQUIRE(counter == 0); + ex::start(op1); // puts a unit of work on the impulse_scheduler and + // op1 into the list of waiting operations. + REQUIRE(counter == 0); + REQUIRE(!called); + ssource.request_stop(); + ex::start(op2); // puts op2 in the list of waiting operations. + REQUIRE(counter == 0); + REQUIRE(!called); + sched.start_next(); // Impulse scheduler notices stop has been requested + // and completes op1 with "stopped", which notifies + // all waiting states. + REQUIRE(counter == 2); + REQUIRE(!called); + } - std::mt19937_64 eng{std::random_device{}()}; // or seed however you want - std::uniform_int_distribution<> dist{0, 1000}; + TEST_CASE("split forwards external stop signal (4)", "[adaptors][split]") { + impulse_scheduler sched; + stdexec::in_place_stop_source ssource; + bool called = false; + int counter{}; + auto split = ex::split( + ex::just() // + | ex::then([&] { + called = true; + return 7; + })); + auto sndr1 = ex::on( + sched, + ex::upon_stopped( + exec::write(split, exec::with(ex::get_stop_token, ssource.get_token())), [&] { + ++counter; + return 42; + })); + auto sndr2 = exec::write( + ex::on( + sched, + ex::upon_stopped( + split, + [&] { + ++counter; + return 42; + })), + exec::with(ex::get_stop_token, ssource.get_token())); + auto op1 = ex::connect(sndr1, expect_value_receiver{7}); + auto op2 = ex::connect(sndr2, expect_stopped_receiver{}); + REQUIRE(counter == 0); + ex::start(op1); // puts a unit of work on the impulse_scheduler. + REQUIRE(counter == 0); + REQUIRE(!called); + sched.start_next(); // Impulse scheduler starts split sender, which + // completes with 7. + REQUIRE(counter == 0); + REQUIRE(called); + ex::start(op2); // puts another unit of work on the impulse_scheduler + REQUIRE(counter == 0); + ssource.request_stop(); + sched.start_next(); // Impulse scheduler notices stop has been + // requested and "stops" the work. + REQUIRE(counter == 0); + } - auto split = ex::transfer_just(pool.get_scheduler(), std::chrono::microseconds{dist(eng)}) - | ex::then([](std::chrono::microseconds delay) { - std::this_thread::sleep_for(delay); - return 42; - }) - | ex::split(); + TEST_CASE("split forwards results from a different thread", "[adaptors][split]") { + exec::static_thread_pool pool{1}; + auto split = ex::schedule(pool.get_scheduler()) // + | ex::then([] { + std::this_thread::sleep_for(1ms); + return 42; + }) + | ex::split(); + + auto [val] = stdexec::sync_wait(split).value(); + REQUIRE(val == 42); + } - const unsigned n_threads = std::thread::hardware_concurrency(); - std::vector threads(n_threads); - std::vector thread_results(n_threads, 0); - const std::vector delays = [&] { - std::vector thread_delay(n_threads); + template + struct undef; + + TEST_CASE("split is thread-safe", "[adaptors][split]") { + exec::static_thread_pool pool{1}; + + std::mt19937_64 eng{std::random_device{}()}; // or seed however you want + std::uniform_int_distribution<> dist{0, 1000}; + + auto split = ex::transfer_just(pool.get_scheduler(), std::chrono::microseconds{dist(eng)}) + | ex::then([](std::chrono::microseconds delay) { + std::this_thread::sleep_for(delay); + return 42; + }) + | ex::split(); + + const unsigned n_threads = std::thread::hardware_concurrency(); + std::vector threads(n_threads); + std::vector thread_results(n_threads, 0); + const std::vector delays = [&] { + std::vector thread_delay(n_threads); + for (unsigned tid = 0; tid < n_threads; tid++) { + thread_delay[tid] = std::chrono::microseconds{dist(eng)}; + } + return thread_delay; + }(); for (unsigned tid = 0; tid < n_threads; tid++) { - thread_delay[tid] = std::chrono::microseconds{dist(eng)}; + threads[tid] = std::thread([&split, &delays, &thread_results, tid] { + inline_scheduler scheduler{}; + + std::this_thread::sleep_for(delays[tid]); + auto [val] = + stdexec::sync_wait(split | ex::transfer(scheduler) | ex::then([](int v) { return v; })) + .value(); + thread_results[tid] = val; + }); + } + for (unsigned tid = 0; tid < n_threads; tid++) { + threads[tid].join(); + REQUIRE(thread_results[tid] == 42); } - return thread_delay; - }(); - for (unsigned tid = 0; tid < n_threads; tid++) { - threads[tid] = std::thread([&split, &delays, &thread_results, tid] { - inline_scheduler scheduler{}; - - std::this_thread::sleep_for(delays[tid]); - auto [val] = - stdexec::sync_wait(split | ex::transfer(scheduler) | ex::then([](int v) { return v; })) - .value(); - thread_results[tid] = val; - }); - } - for (unsigned tid = 0; tid < n_threads; tid++) { - threads[tid].join(); - REQUIRE(thread_results[tid] == 42); } -} - -TEST_CASE("split can be an rvalue", "[adaptors][split]") { - auto [val] = - stdexec::sync_wait(ex::just(42) | ex::split() | ex::then([](int v) { return v; })).value(); - REQUIRE(val == 42); -} + TEST_CASE("split can be an rvalue", "[adaptors][split]") { + auto [val] = + stdexec::sync_wait(ex::just(42) | ex::split() | ex::then([](int v) { return v; })).value(); -struct move_only_type { - move_only_type() - : val(0) { + REQUIRE(val == 42); } - move_only_type(int v) - : val(v) { - } + struct move_only_type { + move_only_type() + : val(0) { + } - move_only_type(move_only_type&&) = default; - int val; -}; + move_only_type(int v) + : val(v) { + } -struct copy_and_movable_type { - copy_and_movable_type(int v) - : val(v) { - } + move_only_type(move_only_type&&) = default; + int val; + }; - int val; -}; + struct copy_and_movable_type { + copy_and_movable_type(int v) + : val(v) { + } -TEST_CASE("split into then", "[adaptors][split]") { - SECTION("split with move only input sender of temporary") { - auto snd = ex::split(ex::just(move_only_type{0})) | ex::then([](const move_only_type&) {}); - ex::sync_wait(snd); - } + int val; + }; - SECTION("split with move only input sender by moving in") { - auto snd0 = ex::just(move_only_type{}); - auto snd = ex::split(std::move(snd0)) | ex::then([](const move_only_type&) {}); - ex::sync_wait(snd); - } + TEST_CASE("split into then", "[adaptors][split]") { + SECTION("split with move only input sender of temporary") { + auto snd = ex::split(ex::just(move_only_type{0})) | ex::then([](const move_only_type&) {}); + ex::sync_wait(snd); + } - SECTION("split with copyable rvalue input sender") { - auto snd = ex::split(ex::just(copy_and_movable_type{0})) - | ex::then([](const copy_and_movable_type&) {}); - ex::sync_wait(snd); - } + SECTION("split with move only input sender by moving in") { + auto snd0 = ex::just(move_only_type{}); + auto snd = ex::split(std::move(snd0)) | ex::then([](const move_only_type&) {}); + ex::sync_wait(snd); + } - SECTION("split with copyable lvalue input sender") { - auto snd0 = ex::just(copy_and_movable_type{0}); - auto snd = ex::split(snd0) | ex::then([](const copy_and_movable_type&) {}); - ex::sync_wait(snd); - } + SECTION("split with copyable rvalue input sender") { + auto snd = ex::split(ex::just(copy_and_movable_type{0})) + | ex::then([](const copy_and_movable_type&) {}); + ex::sync_wait(snd); + } - SECTION("lvalue split move only sender") { - auto multishot = ex::split(ex::just(move_only_type{0})); - auto snd = multishot | ex::then([](const move_only_type&) {}); + SECTION("split with copyable lvalue input sender") { + auto snd0 = ex::just(copy_and_movable_type{0}); + auto snd = ex::split(snd0) | ex::then([](const copy_and_movable_type&) {}); + ex::sync_wait(snd); + } - REQUIRE(ex::sender_of); - REQUIRE(!ex::sender_of); - REQUIRE(!ex::sender_of); - REQUIRE(!ex::sender_of); + SECTION("lvalue split move only sender") { + auto multishot = ex::split(ex::just(move_only_type{0})); + auto snd = multishot | ex::then([](const move_only_type&) {}); - ex::sync_wait(snd); - } + REQUIRE(ex::sender_of); + REQUIRE(!ex::sender_of); + REQUIRE(!ex::sender_of); + REQUIRE(!ex::sender_of); - SECTION("lvalue split copyable sender") { - auto multishot = ex::split(ex::just(copy_and_movable_type{0})); - ex::get_completion_signatures_t{}(multishot, ex::__default_env{}); - auto snd = multishot | ex::then([](const copy_and_movable_type&) {}); + ex::sync_wait(snd); + } - REQUIRE(!ex::sender_of); - REQUIRE(!ex::sender_of); - REQUIRE(!ex::sender_of); - REQUIRE(ex::sender_of); - REQUIRE(!ex::sender_of); - REQUIRE(!ex::sender_of); + SECTION("lvalue split copyable sender") { + auto multishot = ex::split(ex::just(copy_and_movable_type{0})); + ex::get_completion_signatures_t{}(multishot, ex::__default_env{}); + auto snd = multishot | ex::then([](const copy_and_movable_type&) {}); - ex::sync_wait(snd); - } -} + REQUIRE(!ex::sender_of); + REQUIRE(!ex::sender_of); + REQUIRE(!ex::sender_of); + REQUIRE(ex::sender_of); + REQUIRE(!ex::sender_of); + REQUIRE(!ex::sender_of); -TEMPLATE_TEST_CASE( - "split move-only and copyable senders", - "[adaptors][split]", - move_only_type, - copy_and_movable_type) { - int called = 0; - auto multishot = ex::just(TestType(10)) // - | ex::then([&](TestType obj) { - ++called; - return TestType(obj.val + 1); - }) - | ex::split(); - auto wa = ex::when_all( - ex::then(multishot, [](const TestType& obj) { return obj.val; }), - ex::then(multishot, [](const TestType& obj) { return obj.val * 2; }), - ex::then(multishot, [](const TestType& obj) { return obj.val * 3; })); - - auto [v1, v2, v3] = stdexec::sync_wait(std::move(wa)).value(); - - REQUIRE(called == 1); - REQUIRE(v1 == 11); - REQUIRE(v2 == 22); - REQUIRE(v3 == 33); -} + ex::sync_wait(snd); + } + } -template -concept can_split_lvalue_of = requires(T t) { ex::split(t); }; + TEMPLATE_TEST_CASE( + "split move-only and copyable senders", + "[adaptors][split]", + move_only_type, + copy_and_movable_type) { + int called = 0; + auto multishot = ex::just(TestType(10)) // + | ex::then([&](TestType obj) { + ++called; + return TestType(obj.val + 1); + }) + | ex::split(); + auto wa = ex::when_all( + ex::then(multishot, [](const TestType& obj) { return obj.val; }), + ex::then(multishot, [](const TestType& obj) { return obj.val * 2; }), + ex::then(multishot, [](const TestType& obj) { return obj.val * 3; })); + + auto [v1, v2, v3] = stdexec::sync_wait(std::move(wa)).value(); + + REQUIRE(called == 1); + REQUIRE(v1 == 11); + REQUIRE(v2 == 22); + REQUIRE(v3 == 33); + } -TEST_CASE("split can only accept copyable lvalue input senders", "[adaptors][split]") { - static_assert(!can_split_lvalue_of); - static_assert(can_split_lvalue_of); -} + template + concept can_split_lvalue_of = requires(T t) { ex::split(t); }; -TEST_CASE("split into when_all", "[adaptors][split]") { - int counter{}; - auto snd = ex::split( - ex::just() // - | ex::then([&] { - counter++; - return counter; - })); - auto wa = ex::when_all( - snd | ex::then([](auto) { return 10; }), snd | ex::then([](auto) { return 20; })); - REQUIRE(counter == 0); - auto [v1, v2] = stdexec::sync_wait(std::move(wa)).value(); - REQUIRE(counter == 1); - REQUIRE(v1 == 10); - REQUIRE(v2 == 20); -} + TEST_CASE("split can only accept copyable lvalue input senders", "[adaptors][split]") { + static_assert(!can_split_lvalue_of); + static_assert(can_split_lvalue_of); + } -TEST_CASE("split can nest", "[adaptors][split]") { - auto split_1 = ex::just(42) | ex::split(); - auto split_2 = split_1 | ex::split(); - - auto [v1] = stdexec::sync_wait( - split_1 // - | ex::then([](const int& cv) { - int& v = const_cast(cv); - return v = 1; - })) - .value(); - - auto [v2] = stdexec::sync_wait( - split_2 // - | ex::then([](const int& cv) { - int& v = const_cast(cv); - return v = 2; - })) - .value(); - - auto [v3] = stdexec::sync_wait(split_1).value(); - - REQUIRE(v1 == 1); - REQUIRE(v2 == 2); - REQUIRE(v3 == 1); -} + TEST_CASE("split into when_all", "[adaptors][split]") { + int counter{}; + auto snd = ex::split( + ex::just() // + | ex::then([&] { + counter++; + return counter; + })); + auto wa = ex::when_all( + snd | ex::then([](auto) { return 10; }), snd | ex::then([](auto) { return 20; })); + REQUIRE(counter == 0); + auto [v1, v2] = stdexec::sync_wait(std::move(wa)).value(); + REQUIRE(counter == 1); + REQUIRE(v1 == 10); + REQUIRE(v2 == 20); + } -TEST_CASE("split doesn't advertise completion scheduler", "[adaptors][split]") { - inline_scheduler sched; + TEST_CASE("split can nest", "[adaptors][split]") { + auto split_1 = ex::just(42) | ex::split(); + auto split_2 = split_1 | ex::split(); + + auto [v1] = stdexec::sync_wait( + split_1 // + | ex::then([](const int& cv) { + int& v = const_cast(cv); + return v = 1; + })) + .value(); + + auto [v2] = stdexec::sync_wait( + split_2 // + | ex::then([](const int& cv) { + int& v = const_cast(cv); + return v = 2; + })) + .value(); + + auto [v3] = stdexec::sync_wait(split_1).value(); + + REQUIRE(v1 == 1); + REQUIRE(v2 == 2); + REQUIRE(v3 == 1); + } - auto snd = ex::transfer_just(sched, 42) | ex::split(); - using snd_t = decltype(snd); - static_assert(!stdexec::__callable, snd_t>); - static_assert(!stdexec::__callable, snd_t>); - static_assert( - !stdexec::__callable, snd_t>); - (void) snd; + TEST_CASE("split doesn't advertise completion scheduler", "[adaptors][split]") { + inline_scheduler sched; + + auto snd = ex::transfer_just(sched, 42) | ex::split(); + using snd_t = decltype(snd); + static_assert( + !stdexec::__callable, snd_t>); + static_assert( + !stdexec::__callable, snd_t>); + static_assert( + !stdexec::__callable, snd_t>); + (void) snd; + } } diff --git a/test/stdexec/algos/adaptors/test_stopped_as_error.cpp b/test/stdexec/algos/adaptors/test_stopped_as_error.cpp index 67b1596f5..f2c9e571d 100644 --- a/test/stdexec/algos/adaptors/test_stopped_as_error.cpp +++ b/test/stdexec/algos/adaptors/test_stopped_as_error.cpp @@ -22,124 +22,131 @@ namespace ex = stdexec; -TEST_CASE("stopped_as_error returns a sender", "[adaptors][stopped_as_error]") { - auto snd = ex::stopped_as_error(ex::just(11), -1); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("stopped_as_error with environment returns a sender", "[adaptors][stopped_as_error]") { - auto snd = ex::stopped_as_error(ex::just(11), -1); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("stopped_as_error simple example", "[adaptors][stopped_as_error]") { - stopped_scheduler sched; - auto snd = ex::stopped_as_error(ex::transfer_just(sched, 11), -1); - auto op = ex::connect(std::move(snd), expect_error_receiver{-1}); - ex::start(op); -} - -TEST_CASE("stopped_as_error can we piped", "[adaptors][stopped_as_error]") { - inline_scheduler sched; - ex::sender auto snd = ex::transfer_just(sched, 11) | ex::stopped_as_error(std::exception_ptr{}); - auto op = ex::connect(std::move(snd), expect_value_receiver{11}); - ex::start(op); -} - -TEST_CASE("stopped_as_error can work with `just_stopped`", "[adaptors][stopped_as_error]") { - ex::sender auto snd = ex::just_stopped() | ex::stopped_as_error(-1); - auto op = ex::connect(std::move(snd), expect_error_receiver{-1}); - ex::start(op); -} - -TEST_CASE("stopped_as_error can we waited on", "[adaptors][stopped_as_error]") { - inline_scheduler sched; - ex::sender auto snd = ex::transfer_just(sched, 11) | ex::stopped_as_error(std::exception_ptr{}); - wait_for_value(std::move(snd), 11); -} - -TEST_CASE("stopped_as_error using error_code error type", "[adaptors][stopped_as_error]") { - impulse_scheduler sched; // scheduler that can send stopped signals - ex::sender auto snd = ex::transfer_just(sched, 11) - | ex::stopped_as_error(std::error_code(1, std::generic_category())); - check_val_types>>(snd); - check_err_types>(snd); - check_sends_stopped(snd); - - auto op = ex::connect(std::move(snd), expect_value_receiver{11}); - ex::start(op); - sched.start_next(); -} - -TEST_CASE( - "stopped_as_error using error_code error type, for stopped signal", - "[adaptors][stopped_as_error]") { - stopped_scheduler sched; - std::error_code errcode(1, std::generic_category()); - ex::sender auto snd = ex::transfer_just(sched, 11) | ex::stopped_as_error(errcode); - check_val_types>>(snd); - check_err_types>(snd); - check_sends_stopped(snd); - - auto op = ex::connect(std::move(snd), expect_error_receiver{errcode}); - ex::start(op); -} - -// `stopped_as_error` is implemented in terms of `let_error`, so the tests for `let_error` cover -// more ground. - -TEST_CASE("stopped_as_error keeps values_type from input sender", "[adaptors][stopped_as_error]") { - inline_scheduler sched; - check_val_types>>( - ex::transfer_just(sched, 23) | ex::stopped_as_error(-1)); - check_val_types>>( - ex::transfer_just(sched, 3.1415) | ex::stopped_as_error(-1)); -} - -TEST_CASE("stopped_as_error keeps error_types from input sender", "[adaptors][stopped_as_error]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{-1}; - - check_err_types>( // - ex::transfer_just(sched1, 11) | ex::stopped_as_error(std::exception_ptr{})); - check_err_types>( // - ex::transfer_just(sched2, 13) | ex::stopped_as_error(std::exception_ptr{})); - - check_err_types>( // - ex::transfer_just(sched3, 13) | ex::stopped_as_error(std::exception_ptr{})); -} - -TEST_CASE("stopped_as_error can add more types to error_types", "[adaptors][stopped_as_error]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{-1}; - - check_err_types>( // - ex::transfer_just(sched1, 11) | ex::stopped_as_error(-1)); - check_err_types>( // - ex::transfer_just(sched2, 13) | ex::stopped_as_error(-1)); - - check_err_types>( // - ex::transfer_just(sched3, 13) | ex::stopped_as_error(-1)); - - check_err_types>( // - ex::transfer_just(sched1, 11) // - | ex::stopped_as_error(-1) // - | ex::stopped_as_error(std::string{"err"})); -} - -TEST_CASE("stopped_as_error overrides sends_stopped to false", "[adaptors][stopped_as_error]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - stopped_scheduler sched3{}; - - check_sends_stopped( // - ex::transfer_just(sched1, 1) | ex::stopped_as_error(-1)); - check_sends_stopped( // - ex::transfer_just(sched2, 2) | ex::stopped_as_error(-1)); - check_sends_stopped( // - ex::transfer_just(sched3, 3) | ex::stopped_as_error(-1)); +namespace { + + TEST_CASE("stopped_as_error returns a sender", "[adaptors][stopped_as_error]") { + auto snd = ex::stopped_as_error(ex::just(11), -1); + static_assert(ex::sender); + (void) snd; + } + + TEST_CASE("stopped_as_error with environment returns a sender", "[adaptors][stopped_as_error]") { + auto snd = ex::stopped_as_error(ex::just(11), -1); + static_assert(ex::sender_in); + (void) snd; + } + + TEST_CASE("stopped_as_error simple example", "[adaptors][stopped_as_error]") { + stopped_scheduler sched; + auto snd = ex::stopped_as_error(ex::transfer_just(sched, 11), -1); + auto op = ex::connect(std::move(snd), expect_error_receiver{-1}); + ex::start(op); + } + + TEST_CASE("stopped_as_error can we piped", "[adaptors][stopped_as_error]") { + inline_scheduler sched; + ex::sender auto snd = ex::transfer_just(sched, 11) | ex::stopped_as_error(std::exception_ptr{}); + auto op = ex::connect(std::move(snd), expect_value_receiver{11}); + ex::start(op); + } + + TEST_CASE("stopped_as_error can work with `just_stopped`", "[adaptors][stopped_as_error]") { + ex::sender auto snd = ex::just_stopped() | ex::stopped_as_error(-1); + auto op = ex::connect(std::move(snd), expect_error_receiver{-1}); + ex::start(op); + } + + TEST_CASE("stopped_as_error can we waited on", "[adaptors][stopped_as_error]") { + inline_scheduler sched; + ex::sender auto snd = ex::transfer_just(sched, 11) | ex::stopped_as_error(std::exception_ptr{}); + wait_for_value(std::move(snd), 11); + } + + TEST_CASE("stopped_as_error using error_code error type", "[adaptors][stopped_as_error]") { + impulse_scheduler sched; // scheduler that can send stopped signals + ex::sender auto snd = ex::transfer_just(sched, 11) + | ex::stopped_as_error(std::error_code(1, std::generic_category())); + check_val_types>>(snd); + check_err_types>(snd); + check_sends_stopped(snd); + + auto op = ex::connect(std::move(snd), expect_value_receiver{11}); + ex::start(op); + sched.start_next(); + } + + TEST_CASE( + "stopped_as_error using error_code error type, for stopped signal", + "[adaptors][stopped_as_error]") { + stopped_scheduler sched; + std::error_code errcode(1, std::generic_category()); + ex::sender auto snd = ex::transfer_just(sched, 11) | ex::stopped_as_error(errcode); + check_val_types>>(snd); + check_err_types>(snd); + check_sends_stopped(snd); + + auto op = ex::connect(std::move(snd), expect_error_receiver{errcode}); + ex::start(op); + } + + // `stopped_as_error` is implemented in terms of `let_error`, so the tests for `let_error` cover + // more ground. + + TEST_CASE( + "stopped_as_error keeps values_type from input sender", + "[adaptors][stopped_as_error]") { + inline_scheduler sched; + check_val_types>>( + ex::transfer_just(sched, 23) | ex::stopped_as_error(-1)); + check_val_types>>( + ex::transfer_just(sched, 3.1415) | ex::stopped_as_error(-1)); + } + + TEST_CASE( + "stopped_as_error keeps error_types from input sender", + "[adaptors][stopped_as_error]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{-1}; + + check_err_types>( // + ex::transfer_just(sched1, 11) | ex::stopped_as_error(std::exception_ptr{})); + check_err_types>( // + ex::transfer_just(sched2, 13) | ex::stopped_as_error(std::exception_ptr{})); + + check_err_types>( // + ex::transfer_just(sched3, 13) | ex::stopped_as_error(std::exception_ptr{})); + } + + TEST_CASE("stopped_as_error can add more types to error_types", "[adaptors][stopped_as_error]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{-1}; + + check_err_types>( // + ex::transfer_just(sched1, 11) | ex::stopped_as_error(-1)); + check_err_types>( // + ex::transfer_just(sched2, 13) | ex::stopped_as_error(-1)); + + check_err_types>( // + ex::transfer_just(sched3, 13) | ex::stopped_as_error(-1)); + + check_err_types>( // + ex::transfer_just(sched1, 11) // + | ex::stopped_as_error(-1) // + | ex::stopped_as_error(std::string{"err"})); + } + + TEST_CASE("stopped_as_error overrides sends_stopped to false", "[adaptors][stopped_as_error]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + stopped_scheduler sched3{}; + + check_sends_stopped( // + ex::transfer_just(sched1, 1) | ex::stopped_as_error(-1)); + check_sends_stopped( // + ex::transfer_just(sched2, 2) | ex::stopped_as_error(-1)); + check_sends_stopped( // + ex::transfer_just(sched3, 3) | ex::stopped_as_error(-1)); + } } diff --git a/test/stdexec/algos/adaptors/test_stopped_as_optional.cpp b/test/stdexec/algos/adaptors/test_stopped_as_optional.cpp index 94ccdee77..101be65aa 100644 --- a/test/stdexec/algos/adaptors/test_stopped_as_optional.cpp +++ b/test/stdexec/algos/adaptors/test_stopped_as_optional.cpp @@ -23,99 +23,102 @@ namespace ex = stdexec; -TEST_CASE("stopped_as_optional returns a sender", "[adaptors][stopped_as_optional]") { - auto snd = ex::stopped_as_optional(ex::just(11)); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE( - "stopped_as_optional with environment returns a sender", - "[adaptors][stopped_as_optional]") { - auto snd = ex::stopped_as_optional(ex::just(11)); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("stopped_as_optional simple example", "[adaptors][stopped_as_optional]") { - stopped_scheduler sched; - auto snd = ex::stopped_as_optional(ex::transfer_just(sched, 11)); - auto op = ex::connect(std::move(snd), expect_value_receiver{std::optional{}}); - ex::start(op); -} - -TEST_CASE("stopped_as_optional can we waited on", "[adaptors][stopped_as_optional]") { - ex::sender auto snd = ex::just(11) | ex::stopped_as_optional(); - wait_for_value(std::move(snd), std::optional{11}); -} - -TEST_CASE( - "stopped_as_optional shall not work with multi-value senders", - "[adaptors][stopped_as_optional]") { - auto snd = ex::just(3, 0.1415) | ex::stopped_as_optional(); - static_assert(!std::invocable>); -} - -TEST_CASE( - "stopped_as_optional shall not work with senders that have multiple alternatives", - "[adaptors][stopped_as_optional]") { - ex::sender auto in_snd = - fallible_just{13} // - | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"err"}); }); - check_val_types, type_array>>(in_snd); - auto snd = std::move(in_snd) | ex::stopped_as_optional(); - static_assert(!std::invocable>); -} - -TEST_CASE("stopped_as_optional forwards errors", "[adaptors][stopped_as_optional]") { - error_scheduler sched; - ex::sender auto snd = ex::transfer_just(sched, 13) | ex::stopped_as_optional(); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); -} - -TEST_CASE("stopped_as_optional doesn't forward cancellation", "[adaptors][stopped_as_optional]") { - stopped_scheduler sched; - ex::sender auto snd = ex::transfer_just(sched, 13) | ex::stopped_as_optional(); - wait_for_value(std::move(snd), std::optional{}); -} - -TEST_CASE( - "stopped_as_optional adds std::optional to values_type", - "[adaptors][stopped_as_optional]") { - check_val_types>>>( - ex::just(23) | ex::stopped_as_optional()); - check_val_types>>>( - ex::just(3.1415) | ex::stopped_as_optional()); -} - -TEST_CASE( - "stopped_as_optional keeps error_types from input sender", - "[adaptors][stopped_as_optional]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{-1}; - - check_err_types>( // - ex::transfer_just(sched1, 11) | ex::stopped_as_optional()); - check_err_types>( // - ex::transfer_just(sched2, 13) | ex::stopped_as_optional()); - - check_err_types>( // - ex::transfer_just(sched3, 13) | ex::stopped_as_optional()); -} - -TEST_CASE( - "stopped_as_optional overrides sends_stopped to false", - "[adaptors][stopped_as_optional]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - stopped_scheduler sched3{}; - - check_sends_stopped( // - ex::transfer_just(sched1, 1) | ex::stopped_as_optional()); - check_sends_stopped( // - ex::transfer_just(sched2, 2) | ex::stopped_as_optional()); - check_sends_stopped( // - ex::transfer_just(sched3, 3) | ex::stopped_as_optional()); +namespace { + + TEST_CASE("stopped_as_optional returns a sender", "[adaptors][stopped_as_optional]") { + auto snd = ex::stopped_as_optional(ex::just(11)); + static_assert(ex::sender); + (void) snd; + } + + TEST_CASE( + "stopped_as_optional with environment returns a sender", + "[adaptors][stopped_as_optional]") { + auto snd = ex::stopped_as_optional(ex::just(11)); + static_assert(ex::sender_in); + (void) snd; + } + + TEST_CASE("stopped_as_optional simple example", "[adaptors][stopped_as_optional]") { + stopped_scheduler sched; + auto snd = ex::stopped_as_optional(ex::transfer_just(sched, 11)); + auto op = ex::connect(std::move(snd), expect_value_receiver{std::optional{}}); + ex::start(op); + } + + TEST_CASE("stopped_as_optional can we waited on", "[adaptors][stopped_as_optional]") { + ex::sender auto snd = ex::just(11) | ex::stopped_as_optional(); + wait_for_value(std::move(snd), std::optional{11}); + } + + TEST_CASE( + "stopped_as_optional shall not work with multi-value senders", + "[adaptors][stopped_as_optional]") { + auto snd = ex::just(3, 0.1415) | ex::stopped_as_optional(); + static_assert(!std::invocable>); + } + + TEST_CASE( + "stopped_as_optional shall not work with senders that have multiple alternatives", + "[adaptors][stopped_as_optional]") { + ex::sender auto in_snd = + fallible_just{13} // + | ex::let_error([](std::exception_ptr) { return ex::just(std::string{"err"}); }); + check_val_types, type_array>>(in_snd); + auto snd = std::move(in_snd) | ex::stopped_as_optional(); + static_assert(!std::invocable>); + } + + TEST_CASE("stopped_as_optional forwards errors", "[adaptors][stopped_as_optional]") { + error_scheduler sched; + ex::sender auto snd = ex::transfer_just(sched, 13) | ex::stopped_as_optional(); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + } + + TEST_CASE("stopped_as_optional doesn't forward cancellation", "[adaptors][stopped_as_optional]") { + stopped_scheduler sched; + ex::sender auto snd = ex::transfer_just(sched, 13) | ex::stopped_as_optional(); + wait_for_value(std::move(snd), std::optional{}); + } + + TEST_CASE( + "stopped_as_optional adds std::optional to values_type", + "[adaptors][stopped_as_optional]") { + check_val_types>>>( + ex::just(23) | ex::stopped_as_optional()); + check_val_types>>>( + ex::just(3.1415) | ex::stopped_as_optional()); + } + + TEST_CASE( + "stopped_as_optional keeps error_types from input sender", + "[adaptors][stopped_as_optional]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{-1}; + + check_err_types>( // + ex::transfer_just(sched1, 11) | ex::stopped_as_optional()); + check_err_types>( // + ex::transfer_just(sched2, 13) | ex::stopped_as_optional()); + + check_err_types>( // + ex::transfer_just(sched3, 13) | ex::stopped_as_optional()); + } + + TEST_CASE( + "stopped_as_optional overrides sends_stopped to false", + "[adaptors][stopped_as_optional]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + stopped_scheduler sched3{}; + + check_sends_stopped( // + ex::transfer_just(sched1, 1) | ex::stopped_as_optional()); + check_sends_stopped( // + ex::transfer_just(sched2, 2) | ex::stopped_as_optional()); + check_sends_stopped( // + ex::transfer_just(sched3, 3) | ex::stopped_as_optional()); + } } diff --git a/test/stdexec/algos/adaptors/test_then.cpp b/test/stdexec/algos/adaptors/test_then.cpp index fcf4329d0..a82447288 100644 --- a/test/stdexec/algos/adaptors/test_then.cpp +++ b/test/stdexec/algos/adaptors/test_then.cpp @@ -25,191 +25,193 @@ namespace ex = stdexec; -TEST_CASE("then returns a sender", "[adaptors][then]") { - auto snd = ex::then(ex::just(), [] {}); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("then with environment returns a sender", "[adaptors][then]") { - auto snd = ex::then(ex::just(), [] {}); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("then simple example", "[adaptors][then]") { - bool called{false}; - auto snd = ex::then(ex::just(), [&] { called = true; }); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); - // The receiver checks that it's called - // we also check that the function was invoked - CHECK(called); -} - -TEST_CASE("then can be piped", "[adaptors][then]") { - ex::sender auto snd = ex::just() | ex::then([] {}); - (void) snd; -} +namespace { + TEST_CASE("then returns a sender", "[adaptors][then]") { + auto snd = ex::then(ex::just(), [] {}); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE("then returning void can we waited on", "[adaptors][then]") { - ex::sender auto snd = ex::just() | ex::then([] {}); - stdexec::sync_wait(std::move(snd)); -} + TEST_CASE("then with environment returns a sender", "[adaptors][then]") { + auto snd = ex::then(ex::just(), [] {}); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("then can be used to transform the value", "[adaptors][then]") { - auto snd = ex::just(13) | ex::then([](int x) -> int { return 2 * x + 1; }); - wait_for_value(std::move(snd), 27); -} + TEST_CASE("then simple example", "[adaptors][then]") { + bool called{false}; + auto snd = ex::then(ex::just(), [&] { called = true; }); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + // The receiver checks that it's called + // we also check that the function was invoked + CHECK(called); + } -TEST_CASE("then can be used to change the value type", "[adaptors][then]") { - auto snd = ex::just(3) | ex::then([](int x) -> double { return x + 0.1415; }); - wait_for_value(std::move(snd), 3.1415); -} + TEST_CASE("then can be piped", "[adaptors][then]") { + ex::sender auto snd = ex::just() | ex::then([] {}); + (void) snd; + } -TEST_CASE("then can be used with multiple parameters", "[adaptors][then]") { - auto snd = ex::just(3, 0.1415) | ex::then([](int x, double y) -> double { return x + y; }); - wait_for_value(std::move(snd), 3.1415); -} + TEST_CASE("then returning void can we waited on", "[adaptors][then]") { + ex::sender auto snd = ex::just() | ex::then([] {}); + stdexec::sync_wait(std::move(snd)); + } -TEST_CASE("then can throw, and set_error will be called", "[adaptors][then]") { - auto snd = ex::just(13) // - | ex::then([](int x) -> int { - throw std::logic_error{"err"}; - return x + 5; - }); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); -} + TEST_CASE("then can be used to transform the value", "[adaptors][then]") { + auto snd = ex::just(13) | ex::then([](int x) -> int { return 2 * x + 1; }); + wait_for_value(std::move(snd), 27); + } -TEST_CASE("then can be used with just_error", "[adaptors][then]") { - ex::sender auto snd = ex::just_error(std::string{"err"}) // - | ex::then([]() -> int { return 17; }); - auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"err"}}); - ex::start(op); -} + TEST_CASE("then can be used to change the value type", "[adaptors][then]") { + auto snd = ex::just(3) | ex::then([](int x) -> double { return x + 0.1415; }); + wait_for_value(std::move(snd), 3.1415); + } -TEST_CASE("then can be used with just_stopped", "[adaptors][then]") { - ex::sender auto snd = ex::just_stopped() | ex::then([]() -> int { return 17; }); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); -} + TEST_CASE("then can be used with multiple parameters", "[adaptors][then]") { + auto snd = ex::just(3, 0.1415) | ex::then([](int x, double y) -> double { return x + y; }); + wait_for_value(std::move(snd), 3.1415); + } -TEST_CASE("then function is not called on error", "[adaptors][then]") { - bool called{false}; - error_scheduler sched; - ex::sender auto snd = ex::transfer_just(sched, 13) // - | ex::then([&](int x) -> int { - called = true; - return x + 5; - }); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); - CHECK_FALSE(called); -} + TEST_CASE("then can throw, and set_error will be called", "[adaptors][then]") { + auto snd = ex::just(13) // + | ex::then([](int x) -> int { + throw std::logic_error{"err"}; + return x + 5; + }); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + } -TEST_CASE("then function is not called when cancelled", "[adaptors][then]") { - bool called{false}; - stopped_scheduler sched; - ex::sender auto snd = ex::transfer_just(sched, 13) // - | ex::then([&](int x) -> int { - called = true; - return x + 5; - }); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); - CHECK_FALSE(called); -} + TEST_CASE("then can be used with just_error", "[adaptors][then]") { + ex::sender auto snd = ex::just_error(std::string{"err"}) // + | ex::then([]() -> int { return 17; }); + auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"err"}}); + ex::start(op); + } -TEST_CASE("then advertises completion schedulers", "[adaptors][then]") { - inline_scheduler sched{}; + TEST_CASE("then can be used with just_stopped", "[adaptors][then]") { + ex::sender auto snd = ex::just_stopped() | ex::then([]() -> int { return 17; }); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + } - SECTION("for value channel") { - ex::sender auto snd = ex::schedule(sched) | ex::then([] {}); - REQUIRE(ex::get_completion_scheduler(ex::get_env(snd)) == sched); + TEST_CASE("then function is not called on error", "[adaptors][then]") { + bool called{false}; + error_scheduler sched; + ex::sender auto snd = ex::transfer_just(sched, 13) // + | ex::then([&](int x) -> int { + called = true; + return x + 5; + }); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + CHECK_FALSE(called); } - SECTION("for stop channel") { - ex::sender auto snd = ex::just_stopped() | ex::transfer(sched) | ex::then([] {}); - REQUIRE(ex::get_completion_scheduler(ex::get_env(snd)) == sched); + + TEST_CASE("then function is not called when cancelled", "[adaptors][then]") { + bool called{false}; + stopped_scheduler sched; + ex::sender auto snd = ex::transfer_just(sched, 13) // + | ex::then([&](int x) -> int { + called = true; + return x + 5; + }); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + CHECK_FALSE(called); } -} -TEST_CASE("then forwards env", "[adaptors][then]") { - SECTION("returns env by value") { - auto snd = just_with_env{value_env{100}, {0}} | ex::then([](int) {}); - static_assert(std::same_as); - CHECK(ex::get_env(snd).value == 100); + TEST_CASE("then advertises completion schedulers", "[adaptors][then]") { + inline_scheduler sched{}; + + SECTION("for value channel") { + ex::sender auto snd = ex::schedule(sched) | ex::then([] {}); + REQUIRE(ex::get_completion_scheduler(ex::get_env(snd)) == sched); + } + SECTION("for stop channel") { + ex::sender auto snd = ex::just_stopped() | ex::transfer(sched) | ex::then([] {}); + REQUIRE(ex::get_completion_scheduler(ex::get_env(snd)) == sched); + } } - SECTION("returns env by reference") { - auto snd = just_with_env{value_env{100}, {0}} | ex::then([](int) {}); - static_assert(std::same_as); - CHECK(ex::get_env(snd).value == 100); + TEST_CASE("then forwards env", "[adaptors][then]") { + SECTION("returns env by value") { + auto snd = just_with_env{value_env{100}, {0}} | ex::then([](int) {}); + static_assert(std::same_as); + CHECK(ex::get_env(snd).value == 100); + } + + SECTION("returns env by reference") { + auto snd = just_with_env{value_env{100}, {0}} | ex::then([](int) {}); + static_assert(std::same_as); + CHECK(ex::get_env(snd).value == 100); + } } -} -TEST_CASE("then has the values_type corresponding to the given values", "[adaptors][then]") { - check_val_types>>(ex::just() | ex::then([] { return 7; })); - check_val_types>>(ex::just() | ex::then([] { return 3.14; })); - check_val_types>>( - ex::just() | ex::then([] { return std::string{"hello"}; })); -} + TEST_CASE("then has the values_type corresponding to the given values", "[adaptors][then]") { + check_val_types>>(ex::just() | ex::then([] { return 7; })); + check_val_types>>(ex::just() | ex::then([] { return 3.14; })); + check_val_types>>( + ex::just() | ex::then([] { return std::string{"hello"}; })); + } -TEST_CASE("then keeps error_types from input sender", "[adaptors][then]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{43}; - - check_err_types>( // - ex::transfer_just(sched1) | ex::then([]() noexcept {})); - check_err_types>( // - ex::transfer_just(sched2) | ex::then([]() noexcept {})); - check_err_types>( // - ex::transfer_just(sched3) | ex::then([] {})); -} + TEST_CASE("then keeps error_types from input sender", "[adaptors][then]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{43}; + + check_err_types>( // + ex::transfer_just(sched1) | ex::then([]() noexcept {})); + check_err_types>( // + ex::transfer_just(sched2) | ex::then([]() noexcept {})); + check_err_types>( // + ex::transfer_just(sched3) | ex::then([] {})); + } -TEST_CASE("then keeps sends_stopped from input sender", "[adaptors][then]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - stopped_scheduler sched3{}; - - check_sends_stopped( // - ex::transfer_just(sched1) | ex::then([] {})); - check_sends_stopped( // - ex::transfer_just(sched2) | ex::then([] {})); - check_sends_stopped( // - ex::transfer_just(sched3) | ex::then([] {})); -} + TEST_CASE("then keeps sends_stopped from input sender", "[adaptors][then]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + stopped_scheduler sched3{}; + + check_sends_stopped( // + ex::transfer_just(sched1) | ex::then([] {})); + check_sends_stopped( // + ex::transfer_just(sched2) | ex::then([] {})); + check_sends_stopped( // + ex::transfer_just(sched3) | ex::then([] {})); + } -// Return a different sender when we invoke this custom defined on implementation -using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); + // Return a different sender when we invoke this custom defined on implementation + using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); -template -auto tag_invoke(ex::then_t, inline_scheduler sched, my_string_sender_t, Fun) { - return ex::just(std::string{"hallo"}); -} + template + auto tag_invoke(ex::then_t, inline_scheduler sched, my_string_sender_t, Fun) { + return ex::just(std::string{"hallo"}); + } -TEST_CASE("then can be customized early", "[adaptors][then]") { - // The customization will return a different value - auto snd = ex::transfer_just(inline_scheduler{}, std::string{"hello"}) // - | ex::then([](std::string x) { return x + ", world"; }); - wait_for_value(std::move(snd), std::string{"hallo"}); -} + TEST_CASE("then can be customized early", "[adaptors][then]") { + // The customization will return a different value + auto snd = ex::transfer_just(inline_scheduler{}, std::string{"hello"}) // + | ex::then([](std::string x) { return x + ", world"; }); + wait_for_value(std::move(snd), std::string{"hallo"}); + } -struct my_domain { - template Sender, class... Env> - static auto transform_sender(Sender snd, const Env&...) { - return ex::just(std::string{"hallo"}); + struct my_domain { + template Sender, class... Env> + static auto transform_sender(Sender snd, const Env&...) { + return ex::just(std::string{"hallo"}); + } + }; + + TEST_CASE("then can be customized late", "[adaptors][then]") { + // The customization will return a different value + basic_inline_scheduler sched; + auto snd = ex::just(std::string{"hello"}) + | exec::on( + sched, // + ex::then([](std::string x) { return x + ", world"; })) + | exec::write(exec::with(ex::get_scheduler, inline_scheduler())); + wait_for_value(std::move(snd), std::string{"hallo"}); } -}; - -TEST_CASE("then can be customized late", "[adaptors][then]") { - // The customization will return a different value - basic_inline_scheduler sched; - auto snd = ex::just(std::string{"hello"}) - | exec::on( - sched, // - ex::then([](std::string x) { return x + ", world"; })) - | exec::write(exec::with(ex::get_scheduler, inline_scheduler())); - wait_for_value(std::move(snd), std::string{"hallo"}); } diff --git a/test/stdexec/algos/adaptors/test_transfer.cpp b/test/stdexec/algos/adaptors/test_transfer.cpp index 71d3a169f..db6d73028 100644 --- a/test/stdexec/algos/adaptors/test_transfer.cpp +++ b/test/stdexec/algos/adaptors/test_transfer.cpp @@ -27,268 +27,272 @@ namespace ex = stdexec; using namespace std::chrono_literals; -TEST_CASE("transfer returns a sender", "[adaptors][transfer]") { - auto snd = ex::transfer(ex::just(13), inline_scheduler{}); - static_assert(ex::sender); - (void) snd; -} +namespace { -TEST_CASE("transfer with environment returns a sender", "[adaptors][transfer]") { - auto snd = ex::transfer(ex::just(13), inline_scheduler{}); - static_assert(ex::sender_in); - (void) snd; -} + TEST_CASE("transfer returns a sender", "[adaptors][transfer]") { + auto snd = ex::transfer(ex::just(13), inline_scheduler{}); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE("transfer simple example", "[adaptors][transfer]") { - auto snd = ex::transfer(ex::just(13), inline_scheduler{}); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} + TEST_CASE("transfer with environment returns a sender", "[adaptors][transfer]") { + auto snd = ex::transfer(ex::just(13), inline_scheduler{}); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("transfer can be piped", "[adaptors][transfer]") { - // Just transfer a value to the impulse scheduler - ex::scheduler auto sched = impulse_scheduler{}; - ex::sender auto snd = ex::just(13) | ex::transfer(sched); - // Start the operation - int res{0}; - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); - ex::start(op); - - // The value will be available when the scheduler will execute the next operation - REQUIRE(res == 0); - sched.start_next(); - REQUIRE(res == 13); -} + TEST_CASE("transfer simple example", "[adaptors][transfer]") { + auto snd = ex::transfer(ex::just(13), inline_scheduler{}); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } -TEST_CASE("transfer calls the receiver when the scheduler dictates", "[adaptors][transfer]") { - int recv_value{0}; - impulse_scheduler sched; - auto snd = ex::transfer(ex::just(13), sched); - auto op = ex::connect(snd, expect_value_receiver_ex{recv_value}); - ex::start(op); - // Up until this point, the scheduler didn't start any task; no effect expected - CHECK(recv_value == 0); - - // Tell the scheduler to start executing one task - sched.start_next(); - CHECK(recv_value == 13); -} + TEST_CASE("transfer can be piped", "[adaptors][transfer]") { + // Just transfer a value to the impulse scheduler + ex::scheduler auto sched = impulse_scheduler{}; + ex::sender auto snd = ex::just(13) | ex::transfer(sched); + // Start the operation + int res{0}; + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); + ex::start(op); + + // The value will be available when the scheduler will execute the next operation + REQUIRE(res == 0); + sched.start_next(); + REQUIRE(res == 13); + } -TEST_CASE("transfer calls the given sender when the scheduler dictates", "[adaptors][transfer]") { - bool called{false}; - auto snd_base = ex::just() // - | ex::then([&]() -> int { - called = true; - return 19; - }); - - int recv_value{0}; - impulse_scheduler sched; - auto snd = ex::transfer(std::move(snd_base), sched); - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); - ex::start(op); - // The sender is started, even if the scheduler hasn't yet triggered - CHECK(called); - // ... but didn't send the value to the receiver yet - CHECK(recv_value == 0); - - // Tell the scheduler to start executing one task - sched.start_next(); - - // Now the base sender is called, and a value is sent to the receiver - CHECK(called); - CHECK(recv_value == 19); -} + TEST_CASE("transfer calls the receiver when the scheduler dictates", "[adaptors][transfer]") { + int recv_value{0}; + impulse_scheduler sched; + auto snd = ex::transfer(ex::just(13), sched); + auto op = ex::connect(snd, expect_value_receiver_ex{recv_value}); + ex::start(op); + // Up until this point, the scheduler didn't start any task; no effect expected + CHECK(recv_value == 0); + + // Tell the scheduler to start executing one task + sched.start_next(); + CHECK(recv_value == 13); + } -TEST_CASE("transfer works when changing threads", "[adaptors][transfer]") { - exec::static_thread_pool pool{2}; - std::atomic called{false}; - { - // lunch some work on the thread pool - ex::sender auto snd = ex::transfer(ex::just(), pool.get_scheduler()) // - | ex::then([&] { called.store(true); }); - ex::start_detached(std::move(snd)); + TEST_CASE("transfer calls the given sender when the scheduler dictates", "[adaptors][transfer]") { + bool called{false}; + auto snd_base = ex::just() // + | ex::then([&]() -> int { + called = true; + return 19; + }); + + int recv_value{0}; + impulse_scheduler sched; + auto snd = ex::transfer(std::move(snd_base), sched); + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{recv_value}); + ex::start(op); + // The sender is started, even if the scheduler hasn't yet triggered + CHECK(called); + // ... but didn't send the value to the receiver yet + CHECK(recv_value == 0); + + // Tell the scheduler to start executing one task + sched.start_next(); + + // Now the base sender is called, and a value is sent to the receiver + CHECK(called); + CHECK(recv_value == 19); } - // wait for the work to be executed, with timeout - // perform a poor-man's sync - // NOTE: it's a shame that the `join` method in static_thread_pool is not public - for (int i = 0; i < 1000 && !called.load(); i++) { - std::this_thread::sleep_for(1ms); + + TEST_CASE("transfer works when changing threads", "[adaptors][transfer]") { + exec::static_thread_pool pool{2}; + std::atomic called{false}; + { + // lunch some work on the thread pool + ex::sender auto snd = ex::transfer(ex::just(), pool.get_scheduler()) // + | ex::then([&] { called.store(true); }); + ex::start_detached(std::move(snd)); + } + // wait for the work to be executed, with timeout + // perform a poor-man's sync + // NOTE: it's a shame that the `join` method in static_thread_pool is not public + for (int i = 0; i < 1000 && !called.load(); i++) { + std::this_thread::sleep_for(1ms); + } + // the work should be executed + REQUIRE(called); } - // the work should be executed - REQUIRE(called); -} -TEST_CASE("transfer can be called with rvalue ref scheduler", "[adaptors][transfer]") { - auto snd = ex::transfer(ex::just(13), inline_scheduler{}); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} + TEST_CASE("transfer can be called with rvalue ref scheduler", "[adaptors][transfer]") { + auto snd = ex::transfer(ex::just(13), inline_scheduler{}); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } -TEST_CASE("transfer can be called with const ref scheduler", "[adaptors][transfer]") { - const inline_scheduler sched; - auto snd = ex::transfer(ex::just(13), sched); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} + TEST_CASE("transfer can be called with const ref scheduler", "[adaptors][transfer]") { + const inline_scheduler sched; + auto snd = ex::transfer(ex::just(13), sched); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } -TEST_CASE("transfer can be called with ref scheduler", "[adaptors][transfer]") { - inline_scheduler sched; - auto snd = ex::transfer(ex::just(13), sched); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} + TEST_CASE("transfer can be called with ref scheduler", "[adaptors][transfer]") { + inline_scheduler sched; + auto snd = ex::transfer(ex::just(13), sched); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } -TEST_CASE("transfer forwards set_error calls", "[adaptors][transfer]") { - error_scheduler sched{std::exception_ptr{}}; - auto snd = ex::transfer(ex::just(13), sched); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); - // The receiver checks if we receive an error -} + TEST_CASE("transfer forwards set_error calls", "[adaptors][transfer]") { + error_scheduler sched{std::exception_ptr{}}; + auto snd = ex::transfer(ex::just(13), sched); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + // The receiver checks if we receive an error + } -TEST_CASE("transfer forwards set_error calls of other types", "[adaptors][transfer]") { - error_scheduler sched{std::string{"error"}}; - auto snd = ex::transfer(ex::just(13), sched); - auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"error"}}); - ex::start(op); - // The receiver checks if we receive an error -} + TEST_CASE("transfer forwards set_error calls of other types", "[adaptors][transfer]") { + error_scheduler sched{std::string{"error"}}; + auto snd = ex::transfer(ex::just(13), sched); + auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"error"}}); + ex::start(op); + // The receiver checks if we receive an error + } -TEST_CASE("transfer forwards set_stopped calls", "[adaptors][transfer]") { - stopped_scheduler sched{}; - auto snd = ex::transfer(ex::just(13), sched); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); - // The receiver checks if we receive the stopped signal -} + TEST_CASE("transfer forwards set_stopped calls", "[adaptors][transfer]") { + stopped_scheduler sched{}; + auto snd = ex::transfer(ex::just(13), sched); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + // The receiver checks if we receive the stopped signal + } -TEST_CASE( - "transfer has the values_type corresponding to the given values", - "[adaptors][transfer]") { - inline_scheduler sched{}; + TEST_CASE( + "transfer has the values_type corresponding to the given values", + "[adaptors][transfer]") { + inline_scheduler sched{}; - check_val_types>>(ex::transfer(ex::just(1), sched)); - check_val_types>>(ex::transfer(ex::just(3, 0.14), sched)); - check_val_types>>( - ex::transfer(ex::just(3, 0.14, std::string{"pi"}), sched)); -} + check_val_types>>(ex::transfer(ex::just(1), sched)); + check_val_types>>( + ex::transfer(ex::just(3, 0.14), sched)); + check_val_types>>( + ex::transfer(ex::just(3, 0.14, std::string{"pi"}), sched)); + } -TEST_CASE("transfer keeps error_types from scheduler's sender", "[adaptors][transfer]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{43}; + TEST_CASE("transfer keeps error_types from scheduler's sender", "[adaptors][transfer]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{43}; - check_err_types>(ex::transfer(ex::just(1), sched1)); - check_err_types>(ex::transfer(ex::just(2), sched2)); - check_err_types>(ex::transfer(ex::just(3), sched3)); -} + check_err_types>(ex::transfer(ex::just(1), sched1)); + check_err_types>(ex::transfer(ex::just(2), sched2)); + check_err_types>(ex::transfer(ex::just(3), sched3)); + } -TEST_CASE( - "transfer sends an exception_ptr if value types are potentially throwing when copied", - "[adaptors][transfer]") { - inline_scheduler sched{}; + TEST_CASE( + "transfer sends an exception_ptr if value types are potentially throwing when copied", + "[adaptors][transfer]") { + inline_scheduler sched{}; - check_err_types>( - ex::transfer(ex::just(potentially_throwing{}), sched)); -} + check_err_types>( + ex::transfer(ex::just(potentially_throwing{}), sched)); + } -TEST_CASE("transfer keeps sends_stopped from scheduler's sender", "[adaptors][transfer]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - stopped_scheduler sched3{}; + TEST_CASE("transfer keeps sends_stopped from scheduler's sender", "[adaptors][transfer]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + stopped_scheduler sched3{}; - check_sends_stopped(ex::transfer(ex::just(1), sched1)); - check_sends_stopped(ex::transfer(ex::just(2), sched2)); - check_sends_stopped(ex::transfer(ex::just(3), sched3)); -} + check_sends_stopped(ex::transfer(ex::just(1), sched1)); + check_sends_stopped(ex::transfer(ex::just(2), sched2)); + check_sends_stopped(ex::transfer(ex::just(3), sched3)); + } -struct val_type1 { - int val_; -}; + struct val_type1 { + int val_; + }; -struct val_type2 { - int val_; -}; + struct val_type2 { + int val_; + }; -struct val_type3 { - int val_; -}; + struct val_type3 { + int val_; + }; -using just_val1_sender_t = decltype(ex::just(val_type1{0})); -using just_val2_sender_t = decltype(ex::just(val_type2{0})); -using just_val3_sender_t = decltype(ex::transfer_just(impulse_scheduler{}, val_type3{0})); + using just_val1_sender_t = decltype(ex::just(val_type1{0})); + using just_val2_sender_t = decltype(ex::just(val_type2{0})); + using just_val3_sender_t = decltype(ex::transfer_just(impulse_scheduler{}, val_type3{0})); -// Customization of transfer -// Return a different sender when we invoke this custom defined transfer implementation -auto tag_invoke(decltype(ex::transfer), just_val1_sender_t, inline_scheduler sched) { - return ex::just(val_type1{53}); -} + // Customization of transfer + // Return a different sender when we invoke this custom defined transfer implementation + auto tag_invoke(decltype(ex::transfer), just_val1_sender_t, inline_scheduler sched) { + return ex::just(val_type1{53}); + } -// Customization of schedule_from -// Return a different sender when we invoke this custom defined transfer implementation -auto tag_invoke(decltype(ex::schedule_from), inline_scheduler sched, just_val2_sender_t) { - return ex::just(val_type2{59}); -} + // Customization of schedule_from + // Return a different sender when we invoke this custom defined transfer implementation + auto tag_invoke(decltype(ex::schedule_from), inline_scheduler sched, just_val2_sender_t) { + return ex::just(val_type2{59}); + } -// Customization of transfer with scheduler -// Return a different sender when we invoke this custom defined transfer implementation -auto tag_invoke( - decltype(ex::transfer), - impulse_scheduler /*sched_src*/, - just_val3_sender_t, - inline_scheduler sched_dest) { - return ex::transfer_just(sched_dest, val_type3{61}); -} + // Customization of transfer with scheduler + // Return a different sender when we invoke this custom defined transfer implementation + auto tag_invoke( + decltype(ex::transfer), + impulse_scheduler /*sched_src*/, + just_val3_sender_t, + inline_scheduler sched_dest) { + return ex::transfer_just(sched_dest, val_type3{61}); + } -TEST_CASE("transfer can be customized", "[adaptors][transfer]") { - // The customization will return a different value - auto snd = ex::transfer(ex::just(val_type1{1}), inline_scheduler{}); - val_type1 res{0}; - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); - ex::start(op); - REQUIRE(res.val_ == 53); -} + TEST_CASE("transfer can be customized", "[adaptors][transfer]") { + // The customization will return a different value + auto snd = ex::transfer(ex::just(val_type1{1}), inline_scheduler{}); + val_type1 res{0}; + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); + ex::start(op); + REQUIRE(res.val_ == 53); + } -TEST_CASE("transfer follows schedule_from customization", "[adaptors][transfer]") { - // The schedule_from customization will return a different value - auto snd = ex::transfer(ex::just(val_type2{2}), inline_scheduler{}); - val_type2 res{0}; - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); - ex::start(op); - REQUIRE(res.val_ == 59); -} + TEST_CASE("transfer follows schedule_from customization", "[adaptors][transfer]") { + // The schedule_from customization will return a different value + auto snd = ex::transfer(ex::just(val_type2{2}), inline_scheduler{}); + val_type2 res{0}; + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); + ex::start(op); + REQUIRE(res.val_ == 59); + } -TEST_CASE("transfer can be customized with two schedulers", "[adaptors][transfer]") { - // The customization will return a different value - ex::scheduler auto sched_src = impulse_scheduler{}; - ex::scheduler auto sched_dest = inline_scheduler{}; - auto snd = ex::transfer_just(sched_src, val_type3{1}) // - | ex::transfer(sched_dest); - val_type3 res{0}; - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); - ex::start(op); - // we are not using impulse_scheduler anymore, so the value should be available - REQUIRE(res.val_ == 61); -} + TEST_CASE("transfer can be customized with two schedulers", "[adaptors][transfer]") { + // The customization will return a different value + ex::scheduler auto sched_src = impulse_scheduler{}; + ex::scheduler auto sched_dest = inline_scheduler{}; + auto snd = ex::transfer_just(sched_src, val_type3{1}) // + | ex::transfer(sched_dest); + val_type3 res{0}; + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); + ex::start(op); + // we are not using impulse_scheduler anymore, so the value should be available + REQUIRE(res.val_ == 61); + } -struct test_domain { - template Sender, class... Env> - auto transform_sender(Sender&& sndr, Env&&... env) const { - return ex::just(std::string("hello")); + struct test_domain { + template Sender, class... Env> + auto transform_sender(Sender&& sndr, Env&&... env) const { + return ex::just(std::string("hello")); + } + }; + + TEST_CASE("transfer can be customized late", "[adaptors][transfer]") { + // The customization will return a different value + ex::scheduler auto sched = basic_inline_scheduler{}; + auto snd = ex::on(sched, ex::just() | ex::transfer(inline_scheduler{})); + std::string res; + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); + ex::start(op); + REQUIRE(res == "hello"); } -}; - -TEST_CASE("transfer can be customized late", "[adaptors][transfer]") { - // The customization will return a different value - ex::scheduler auto sched = basic_inline_scheduler{}; - auto snd = ex::on(sched, ex::just() | ex::transfer(inline_scheduler{})); - std::string res; - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); - ex::start(op); - REQUIRE(res == "hello"); } diff --git a/test/stdexec/algos/adaptors/test_transfer_when_all.cpp b/test/stdexec/algos/adaptors/test_transfer_when_all.cpp index 3378c9a26..382e5d4fe 100644 --- a/test/stdexec/algos/adaptors/test_transfer_when_all.cpp +++ b/test/stdexec/algos/adaptors/test_transfer_when_all.cpp @@ -28,274 +28,282 @@ namespace ex = stdexec; // For testing `transfer_when_all_with_variant`, we just check a couple of examples, check // customization, and we assume it's implemented in terms of `transfer_when_all`. -TEST_CASE("transfer_when_all returns a sender", "[adaptors][transfer_when_all]") { - auto snd = ex::transfer_when_all(inline_scheduler{}, ex::just(3), ex::just(0.1415)); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("transfer_when_all with environment returns a sender", "[adaptors][transfer_when_all]") { - auto snd = ex::transfer_when_all(inline_scheduler{}, ex::just(3), ex::just(0.1415)); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("transfer_when_all simple example", "[adaptors][transfer_when_all]") { - auto snd = ex::transfer_when_all(inline_scheduler{}, ex::just(3), ex::just(0.1415)); - auto snd1 = std::move(snd) | ex::then([](int x, double y) { return x + y; }); - auto op = ex::connect(std::move(snd1), expect_value_receiver{3.1415}); - ex::start(op); -} - -TEST_CASE("transfer_when_all with no senders", "[adaptors][transfer_when_all]") { - auto snd = ex::transfer_when_all(inline_scheduler{}); - auto op = ex::connect(std::move(snd), expect_void_receiver{}); - ex::start(op); -} - -TEST_CASE( - "transfer_when_all transfers the result when the scheduler dictates", - "[adaptors][transfer_when_all]") { - impulse_scheduler sched; - auto snd = ex::transfer_when_all(sched, ex::just(3), ex::just(0.1415)); - auto snd1 = std::move(snd) | ex::then([](int x, double y) { return x + y; }); - double res{0.0}; - auto op = ex::connect(std::move(snd1), expect_value_receiver_ex{res}); - ex::start(op); - CHECK(res == 0.0); - sched.start_next(); - CHECK(res == 3.1415); -} - -TEST_CASE( - "transfer_when_all with no senders transfers the result", - "[adaptors][transfer_when_all]") { - impulse_scheduler sched; - auto snd = ex::transfer_when_all(sched); - auto snd1 = std::move(snd) | ex::then([]() { return true; }); - bool res{false}; - auto op = ex::connect(std::move(snd1), expect_value_receiver_ex{res}); - ex::start(op); - CHECK(!res); - sched.start_next(); - CHECK(res); -} +namespace { -TEST_CASE("transfer_when_all_with_variant returns a sender", "[adaptors][transfer_when_all]") { - auto snd = ex::transfer_when_all_with_variant(inline_scheduler{}, ex::just(3), ex::just(0.1415)); - static_assert(ex::sender); - (void) snd; -} + TEST_CASE("transfer_when_all returns a sender", "[adaptors][transfer_when_all]") { + auto snd = ex::transfer_when_all(inline_scheduler{}, ex::just(3), ex::just(0.1415)); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE( - "transfer_when_all_with_variant with environment returns a sender", - "[adaptors][transfer_when_all]") { - auto snd = ex::transfer_when_all_with_variant(inline_scheduler{}, ex::just(3), ex::just(0.1415)); - static_assert(ex::sender_in); - (void) snd; -} + TEST_CASE( + "transfer_when_all with environment returns a sender", + "[adaptors][transfer_when_all]") { + auto snd = ex::transfer_when_all(inline_scheduler{}, ex::just(3), ex::just(0.1415)); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("transfer_when_all_with_variant basic example", "[adaptors][transfer_when_all]") { - ex::sender auto snd = ex::transfer_when_all_with_variant( // - inline_scheduler{}, // - ex::just(2), // - ex::just(3.14) // - ); - wait_for_value( - std::move(snd), std::variant>{2}, std::variant>{3.14}); -} + TEST_CASE("transfer_when_all simple example", "[adaptors][transfer_when_all]") { + auto snd = ex::transfer_when_all(inline_scheduler{}, ex::just(3), ex::just(0.1415)); + auto snd1 = std::move(snd) | ex::then([](int x, double y) { return x + y; }); + auto op = ex::connect(std::move(snd1), expect_value_receiver{3.1415}); + ex::start(op); + } -using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); + TEST_CASE("transfer_when_all with no senders", "[adaptors][transfer_when_all]") { + auto snd = ex::transfer_when_all(inline_scheduler{}); + auto op = ex::connect(std::move(snd), expect_void_receiver{}); + ex::start(op); + } -auto tag_invoke(ex::transfer_when_all_t, inline_scheduler, my_string_sender_t, my_string_sender_t) { - // Return a different sender when we invoke this custom defined on implementation - return ex::just(std::string{"first program"}); -} + TEST_CASE( + "transfer_when_all transfers the result when the scheduler dictates", + "[adaptors][transfer_when_all]") { + impulse_scheduler sched; + auto snd = ex::transfer_when_all(sched, ex::just(3), ex::just(0.1415)); + auto snd1 = std::move(snd) | ex::then([](int x, double y) { return x + y; }); + double res{0.0}; + auto op = ex::connect(std::move(snd1), expect_value_receiver_ex{res}); + ex::start(op); + CHECK(res == 0.0); + sched.start_next(); + CHECK(res == 3.1415); + } -TEST_CASE("transfer_when_all can be customized", "[adaptors][transfer_when_all]") { - // The customization will return a different value - auto snd = ex::transfer_when_all( // - inline_scheduler{}, // - ex::transfer_just(inline_scheduler{}, std::string{"hello,"}), // - ex::transfer_just(inline_scheduler{}, std::string{" world!"}) // - ); - wait_for_value(std::move(snd), std::string{"first program"}); -} + TEST_CASE( + "transfer_when_all with no senders transfers the result", + "[adaptors][transfer_when_all]") { + impulse_scheduler sched; + auto snd = ex::transfer_when_all(sched); + auto snd1 = std::move(snd) | ex::then([]() { return true; }); + bool res{false}; + auto op = ex::connect(std::move(snd1), expect_value_receiver_ex{res}); + ex::start(op); + CHECK(!res); + sched.start_next(); + CHECK(res); + } -auto tag_invoke( - ex::transfer_when_all_with_variant_t, - inline_scheduler, - my_string_sender_t, - my_string_sender_t) { - // Return a different sender when we invoke this custom defined on implementation - return ex::just(std::string{"first program"}); -} + TEST_CASE("transfer_when_all_with_variant returns a sender", "[adaptors][transfer_when_all]") { + auto snd = ex::transfer_when_all_with_variant( + inline_scheduler{}, ex::just(3), ex::just(0.1415)); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE("transfer_when_all_with_variant can be customized", "[adaptors][transfer_when_all]") { - // The customization will return a different value - auto snd = ex::transfer_when_all_with_variant( // - inline_scheduler{}, // - ex::transfer_just(inline_scheduler{}, std::string{"hello,"}), // - ex::transfer_just(inline_scheduler{}, std::string{" world!"}) // - ); - wait_for_value(std::move(snd), std::string{"first program"}); -} + TEST_CASE( + "transfer_when_all_with_variant with environment returns a sender", + "[adaptors][transfer_when_all]") { + auto snd = ex::transfer_when_all_with_variant( + inline_scheduler{}, ex::just(3), ex::just(0.1415)); + static_assert(ex::sender_in); + (void) snd; + } -namespace { - enum customize : std::size_t { - early, - late, - none - }; - - template - struct basic_domain { - template Sender, class... Env> - requires(sizeof...(Env) == C) - auto transform_sender(Sender&& sender, const Env&...) const { - return Fun(); - } - }; -} // anonymous namespace - -TEST_CASE("transfer_when_all works with custom domain", "[adaptors][transfer_when_all]") { - constexpr auto hello = [] { - return ex::just(std::string{"hello world"}); - }; - - SECTION("sender has correct domain") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; - - auto snd = ex::transfer_when_all( // - scheduler(), - ex::just(3), // - ex::just(0.1415) // + TEST_CASE("transfer_when_all_with_variant basic example", "[adaptors][transfer_when_all]") { + ex::sender auto snd = ex::transfer_when_all_with_variant( // + inline_scheduler{}, // + ex::just(2), // + ex::just(3.14) // ); - static_assert(ex::sender_expr_for); - [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + wait_for_value( + std::move(snd), std::variant>{2}, std::variant>{3.14}); } - SECTION("early customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; + using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); + + auto + tag_invoke(ex::transfer_when_all_t, inline_scheduler, my_string_sender_t, my_string_sender_t) { + // Return a different sender when we invoke this custom defined on implementation + return ex::just(std::string{"first program"}); + } - auto snd = ex::transfer_when_all( // - scheduler(), // - ex::just(3), // - ex::just(0.1415) // + TEST_CASE("transfer_when_all can be customized", "[adaptors][transfer_when_all]") { + // The customization will return a different value + auto snd = ex::transfer_when_all( // + inline_scheduler{}, // + ex::transfer_just(inline_scheduler{}, std::string{"hello,"}), // + ex::transfer_just(inline_scheduler{}, std::string{" world!"}) // ); - static_assert(ex::sender_expr_for); - wait_for_value(std::move(snd), std::string{"hello world"}); + wait_for_value(std::move(snd), std::string{"first program"}); } - SECTION("late customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; - - auto snd = ex::on( - scheduler(), - ex::transfer_when_all( // - inline_scheduler(), // - ex::just(3), // - ex::just(0.1415) // - )); - wait_for_value(std::move(snd), std::string{"hello world"}); + auto tag_invoke( + ex::transfer_when_all_with_variant_t, + inline_scheduler, + my_string_sender_t, + my_string_sender_t) { + // Return a different sender when we invoke this custom defined on implementation + return ex::just(std::string{"first program"}); } -} -TEST_CASE( - "transfer_when_all_with_variant works with custom domain", - "[adaptors][transfer_when_all]") { - constexpr auto hello = [] { - return ex::just(std::string{"hello world"}); - }; - - SECTION("sender has correct domain") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; - - auto snd = ex::transfer_when_all_with_variant( // - scheduler(), // - ex::just(3), // - ex::just(0.1415) // + TEST_CASE("transfer_when_all_with_variant can be customized", "[adaptors][transfer_when_all]") { + // The customization will return a different value + auto snd = ex::transfer_when_all_with_variant( // + inline_scheduler{}, // + ex::transfer_just(inline_scheduler{}, std::string{"hello,"}), // + ex::transfer_just(inline_scheduler{}, std::string{" world!"}) // ); - static_assert(ex::sender_expr_for); - [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + wait_for_value(std::move(snd), std::string{"first program"}); } - SECTION("early customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; + namespace { + enum customize : std::size_t { + early, + late, + none + }; + + template + struct basic_domain { + template Sender, class... Env> + requires(sizeof...(Env) == C) + auto transform_sender(Sender&& sender, const Env&...) const { + return Fun(); + } + }; + } // anonymous namespace + + TEST_CASE("transfer_when_all works with custom domain", "[adaptors][transfer_when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all( // + scheduler(), + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } - auto snd = ex::transfer_when_all_with_variant( // - scheduler(), - ex::just(3), // - ex::just(0.1415) // - ); - static_assert(ex::sender_expr_for); - wait_for_value(std::move(snd), std::string{"hello world"}); - } + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all( // + scheduler(), // + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } - SECTION("late customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; - - auto snd = ex::on( - scheduler(), - ex::transfer_when_all_with_variant( // - inline_scheduler(), // - ex::just(3), // - ex::just(0.1415) // - )); - wait_for_value(std::move(snd), std::string{"hello world"}); + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::transfer_when_all( // + inline_scheduler(), // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } } -} -TEST_CASE( - "transfer_when_all_with_variant finds transfer_when_all customizations", - "[adaptors][transfer_when_all]") { - constexpr auto hello = [] { - return ex::just(std::string{"hello world"}); - }; - - SECTION("sender has correct domain") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; - - auto snd = ex::transfer_when_all_with_variant( // - scheduler(), // - ex::just(3), // - ex::just(0.1415) // - ); - static_assert(ex::sender_expr_for); - [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); - } + TEST_CASE( + "transfer_when_all_with_variant works with custom domain", + "[adaptors][transfer_when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all_with_variant( // + scheduler(), // + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } - SECTION("early customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all_with_variant( // + scheduler(), + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } - auto snd = ex::transfer_when_all_with_variant( // - scheduler(), // - ex::just(3), // - ex::just(0.1415) // - ); - static_assert(ex::sender_expr_for); - wait_for_value(std::move(snd), std::string{"hello world"}); + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::transfer_when_all_with_variant( // + inline_scheduler(), // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } } - SECTION("late customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; - - auto snd = ex::on( - scheduler(), - ex::transfer_when_all_with_variant( // - inline_scheduler(), // - ex::just(3), // - ex::just(0.1415) // - )); - wait_for_value(std::move(snd), std::string{"hello world"}); + TEST_CASE( + "transfer_when_all_with_variant finds transfer_when_all customizations", + "[adaptors][transfer_when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all_with_variant( // + scheduler(), // + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } + + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::transfer_when_all_with_variant( // + scheduler(), // + ex::just(3), // + ex::just(0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } + + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::transfer_when_all_with_variant( // + inline_scheduler(), // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } } } diff --git a/test/stdexec/algos/adaptors/test_upon_error.cpp b/test/stdexec/algos/adaptors/test_upon_error.cpp index 2a96dd92a..32cc27c45 100644 --- a/test/stdexec/algos/adaptors/test_upon_error.cpp +++ b/test/stdexec/algos/adaptors/test_upon_error.cpp @@ -22,126 +22,129 @@ namespace ex = stdexec; -TEST_CASE("upon_error returns a sender", "[adaptors][upon_error]") { - auto snd = ex::upon_error(ex::just_error(std::exception_ptr{}), [](std::exception_ptr) {}); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("upon_error with environment returns a sender", "[adaptors][upon_error]") { - auto snd = ex::upon_error(ex::just_error(std::exception_ptr{}), [](std::exception_ptr) {}); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("upon_error simple example", "[adaptors][upon_error]") { - bool called{false}; - auto snd = ex::upon_error(ex::just_error(std::exception_ptr{}), [&](std::exception_ptr) { - called = true; - return 0; - }); - auto op = ex::connect(std::move(snd), expect_value_receiver{0}); - ex::start(op); - // The receiver checks that it's called - // we also check that the function was invoked - CHECK(called); -} +namespace { -TEST_CASE("upon_error with no-error input sender", "[adaptors][upon_error]") { - auto snd = ex::upon_error(ex::just(), []() -> double { return 0.0; }); - static_assert(ex::sender); - - using S = decltype(snd); - static_assert(ex::sender); - using completion_sigs = decltype(ex::get_completion_signatures(snd, ex::__default_env{})); - static_assert(std::same_as< completion_sigs, ex::completion_signatures< ex::set_value_t() >>); -} - -template -struct oper : immovable { - R recv_; - - friend void tag_invoke(ex::start_t, oper& self) noexcept { - ex::set_value((R&&) self.recv_, 0); + TEST_CASE("upon_error returns a sender", "[adaptors][upon_error]") { + auto snd = ex::upon_error(ex::just_error(std::exception_ptr{}), [](std::exception_ptr) {}); + static_assert(ex::sender); + (void) snd; } -}; - -struct Error1 { }; -struct Error2 { }; - -struct Error3 { }; - -struct Error4 { }; - -template -struct many_error_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures< - AdditionalCompletions..., - ex::set_error_t(Error1), - ex::set_error_t(Error2), - ex::set_error_t(Error3) >; - - template - friend oper tag_invoke(ex::connect_t, many_error_sender self, R&& r) { - return {{}, (R&&) r}; + TEST_CASE("upon_error with environment returns a sender", "[adaptors][upon_error]") { + auto snd = ex::upon_error(ex::just_error(std::exception_ptr{}), [](std::exception_ptr) {}); + static_assert(ex::sender_in); + (void) snd; } - friend auto tag_invoke(ex::get_env_t, const many_error_sender&) noexcept -> ex::empty_env { - return {}; - } -}; - -TEST_CASE("upon_error many input error types", "[adaptors][upon_error]") { - { - auto s = many_error_sender<>{} | ex::upon_error([](auto e) { - if constexpr (std::same_as) { - return Error4{}; - } else { - return e; - } - STDEXEC_UNREACHABLE(); - }); - - using S = decltype(s); - static_assert(ex::sender); - using completion_sigs = decltype(ex::get_completion_signatures(s, ex::__default_env{})); - static_assert( - std::same_as< - completion_sigs, - ex::completion_signatures< - ex::set_error_t(std::exception_ptr), - ex::set_value_t(Error1), - ex::set_value_t(Error2), - ex::set_value_t(Error4) >>); + TEST_CASE("upon_error simple example", "[adaptors][upon_error]") { + bool called{false}; + auto snd = ex::upon_error(ex::just_error(std::exception_ptr{}), [&](std::exception_ptr) { + called = true; + return 0; + }); + auto op = ex::connect(std::move(snd), expect_value_receiver{0}); + ex::start(op); + // The receiver checks that it's called + // we also check that the function was invoked + CHECK(called); } - { - auto s = many_error_sender{} | ex::upon_error([](auto e) { return 0; }); + TEST_CASE("upon_error with no-error input sender", "[adaptors][upon_error]") { + auto snd = ex::upon_error(ex::just(), []() -> double { return 0.0; }); + static_assert(ex::sender); - using S = decltype(s); + using S = decltype(snd); static_assert(ex::sender); - using completion_sigs = decltype(ex::get_completion_signatures(s, ex::__default_env{})); - static_assert( - std::same_as< - completion_sigs, - ex::completion_signatures< ex::set_error_t(std::exception_ptr), ex::set_value_t(int) >>); + using completion_sigs = decltype(ex::get_completion_signatures(snd, ex::__default_env{})); + static_assert(std::same_as< completion_sigs, ex::completion_signatures< ex::set_value_t() >>); } - { - auto s = many_error_sender{} - | ex::upon_error([](auto e) { return 0; }); - - using S = decltype(s); - static_assert(ex::sender); - using completion_sigs = decltype(ex::get_completion_signatures(s, ex::__default_env{})); - static_assert( - std::same_as< - completion_sigs, - ex::completion_signatures< - ex::set_error_t(std::exception_ptr), - ex::set_value_t(double), - ex::set_value_t(int) >>); + template + struct oper : immovable { + R recv_; + + friend void tag_invoke(ex::start_t, oper& self) noexcept { + ex::set_value((R&&) self.recv_, 0); + } + }; + + struct Error1 { }; + + struct Error2 { }; + + struct Error3 { }; + + struct Error4 { }; + + template + struct many_error_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures< + AdditionalCompletions..., + ex::set_error_t(Error1), + ex::set_error_t(Error2), + ex::set_error_t(Error3) >; + + template + friend oper tag_invoke(ex::connect_t, many_error_sender self, R&& r) { + return {{}, (R&&) r}; + } + + friend auto tag_invoke(ex::get_env_t, const many_error_sender&) noexcept -> ex::empty_env { + return {}; + } + }; + + TEST_CASE("upon_error many input error types", "[adaptors][upon_error]") { + { + auto s = many_error_sender<>{} | ex::upon_error([](auto e) { + if constexpr (std::same_as) { + return Error4{}; + } else { + return e; + } + STDEXEC_UNREACHABLE(); + }); + + using S = decltype(s); + static_assert(ex::sender); + using completion_sigs = decltype(ex::get_completion_signatures(s, ex::__default_env{})); + static_assert( + std::same_as< + completion_sigs, + ex::completion_signatures< + ex::set_error_t(std::exception_ptr), + ex::set_value_t(Error1), + ex::set_value_t(Error2), + ex::set_value_t(Error4) >>); + } + + { + auto s = many_error_sender{} | ex::upon_error([](auto e) { return 0; }); + + using S = decltype(s); + static_assert(ex::sender); + using completion_sigs = decltype(ex::get_completion_signatures(s, ex::__default_env{})); + static_assert( + std::same_as< + completion_sigs, + ex::completion_signatures< ex::set_error_t(std::exception_ptr), ex::set_value_t(int) >>); + } + + { + auto s = many_error_sender{} + | ex::upon_error([](auto e) { return 0; }); + + using S = decltype(s); + static_assert(ex::sender); + using completion_sigs = decltype(ex::get_completion_signatures(s, ex::__default_env{})); + static_assert( + std::same_as< + completion_sigs, + ex::completion_signatures< + ex::set_error_t(std::exception_ptr), + ex::set_value_t(double), + ex::set_value_t(int) >>); + } } } diff --git a/test/stdexec/algos/adaptors/test_upon_stopped.cpp b/test/stdexec/algos/adaptors/test_upon_stopped.cpp index ffecf9265..cee130168 100644 --- a/test/stdexec/algos/adaptors/test_upon_stopped.cpp +++ b/test/stdexec/algos/adaptors/test_upon_stopped.cpp @@ -22,28 +22,31 @@ namespace ex = stdexec; -// TODO: implement upon_stopped -TEST_CASE("upon_stopped returns a sender", "[adaptors][upon_stopped]") { - auto snd = ex::upon_stopped(ex::just_stopped(), []() {}); - static_assert(ex::sender); - (void) snd; -} +namespace { -TEST_CASE("upon_stopped with environment returns a sender", "[adaptors][upon_stopped]") { - auto snd = ex::upon_stopped(ex::just_stopped(), []() {}); - static_assert(ex::sender_in); - (void) snd; -} + // TODO: implement upon_stopped + TEST_CASE("upon_stopped returns a sender", "[adaptors][upon_stopped]") { + auto snd = ex::upon_stopped(ex::just_stopped(), []() {}); + static_assert(ex::sender); + (void) snd; + } + + TEST_CASE("upon_stopped with environment returns a sender", "[adaptors][upon_stopped]") { + auto snd = ex::upon_stopped(ex::just_stopped(), []() {}); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("upon_stopped simple example", "[adaptors][upon_stopped]") { - bool called{false}; - auto snd = ex::upon_stopped(ex::just_stopped(), [&]() { - called = true; - return 0; - }); - auto op = ex::connect(std::move(snd), expect_value_receiver{0}); - ex::start(op); - // The receiver checks that it's called - // we also check that the function was invoked - CHECK(called); + TEST_CASE("upon_stopped simple example", "[adaptors][upon_stopped]") { + bool called{false}; + auto snd = ex::upon_stopped(ex::just_stopped(), [&]() { + called = true; + return 0; + }); + auto op = ex::connect(std::move(snd), expect_value_receiver{0}); + ex::start(op); + // The receiver checks that it's called + // we also check that the function was invoked + CHECK(called); + } } diff --git a/test/stdexec/algos/adaptors/test_when_all.cpp b/test/stdexec/algos/adaptors/test_when_all.cpp index 5edd31585..45de8ffea 100644 --- a/test/stdexec/algos/adaptors/test_when_all.cpp +++ b/test/stdexec/algos/adaptors/test_when_all.cpp @@ -25,508 +25,511 @@ namespace ex = stdexec; // For testing `when_all_with_variant`, we just check a couple of examples, check customization, and // we assume it's implemented in terms of `when_all`. -TEST_CASE("when_all returns a sender", "[adaptors][when_all]") { - auto snd = ex::when_all(ex::just(3), ex::just(0.1415)); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("when_all with environment returns a sender", "[adaptors][when_all]") { - auto snd = ex::when_all(ex::just(3), ex::just(0.1415)); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("when_all simple example", "[adaptors][when_all]") { - auto snd = ex::when_all(ex::just(3), ex::just(0.1415)); - auto snd1 = std::move(snd) | ex::then([](int x, double y) { return x + y; }); - auto op = ex::connect(std::move(snd1), expect_value_receiver{3.1415}); - ex::start(op); -} +namespace { -TEST_CASE("when_all returning two values can we waited on", "[adaptors][when_all]") { - ex::sender auto snd = ex::when_all( // - ex::just(2), // - ex::just(3) // - ); - wait_for_value(std::move(snd), 2, 3); -} + TEST_CASE("when_all returns a sender", "[adaptors][when_all]") { + auto snd = ex::when_all(ex::just(3), ex::just(0.1415)); + static_assert(ex::sender); + (void) snd; + } -TEST_CASE("when_all with 5 senders", "[adaptors][when_all]") { - ex::sender auto snd = ex::when_all( // - ex::just(2), // - ex::just(3), // - ex::just(5), // - ex::just(7), // - ex::just(11) // - ); - wait_for_value(std::move(snd), 2, 3, 5, 7, 11); -} + TEST_CASE("when_all with environment returns a sender", "[adaptors][when_all]") { + auto snd = ex::when_all(ex::just(3), ex::just(0.1415)); + static_assert(ex::sender_in); + (void) snd; + } -TEST_CASE("when_all with just one sender", "[adaptors][when_all]") { - ex::sender auto snd = ex::when_all( // - ex::just(2) // - ); - wait_for_value(std::move(snd), 2); -} + TEST_CASE("when_all simple example", "[adaptors][when_all]") { + auto snd = ex::when_all(ex::just(3), ex::just(0.1415)); + auto snd1 = std::move(snd) | ex::then([](int x, double y) { return x + y; }); + auto op = ex::connect(std::move(snd1), expect_value_receiver{3.1415}); + ex::start(op); + } -TEST_CASE("when_all with move-only types", "[adaptors][when_all]") { - ex::sender auto snd = ex::when_all( // - ex::just(movable(2)) // - ); - wait_for_value(std::move(snd), movable(2)); -} + TEST_CASE("when_all returning two values can we waited on", "[adaptors][when_all]") { + ex::sender auto snd = ex::when_all( // + ex::just(2), // + ex::just(3) // + ); + wait_for_value(std::move(snd), 2, 3); + } -TEST_CASE("when_all with no senders", "[adaptors][when_all]") { - ex::sender auto snd = ex::when_all(); - wait_for_value(std::move(snd)); -} + TEST_CASE("when_all with 5 senders", "[adaptors][when_all]") { + ex::sender auto snd = ex::when_all( // + ex::just(2), // + ex::just(3), // + ex::just(5), // + ex::just(7), // + ex::just(11) // + ); + wait_for_value(std::move(snd), 2, 3, 5, 7, 11); + } -TEST_CASE("when_all when one sender sends void", "[adaptors][when_all]") { - ex::sender auto snd = ex::when_all( // - ex::just(2), // - ex::just() // - ); - wait_for_value(std::move(snd), 2); -} + TEST_CASE("when_all with just one sender", "[adaptors][when_all]") { + ex::sender auto snd = ex::when_all( // + ex::just(2) // + ); + wait_for_value(std::move(snd), 2); + } -TEST_CASE("when_all_with_variant basic example", "[adaptors][when_all]") { - ex::sender auto snd = ex::when_all_with_variant( // - ex::just(2), // - ex::just(3.14) // - ); - wait_for_value( - std::move(snd), std::variant>{2}, std::variant>{3.14}); -} + TEST_CASE("when_all with move-only types", "[adaptors][when_all]") { + ex::sender auto snd = ex::when_all( // + ex::just(movable(2)) // + ); + wait_for_value(std::move(snd), movable(2)); + } -TEST_CASE("when_all_with_variant with same type", "[adaptors][when_all]") { - ex::sender auto snd = ex::when_all_with_variant( // - ex::just(2), // - ex::just(3) // - ); - wait_for_value( - std::move(snd), std::variant>{2}, std::variant>{3}); -} + TEST_CASE("when_all with no senders", "[adaptors][when_all]") { + ex::sender auto snd = ex::when_all(); + wait_for_value(std::move(snd)); + } -TEST_CASE("when_all_with_variant with no senders", "[adaptors][when_all]") { - ex::sender auto snd = ex::when_all_with_variant(); - wait_for_value(std::move(snd)); -} + TEST_CASE("when_all when one sender sends void", "[adaptors][when_all]") { + ex::sender auto snd = ex::when_all( // + ex::just(2), // + ex::just() // + ); + wait_for_value(std::move(snd), 2); + } -TEST_CASE("when_all completes when children complete", "[adaptors][when_all]") { - impulse_scheduler sched; - bool called{false}; - ex::sender auto snd = // - ex::when_all( // - ex::transfer_just(sched, 11), // - ex::transfer_just(sched, 13), // - ex::transfer_just(sched, 17) // - ) // - | ex::then([&](int a, int b, int c) { - called = true; - return a + b + c; - }); - auto op = ex::connect(std::move(snd), expect_value_receiver{41}); - ex::start(op); - // The when_all scheduler will complete only after 3 impulses - CHECK_FALSE(called); - sched.start_next(); - CHECK_FALSE(called); - sched.start_next(); - CHECK_FALSE(called); - sched.start_next(); - CHECK(called); -} + TEST_CASE("when_all_with_variant basic example", "[adaptors][when_all]") { + ex::sender auto snd = ex::when_all_with_variant( // + ex::just(2), // + ex::just(3.14) // + ); + wait_for_value( + std::move(snd), std::variant>{2}, std::variant>{3.14}); + } -TEST_CASE("when_all can be used with just_*", "[adaptors][when_all]") { - ex::sender auto snd = ex::when_all( // - ex::just(2), // - ex::just_error(std::exception_ptr{}), // - ex::just_stopped() // - ); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); -} + TEST_CASE("when_all_with_variant with same type", "[adaptors][when_all]") { + ex::sender auto snd = ex::when_all_with_variant( // + ex::just(2), // + ex::just(3) // + ); + wait_for_value( + std::move(snd), std::variant>{2}, std::variant>{3}); + } -TEST_CASE( - "when_all terminates with error if one child terminates with error", - "[adaptors][when_all]") { - error_scheduler sched; - ex::sender auto snd = ex::when_all( // - ex::just(2), // - ex::transfer_just(sched, 5), // - ex::just(7) // - ); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); -} + TEST_CASE("when_all_with_variant with no senders", "[adaptors][when_all]") { + ex::sender auto snd = ex::when_all_with_variant(); + wait_for_value(std::move(snd)); + } -TEST_CASE("when_all terminates with stopped if one child is cancelled", "[adaptors][when_all]") { - stopped_scheduler sched; - ex::sender auto snd = ex::when_all( // - ex::just(2), // - ex::transfer_just(sched, 5), // - ex::just(7) // - ); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); -} + TEST_CASE("when_all completes when children complete", "[adaptors][when_all]") { + impulse_scheduler sched; + bool called{false}; + ex::sender auto snd = // + ex::when_all( // + ex::transfer_just(sched, 11), // + ex::transfer_just(sched, 13), // + ex::transfer_just(sched, 17) // + ) // + | ex::then([&](int a, int b, int c) { + called = true; + return a + b + c; + }); + auto op = ex::connect(std::move(snd), expect_value_receiver{41}); + ex::start(op); + // The when_all scheduler will complete only after 3 impulses + CHECK_FALSE(called); + sched.start_next(); + CHECK_FALSE(called); + sched.start_next(); + CHECK_FALSE(called); + sched.start_next(); + CHECK(called); + } -TEST_CASE("when_all cancels remaining children if error is detected", "[adaptors][when_all]") { - impulse_scheduler sched; - error_scheduler err_sched; - bool called1{false}; - bool called3{false}; - bool cancelled{false}; - ex::sender auto snd = ex::when_all( // - ex::on(sched, ex::just()) | ex::then([&] { called1 = true; }), // - ex::on(sched, ex::transfer_just(err_sched, 5)), // - ex::on(sched, ex::just()) // - | ex::then([&] { called3 = true; }) // - | ex::let_stopped([&] { - cancelled = true; - return ex::just(); - }) // - ); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); - // The first child will complete; the third one will be cancelled - CHECK_FALSE(called1); - CHECK_FALSE(called3); - sched.start_next(); // start the first child - CHECK(called1); - sched.start_next(); // start the second child; this will generate an error - CHECK_FALSE(called3); - sched.start_next(); // start the third child - CHECK_FALSE(called3); - CHECK(cancelled); -} + TEST_CASE("when_all can be used with just_*", "[adaptors][when_all]") { + ex::sender auto snd = ex::when_all( // + ex::just(2), // + ex::just_error(std::exception_ptr{}), // + ex::just_stopped() // + ); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + } -TEST_CASE("when_all cancels remaining children if cancel is detected", "[adaptors][when_all]") { - stopped_scheduler stopped_sched; - impulse_scheduler sched; - bool called1{false}; - bool called3{false}; - bool cancelled{false}; - ex::sender auto snd = ex::when_all( // - ex::on(sched, ex::just()) | ex::then([&] { called1 = true; }), // - ex::on(sched, ex::transfer_just(stopped_sched, 5)), // - ex::on(sched, ex::just()) // - | ex::then([&] { called3 = true; }) // - | ex::let_stopped([&] { - cancelled = true; - return ex::just(); - }) // - ); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); - // The first child will complete; the third one will be cancelled - CHECK_FALSE(called1); - CHECK_FALSE(called3); - sched.start_next(); // start the first child - CHECK(called1); - sched.start_next(); // start the second child; this will call set_stopped - CHECK_FALSE(called3); - sched.start_next(); // start the third child - CHECK_FALSE(called3); - CHECK(cancelled); -} + TEST_CASE( + "when_all terminates with error if one child terminates with error", + "[adaptors][when_all]") { + error_scheduler sched; + ex::sender auto snd = ex::when_all( // + ex::just(2), // + ex::transfer_just(sched, 5), // + ex::just(7) // + ); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + } -TEST_CASE( - "when_all has the values_type based on the children, decayed and as rvalue references", - "[adaptors][when_all]") { - check_val_types>>(ex::when_all(ex::just(13))); - check_val_types>>(ex::when_all(ex::just(3.14))); - check_val_types>>(ex::when_all(ex::just(3, 0.14))); - - check_val_types>>(ex::when_all(ex::just())); - - check_val_types>>( - ex::when_all(ex::just(3), ex::just(0.14))); - check_val_types>>( // - ex::when_all( // - ex::just(3), // - ex::just(0.14), // - ex::just(1, 0.4142) // - ) // - ); - - // if one child returns void, then the value is simply missing - check_val_types>>( // - ex::when_all( // - ex::just(3), // - ex::just(), // - ex::just(0.14) // - ) // - ); - - // if children send references, they get decayed - check_val_types>>( // - ex::when_all( // - ex::split(ex::just(3)), // - ex::split(ex::just(0.14)) // - ) // - ); -} + TEST_CASE("when_all terminates with stopped if one child is cancelled", "[adaptors][when_all]") { + stopped_scheduler sched; + ex::sender auto snd = ex::when_all( // + ex::just(2), // + ex::transfer_just(sched, 5), // + ex::just(7) // + ); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + } -TEST_CASE("when_all has the error_types based on the children", "[adaptors][when_all]") { - check_err_types>(ex::when_all(ex::just_error(13))); - check_err_types>(ex::when_all(ex::just_error(3.14))); - - check_err_types>(ex::when_all(ex::just())); - - check_err_types>( - ex::when_all(ex::just_error(3), ex::just_error(0.14))); - check_err_types>( // - ex::when_all( // - ex::just_error(3), // - ex::just_error(0.14), // - ex::just_error(std::string{"err"}) // - ) // - ); - - check_err_types>( // - ex::when_all( // - ex::just(13), // - ex::just_error(std::exception_ptr{}), // - ex::just_stopped() // - ) // - ); -} + TEST_CASE("when_all cancels remaining children if error is detected", "[adaptors][when_all]") { + impulse_scheduler sched; + error_scheduler err_sched; + bool called1{false}; + bool called3{false}; + bool cancelled{false}; + ex::sender auto snd = ex::when_all( // + ex::on(sched, ex::just()) | ex::then([&] { called1 = true; }), // + ex::on(sched, ex::transfer_just(err_sched, 5)), // + ex::on(sched, ex::just()) // + | ex::then([&] { called3 = true; }) // + | ex::let_stopped([&] { + cancelled = true; + return ex::just(); + }) // + ); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + // The first child will complete; the third one will be cancelled + CHECK_FALSE(called1); + CHECK_FALSE(called3); + sched.start_next(); // start the first child + CHECK(called1); + sched.start_next(); // start the second child; this will generate an error + CHECK_FALSE(called3); + sched.start_next(); // start the third child + CHECK_FALSE(called3); + CHECK(cancelled); + } -TEST_CASE("when_all has the sends_stopped == true", "[adaptors][when_all]") { - check_sends_stopped(ex::when_all(ex::just(13))); - check_sends_stopped(ex::when_all(ex::just_error(-1))); - check_sends_stopped(ex::when_all(ex::just_stopped())); - - check_sends_stopped(ex::when_all(ex::just(3), ex::just(0.14))); - check_sends_stopped( // - ex::when_all( // - ex::just(3), // - ex::just_error(-1), // - ex::just_stopped() // - ) // - ); -} + TEST_CASE("when_all cancels remaining children if cancel is detected", "[adaptors][when_all]") { + stopped_scheduler stopped_sched; + impulse_scheduler sched; + bool called1{false}; + bool called3{false}; + bool cancelled{false}; + ex::sender auto snd = ex::when_all( // + ex::on(sched, ex::just()) | ex::then([&] { called1 = true; }), // + ex::on(sched, ex::transfer_just(stopped_sched, 5)), // + ex::on(sched, ex::just()) // + | ex::then([&] { called3 = true; }) // + | ex::let_stopped([&] { + cancelled = true; + return ex::just(); + }) // + ); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + // The first child will complete; the third one will be cancelled + CHECK_FALSE(called1); + CHECK_FALSE(called3); + sched.start_next(); // start the first child + CHECK(called1); + sched.start_next(); // start the second child; this will call set_stopped + CHECK_FALSE(called3); + sched.start_next(); // start the third child + CHECK_FALSE(called3); + CHECK(cancelled); + } -struct my_string_sender_t { - std::string str_; + TEST_CASE( + "when_all has the values_type based on the children, decayed and as rvalue references", + "[adaptors][when_all]") { + check_val_types>>(ex::when_all(ex::just(13))); + check_val_types>>(ex::when_all(ex::just(3.14))); + check_val_types>>(ex::when_all(ex::just(3, 0.14))); + + check_val_types>>(ex::when_all(ex::just())); + + check_val_types>>( + ex::when_all(ex::just(3), ex::just(0.14))); + check_val_types>>( // + ex::when_all( // + ex::just(3), // + ex::just(0.14), // + ex::just(1, 0.4142) // + ) // + ); - using is_sender = void; - using completion_signatures = - ex::completion_signatures_of_t; + // if one child returns void, then the value is simply missing + check_val_types>>( // + ex::when_all( // + ex::just(3), // + ex::just(), // + ex::just(0.14) // + ) // + ); - template - friend auto tag_invoke(ex::connect_t, my_string_sender_t&& self, Recv&& recv) { - return ex::connect(ex::just(std::move(self.str_)), std::forward(recv)); + // if children send references, they get decayed + check_val_types>>( // + ex::when_all( // + ex::split(ex::just(3)), // + ex::split(ex::just(0.14)) // + ) // + ); } - template - friend auto tag_invoke(ex::connect_t, const my_string_sender_t& self, Recv&& recv) { - return ex::connect(ex::just(self.str_), std::forward(recv)); - } + TEST_CASE("when_all has the error_types based on the children", "[adaptors][when_all]") { + check_err_types>(ex::when_all(ex::just_error(13))); + check_err_types>(ex::when_all(ex::just_error(3.14))); - friend empty_env tag_invoke(ex::get_env_t, const my_string_sender_t&) noexcept { - return {}; - } -}; + check_err_types>(ex::when_all(ex::just())); -auto tag_invoke(ex::when_all_t, my_string_sender_t, my_string_sender_t) { - // Return a different sender when we invoke this custom defined on implementation - return ex::just(std::string{"first program"}); -} + check_err_types>( + ex::when_all(ex::just_error(3), ex::just_error(0.14))); + check_err_types>( // + ex::when_all( // + ex::just_error(3), // + ex::just_error(0.14), // + ex::just_error(std::string{"err"}) // + ) // + ); -TEST_CASE("when_all can be customized", "[adaptors][when_all]") { - // The customization will return a different value - auto snd = ex::when_all( // - my_string_sender_t{std::string{"hello,"}}, // - my_string_sender_t{std::string{" world!"}} // - ); - wait_for_value(std::move(snd), std::string{"first program"}); -} + check_err_types>( // + ex::when_all( // + ex::just(13), // + ex::just_error(std::exception_ptr{}), // + ex::just_stopped() // + ) // + ); + } -auto tag_invoke(ex::when_all_with_variant_t, my_string_sender_t, my_string_sender_t) { - // Return a different sender when we invoke this custom defined on implementation - return ex::just(std::string{"first program"}); -} + TEST_CASE("when_all has the sends_stopped == true", "[adaptors][when_all]") { + check_sends_stopped(ex::when_all(ex::just(13))); + check_sends_stopped(ex::when_all(ex::just_error(-1))); + check_sends_stopped(ex::when_all(ex::just_stopped())); + + check_sends_stopped(ex::when_all(ex::just(3), ex::just(0.14))); + check_sends_stopped( // + ex::when_all( // + ex::just(3), // + ex::just_error(-1), // + ex::just_stopped() // + ) // + ); + } -TEST_CASE("when_all_with_variant can be customized", "[adaptors][when_all]") { - // The customization will return a different value - auto snd = ex::when_all_with_variant( // - my_string_sender_t{std::string{"hello,"}}, // - my_string_sender_t{std::string{" world!"}} // - ); - wait_for_value(std::move(snd), std::string{"first program"}); -} + struct my_string_sender_t { + std::string str_; -// There is no way for ADL to find the following overload. This test is broken and needs to be -// rewritten using a custom domain. -// -// using my_string_variant_sender_t = decltype(ex::into_variant(my_string_sender_t{std::string{}})); -// -// auto tag_invoke(ex::when_all_t, my_string_variant_sender_t, my_string_variant_sender_t) { -// // Return a different sender when we invoke this custom defined on implementation -// return ex::just(std::string{"first program"}); -// } - -// TEST_CASE( -// "when_all_with_variant take into account when_all customizations", -// "[adaptors][when_all]") { -// // when_all_with_variant must be using the `when_all` implementation that allows customizations -// // The customization will return a different value -// auto snd = ex::when_all_with_variant( // -// my_string_sender_t{std::string{"hello,"}}, // -// my_string_sender_t{std::string{" world!"}} // -// ); -// wait_for_value(std::move(snd), std::string{"first program"}); -// } - -TEST_CASE("when_all returns empty env", "[adaptors][when_all]") { - check_env_type(ex::when_all(ex::just(), ex::just())); -} + using is_sender = void; + using completion_signatures = + ex::completion_signatures_of_t; -namespace { - enum customize : std::size_t { - early, - late, - none - }; + template + friend auto tag_invoke(ex::connect_t, my_string_sender_t&& self, Recv&& recv) { + return ex::connect(ex::just(std::move(self.str_)), std::forward(recv)); + } - template - struct basic_domain { - template Sender, class... Env> - requires(sizeof...(Env) == C) - auto transform_sender(Sender&& sender, const Env&...) const { - return Fun(); + template + friend auto tag_invoke(ex::connect_t, const my_string_sender_t& self, Recv&& recv) { + return ex::connect(ex::just(self.str_), std::forward(recv)); } - }; -} // anonymous namespace -TEST_CASE("when_all works with custom domain", "[adaptors][when_all]") { - constexpr auto hello = [] { - return ex::just(std::string{"hello world"}); + friend empty_env tag_invoke(ex::get_env_t, const my_string_sender_t&) noexcept { + return {}; + } }; - SECTION("sender has correct domain") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; + auto tag_invoke(ex::when_all_t, my_string_sender_t, my_string_sender_t) { + // Return a different sender when we invoke this custom defined on implementation + return ex::just(std::string{"first program"}); + } - auto snd = ex::when_all( // - ex::transfer_just(scheduler(), 3), // - ex::transfer_just(scheduler(), 0.1415) // + TEST_CASE("when_all can be customized", "[adaptors][when_all]") { + // The customization will return a different value + auto snd = ex::when_all( // + my_string_sender_t{std::string{"hello,"}}, // + my_string_sender_t{std::string{" world!"}} // ); - static_assert(ex::sender_expr_for); - [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + wait_for_value(std::move(snd), std::string{"first program"}); } - SECTION("early customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; + auto tag_invoke(ex::when_all_with_variant_t, my_string_sender_t, my_string_sender_t) { + // Return a different sender when we invoke this custom defined on implementation + return ex::just(std::string{"first program"}); + } - auto snd = ex::when_all( // - ex::transfer_just(scheduler(), 3), // - ex::transfer_just(scheduler(), 0.1415) // + TEST_CASE("when_all_with_variant can be customized", "[adaptors][when_all]") { + // The customization will return a different value + auto snd = ex::when_all_with_variant( // + my_string_sender_t{std::string{"hello,"}}, // + my_string_sender_t{std::string{" world!"}} // ); - static_assert(ex::sender_expr_for); - wait_for_value(std::move(snd), std::string{"hello world"}); + wait_for_value(std::move(snd), std::string{"first program"}); } - SECTION("late customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; - - auto snd = ex::on( - scheduler(), - ex::when_all( // - ex::just(3), // - ex::just(0.1415) // - )); - wait_for_value(std::move(snd), std::string{"hello world"}); + // There is no way for ADL to find the following overload. This test is broken and needs to be + // rewritten using a custom domain. + // + // using my_string_variant_sender_t = decltype(ex::into_variant(my_string_sender_t{std::string{}})); + // + // auto tag_invoke(ex::when_all_t, my_string_variant_sender_t, my_string_variant_sender_t) { + // // Return a different sender when we invoke this custom defined on implementation + // return ex::just(std::string{"first program"}); + // } + + // TEST_CASE( + // "when_all_with_variant take into account when_all customizations", + // "[adaptors][when_all]") { + // // when_all_with_variant must be using the `when_all` implementation that allows customizations + // // The customization will return a different value + // auto snd = ex::when_all_with_variant( // + // my_string_sender_t{std::string{"hello,"}}, // + // my_string_sender_t{std::string{" world!"}} // + // ); + // wait_for_value(std::move(snd), std::string{"first program"}); + // } + + TEST_CASE("when_all returns empty env", "[adaptors][when_all]") { + check_env_type(ex::when_all(ex::just(), ex::just())); } -} -TEST_CASE("when_all_with_variant works with custom domain", "[adaptors][when_all]") { - constexpr auto hello = [] { - return ex::just(std::string{"hello world"}); - }; - - SECTION("sender has correct domain") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; + namespace { + enum customize : std::size_t { + early, + late, + none + }; + + template + struct basic_domain { + template Sender, class... Env> + requires(sizeof...(Env) == C) + auto transform_sender(Sender&& sender, const Env&...) const { + return Fun(); + } + }; + } // anonymous namespace + + TEST_CASE("when_all works with custom domain", "[adaptors][when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::when_all( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } - auto snd = ex::when_all_with_variant( // - ex::transfer_just(scheduler(), 3), // - ex::transfer_just(scheduler(), 0.1415) // - ); - static_assert(ex::sender_expr_for); - [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); - } + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; - SECTION("early customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; + auto snd = ex::when_all( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } - auto snd = ex::when_all_with_variant( // - ex::transfer_just(scheduler(), 3), // - ex::transfer_just(scheduler(), 0.1415) // - ); - static_assert(ex::sender_expr_for); - wait_for_value(std::move(snd), std::string{"hello world"}); + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::when_all( // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } } - SECTION("late customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; - - auto snd = ex::on( - scheduler(), - ex::when_all_with_variant( // - ex::just(3), // - ex::just(0.1415) // - )); - wait_for_value(std::move(snd), std::string{"hello world"}); - } -} + TEST_CASE("when_all_with_variant works with custom domain", "[adaptors][when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::when_all_with_variant( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } -TEST_CASE("when_all_with_variant finds when_all customizations", "[adaptors][when_all]") { - constexpr auto hello = [] { - return ex::just(std::string{"hello world"}); - }; + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; - SECTION("sender has correct domain") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; + auto snd = ex::when_all_with_variant( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } - auto snd = ex::when_all_with_variant( // - ex::transfer_just(scheduler(), 3), // - ex::transfer_just(scheduler(), 0.1415) // - ); - static_assert(ex::sender_expr_for); - [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::when_all_with_variant( // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } } - SECTION("early customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; + TEST_CASE("when_all_with_variant finds when_all customizations", "[adaptors][when_all]") { + constexpr auto hello = [] { + return ex::just(std::string{"hello world"}); + }; + + SECTION("sender has correct domain") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::when_all_with_variant( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + [[maybe_unused]] domain dom = ex::get_domain(ex::get_env(snd)); + } - auto snd = ex::when_all_with_variant( // - ex::transfer_just(scheduler(), 3), // - ex::transfer_just(scheduler(), 0.1415) // - ); - static_assert(ex::sender_expr_for); - wait_for_value(std::move(snd), std::string{"hello world"}); - } + SECTION("early customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; - SECTION("late customization") { - using domain = basic_domain; - using scheduler = basic_inline_scheduler; - - auto snd = ex::on( - scheduler(), - ex::when_all_with_variant( // - ex::just(3), // - ex::just(0.1415) // - )); - wait_for_value(std::move(snd), std::string{"hello world"}); + auto snd = ex::when_all_with_variant( // + ex::transfer_just(scheduler(), 3), // + ex::transfer_just(scheduler(), 0.1415) // + ); + static_assert(ex::sender_expr_for); + wait_for_value(std::move(snd), std::string{"hello world"}); + } + + SECTION("late customization") { + using domain = basic_domain; + using scheduler = basic_inline_scheduler; + + auto snd = ex::on( + scheduler(), + ex::when_all_with_variant( // + ex::just(3), // + ex::just(0.1415) // + )); + wait_for_value(std::move(snd), std::string{"hello world"}); + } } } diff --git a/test/stdexec/algos/consumers/test_start_detached.cpp b/test/stdexec/algos/consumers/test_start_detached.cpp index 8706e25fe..7ab69c1a3 100644 --- a/test/stdexec/algos/consumers/test_start_detached.cpp +++ b/test/stdexec/algos/consumers/test_start_detached.cpp @@ -27,133 +27,143 @@ namespace ex = stdexec; using namespace std::chrono_literals; -TEST_CASE("start_detached simple test", "[consumers][start_detached]") { - bool called{false}; - ex::start_detached(ex::just() | ex::then([&] { called = true; })); - CHECK(called); -} - -TEST_CASE("start_detached works with void senders", "[consumers][start_detached]") { - ex::start_detached(ex::just()); -} - -TEST_CASE("start_detached works with multi-value senders", "[consumers][start_detached]") { - ex::start_detached(ex::just(3, 0.1415)); -} +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-function") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunneeded-internal-declaration") -// Trying to test `start_detached` with error flows will result in calling `std::terminate()`. -// We don't want that +namespace { -TEST_CASE( - "start_detached works with sender ending with `set_stopped`", - "[consumers][start_detached]") { - ex::start_detached(ex::just_stopped()); -} + TEST_CASE("start_detached simple test", "[consumers][start_detached]") { + bool called{false}; + ex::start_detached(ex::just() | ex::then([&] { called = true; })); + CHECK(called); + } -TEST_CASE( - "start_detached works with senders that do not complete immediately", - "[consumers][start_detached]") { - impulse_scheduler sched; - bool called{false}; - // Start the sender - ex::start_detached(ex::transfer_just(sched) | ex::then([&] { called = true; })); - // The `then` function is not yet called - CHECK_FALSE(called); - // After an impulse to the scheduler, the function would complete - sched.start_next(); - CHECK(called); -} + TEST_CASE("start_detached works with void senders", "[consumers][start_detached]") { + ex::start_detached(ex::just()); + } -TEST_CASE("start_detached works when changing threads", "[consumers][start_detached]") { - exec::static_thread_pool pool{2}; - std::atomic called{false}; - { - // lunch some work on the thread pool - ex::sender auto snd = ex::transfer_just(pool.get_scheduler()) // - | ex::then([&] { called.store(true); }); - ex::start_detached(std::move(snd)); + TEST_CASE("start_detached works with multi-value senders", "[consumers][start_detached]") { + ex::start_detached(ex::just(3, 0.1415)); } - // wait for the work to be executed, with timeout - // perform a poor-man's sync - // NOTE: it's a shame that the `join` method in static_thread_pool is not public - for (int i = 0; i < 1000 && !called.load(); i++) - std::this_thread::sleep_for(1ms); - // the work should be executed - REQUIRE(called); -} -struct custom_sender { - using is_sender = void; - bool* called; + // Trying to test `start_detached` with error flows will result in calling `std::terminate()`. + // We don't want that - template - friend auto tag_invoke(ex::connect_t, custom_sender, Receiver&& rcvr) { - return ex::connect(ex::schedule(inline_scheduler{}), (Receiver&&) rcvr); + TEST_CASE( + "start_detached works with sender ending with `set_stopped`", + "[consumers][start_detached]") { + ex::start_detached(ex::just_stopped()); } - template - friend auto tag_invoke(ex::get_completion_signatures_t, custom_sender, Env) noexcept - -> ex::completion_signatures { - return {}; + TEST_CASE( + "start_detached works with senders that do not complete immediately", + "[consumers][start_detached]") { + impulse_scheduler sched; + bool called{false}; + // Start the sender + ex::start_detached(ex::transfer_just(sched) | ex::then([&] { called = true; })); + // The `then` function is not yet called + CHECK_FALSE(called); + // After an impulse to the scheduler, the function would complete + sched.start_next(); + CHECK(called); } - friend void tag_invoke(ex::start_detached_t, custom_sender sndr) { - *sndr.called = true; + TEST_CASE("start_detached works when changing threads", "[consumers][start_detached]") { + exec::static_thread_pool pool{2}; + std::atomic called{false}; + { + // lunch some work on the thread pool + ex::sender auto snd = ex::transfer_just(pool.get_scheduler()) // + | ex::then([&] { called.store(true); }); + ex::start_detached(std::move(snd)); + } + // wait for the work to be executed, with timeout + // perform a poor-man's sync + // NOTE: it's a shame that the `join` method in static_thread_pool is not public + for (int i = 0; i < 1000 && !called.load(); i++) + std::this_thread::sleep_for(1ms); + // the work should be executed + REQUIRE(called); } - friend empty_env tag_invoke(ex::get_env_t, const custom_sender&) noexcept { - return {}; - } -}; + struct custom_sender { + using is_sender = void; + bool* called; -struct custom_scheduler { - struct sender : ex::schedule_result_t { - struct env { - template - friend custom_scheduler tag_invoke(ex::get_completion_scheduler_t, const env&) noexcept { - return {}; - } - }; + template + friend auto tag_invoke(ex::connect_t, custom_sender, Receiver&& rcvr) { + return ex::connect(ex::schedule(inline_scheduler{}), (Receiver&&) rcvr); + } - friend env tag_invoke(ex::get_env_t, const sender&) noexcept { + template + friend auto tag_invoke(ex::get_completion_signatures_t, custom_sender, Env) noexcept + -> ex::completion_signatures { return {}; } - }; - struct domain { - template - friend void tag_invoke(ex::start_detached_t, domain, Sender, Env) { - // drop the sender on the floor + friend void tag_invoke(ex::start_detached_t, custom_sender sndr) { + *sndr.called = true; } - // BUGBUG legacy - operator custom_scheduler() const { + friend empty_env tag_invoke(ex::get_env_t, const custom_sender&) noexcept { return {}; } }; - friend domain tag_invoke(ex::get_domain_t, custom_scheduler) noexcept { - return {}; - } + struct custom_scheduler { + struct sender : ex::schedule_result_t { + struct env { + template + friend custom_scheduler + tag_invoke(ex::get_completion_scheduler_t, const env&) noexcept { + return {}; + } + }; + + friend env tag_invoke(ex::get_env_t, const sender&) noexcept { + return {}; + } + }; - friend sender tag_invoke(ex::schedule_t, custom_scheduler) noexcept { - return {}; - } + struct domain { + template + friend void tag_invoke(ex::start_detached_t, domain, Sender, Env) { + // drop the sender on the floor + } - bool operator==(const custom_scheduler&) const = default; -}; + // BUGBUG legacy + operator custom_scheduler() const { + return {}; + } + }; -TEST_CASE("start_detached can be customized on sender", "[consumers][start_detached]") { - bool called = false; - ex::start_detached(custom_sender{&called}); - CHECK(called); -} + friend domain tag_invoke(ex::get_domain_t, custom_scheduler) noexcept { + return {}; + } -// NOT TO SPEC -TEST_CASE("start_detached can be customized on scheduler", "[consumers][start_detached]") { - bool called = false; - ex::start_detached( - ex::just() | ex::then([&] { called = true; }), - exec::make_env(exec::with(ex::get_scheduler, custom_scheduler{}))); - CHECK_FALSE(called); + friend sender tag_invoke(ex::schedule_t, custom_scheduler) noexcept { + return {}; + } + + bool operator==(const custom_scheduler&) const = default; + }; + + TEST_CASE("start_detached can be customized on sender", "[consumers][start_detached]") { + bool called = false; + ex::start_detached(custom_sender{&called}); + CHECK(called); + } + + // NOT TO SPEC + TEST_CASE("start_detached can be customized on scheduler", "[consumers][start_detached]") { + bool called = false; + ex::start_detached( + ex::just() | ex::then([&] { called = true; }), + exec::make_env(exec::with(ex::get_scheduler, custom_scheduler{}))); + CHECK_FALSE(called); + } } +STDEXEC_PRAGMA_POP() diff --git a/test/stdexec/algos/consumers/test_sync_wait.cpp b/test/stdexec/algos/consumers/test_sync_wait.cpp index 9bd948cea..2979f894f 100644 --- a/test/stdexec/algos/consumers/test_sync_wait.cpp +++ b/test/stdexec/algos/consumers/test_sync_wait.cpp @@ -33,305 +33,309 @@ using stdexec::sync_wait_with_variant; using namespace std::chrono_literals; -TEST_CASE("sync_wait simple test", "[consumers][sync_wait]") { - optional> res = sync_wait(ex::just(49)); - CHECK(res.has_value()); - CHECK(std::get<0>(res.value()) == 49); -} - -TEST_CASE("sync_wait can wait on void values", "[consumers][sync_wait]") { - optional> res = sync_wait(ex::just()); - CHECK(res.has_value()); -} +namespace { -TEST_CASE("sync_wait can wait on senders sending value packs", "[consumers][sync_wait]") { - optional> res = sync_wait(ex::just(3, 0.1415)); - CHECK(res.has_value()); - CHECK(std::get<0>(res.value()) == 3); - CHECK(std::get<1>(res.value()) == 0.1415); -} - -TEST_CASE("sync_wait rethrows received exception", "[consumers][sync_wait]") { - // Generate an exception pointer object - std::exception_ptr eptr; - try { - throw std::logic_error("err"); - } catch (...) { - eptr = std::current_exception(); + TEST_CASE("sync_wait simple test", "[consumers][sync_wait]") { + optional> res = sync_wait(ex::just(49)); + CHECK(res.has_value()); + CHECK(std::get<0>(res.value()) == 49); } - // Ensure that sync_wait will rethrow the error - try { - error_scheduler sched{eptr}; - sync_wait(ex::transfer_just(sched, 19)); - FAIL("exception not thrown?"); - } catch (const std::logic_error& e) { - CHECK(std::string{e.what()} == "err"); - } catch (...) { - FAIL("invalid exception received"); + TEST_CASE("sync_wait can wait on void values", "[consumers][sync_wait]") { + optional> res = sync_wait(ex::just()); + CHECK(res.has_value()); } -} -TEST_CASE("sync_wait handling error_code errors", "[consumers][sync_wait]") { - try { - error_scheduler sched{std::make_error_code(std::errc::argument_out_of_domain)}; - ex::sender auto snd = ex::transfer_just(sched, 19); - static_assert(std::invocable); - sync_wait(std::move(snd)); // doesn't work - FAIL("expecting exception to be thrown"); - } catch (const std::system_error& e) { - CHECK(e.code() == std::errc::argument_out_of_domain); - } catch (...) { - FAIL("expecting std::system_error exception to be thrown"); + TEST_CASE("sync_wait can wait on senders sending value packs", "[consumers][sync_wait]") { + optional> res = sync_wait(ex::just(3, 0.1415)); + CHECK(res.has_value()); + CHECK(std::get<0>(res.value()) == 3); + CHECK(std::get<1>(res.value()) == 0.1415); } -} -TEST_CASE("sync_wait handling non-exception errors", "[consumers][sync_wait]") { - try { - error_scheduler sched{std::string{"err"}}; - ex::sender auto snd = ex::transfer_just(sched, 19); - static_assert(std::invocable); - sync_wait(std::move(snd)); // doesn't work - FAIL("expecting exception to be thrown"); - } catch (const std::string& e) { - CHECK(e == "err"); - } catch (...) { - FAIL("expecting std::string exception to be thrown"); + TEST_CASE("sync_wait rethrows received exception", "[consumers][sync_wait]") { + // Generate an exception pointer object + std::exception_ptr eptr; + try { + throw std::logic_error("err"); + } catch (...) { + eptr = std::current_exception(); + } + + // Ensure that sync_wait will rethrow the error + try { + error_scheduler sched{eptr}; + sync_wait(ex::transfer_just(sched, 19)); + FAIL("exception not thrown?"); + } catch (const std::logic_error& e) { + CHECK(std::string{e.what()} == "err"); + } catch (...) { + FAIL("invalid exception received"); + } } -} - -TEST_CASE("sync_wait returns empty optional on cancellation", "[consumers][sync_wait]") { - stopped_scheduler sched; - optional> res = sync_wait(ex::transfer_just(sched, 19)); - CHECK_FALSE(res.has_value()); -} -template -auto always(T t) { - return [t](auto&&...) mutable { - return std::move(t); - }; -} + TEST_CASE("sync_wait handling error_code errors", "[consumers][sync_wait]") { + try { + error_scheduler sched{ + std::make_error_code(std::errc::argument_out_of_domain)}; + ex::sender auto snd = ex::transfer_just(sched, 19); + static_assert(std::invocable); + sync_wait(std::move(snd)); // doesn't work + FAIL("expecting exception to be thrown"); + } catch (const std::system_error& e) { + CHECK(e.code() == std::errc::argument_out_of_domain); + } catch (...) { + FAIL("expecting std::system_error exception to be thrown"); + } + } -TEST_CASE("sync_wait doesn't accept multi-variant senders", "[consumers][sync_wait]") { - ex::sender auto snd = fallible_just{13} // - | ex::let_error(always(ex::just(std::string{"err"}))); - check_val_types, type_array>>(snd); - static_assert(!std::invocable); -} + TEST_CASE("sync_wait handling non-exception errors", "[consumers][sync_wait]") { + try { + error_scheduler sched{std::string{"err"}}; + ex::sender auto snd = ex::transfer_just(sched, 19); + static_assert(std::invocable); + sync_wait(std::move(snd)); // doesn't work + FAIL("expecting exception to be thrown"); + } catch (const std::string& e) { + CHECK(e == "err"); + } catch (...) { + FAIL("expecting std::string exception to be thrown"); + } + } -TEST_CASE( - "sync_wait_with_variant accepts multi-variant senders", - "[consumers][sync_wait_with_variant]") { - ex::sender auto snd = fallible_just{13} | ex::let_error(always(ex::just(std::string{"err"}))); - check_val_types, type_array>>(snd); - static_assert(std::invocable); + TEST_CASE("sync_wait returns empty optional on cancellation", "[consumers][sync_wait]") { + stopped_scheduler sched; + optional> res = sync_wait(ex::transfer_just(sched, 19)); + CHECK_FALSE(res.has_value()); + } - std::optional, std::tuple>>> res = - sync_wait_with_variant(snd); + template + auto always(T t) { + return [t](auto&&...) mutable { + return std::move(t); + }; + } - CHECK(res.has_value()); - CHECK(std::get<0>(std::get<0>(res.value())) == std::make_tuple(13)); -} + TEST_CASE("sync_wait doesn't accept multi-variant senders", "[consumers][sync_wait]") { + ex::sender auto snd = fallible_just{13} // + | ex::let_error(always(ex::just(std::string{"err"}))); + check_val_types, type_array>>(snd); + static_assert(!std::invocable); + } -TEST_CASE( - "sync_wait_with_variant accepts single-value senders", - "[consumers][sync_wait_with_variant]") { - ex::sender auto snd = ex::just(13); - check_val_types>>(snd); - static_assert(std::invocable); + TEST_CASE( + "sync_wait_with_variant accepts multi-variant senders", + "[consumers][sync_wait_with_variant]") { + ex::sender auto snd = fallible_just{13} | ex::let_error(always(ex::just(std::string{"err"}))); + check_val_types, type_array>>(snd); + static_assert(std::invocable); - std::optional>>> res = sync_wait_with_variant(snd); + std::optional, std::tuple>>> res = + sync_wait_with_variant(snd); - CHECK(res.has_value()); - CHECK(std::get<0>(std::get<0>(res.value())) == std::make_tuple(13)); -} + CHECK(res.has_value()); + CHECK(std::get<0>(std::get<0>(res.value())) == std::make_tuple(13)); + } -TEST_CASE("sync_wait works if signaled from a different thread", "[consumers][sync_wait]") { - std::atomic thread_started{false}; - std::atomic thread_stopped{false}; - impulse_scheduler sched; + TEST_CASE( + "sync_wait_with_variant accepts single-value senders", + "[consumers][sync_wait_with_variant]") { + ex::sender auto snd = ex::just(13); + check_val_types>>(snd); + static_assert(std::invocable); - // Thread that calls `sync_wait` - auto waiting_thread = std::thread{[&] { - thread_started.store(true); + std::optional>>> res = sync_wait_with_variant(snd); - // Wait for a result that is triggered by the impulse scheduler - optional> res = sync_wait(ex::transfer_just(sched, 49)); CHECK(res.has_value()); - CHECK(std::get<0>(res.value()) == 49); + CHECK(std::get<0>(std::get<0>(res.value())) == std::make_tuple(13)); + } - thread_stopped.store(true); - }}; - // Wait for the thread to start (poor-man's sync) - for (int i = 0; i < 10'000 && !thread_started.load(); i++) - std::this_thread::sleep_for(100us); + TEST_CASE("sync_wait works if signaled from a different thread", "[consumers][sync_wait]") { + std::atomic thread_started{false}; + std::atomic thread_stopped{false}; + impulse_scheduler sched; + + // Thread that calls `sync_wait` + auto waiting_thread = std::thread{[&] { + thread_started.store(true); + + // Wait for a result that is triggered by the impulse scheduler + optional> res = sync_wait(ex::transfer_just(sched, 49)); + CHECK(res.has_value()); + CHECK(std::get<0>(res.value()) == 49); + + thread_stopped.store(true); + }}; + // Wait for the thread to start (poor-man's sync) + for (int i = 0; i < 10'000 && !thread_started.load(); i++) + std::this_thread::sleep_for(100us); + + // The thread should be waiting on the impulse + CHECK_FALSE(thread_stopped.load()); + sched.start_next(); + + // Now, the thread should exit + waiting_thread.join(); + CHECK(thread_stopped.load()); + } - // The thread should be waiting on the impulse - CHECK_FALSE(thread_stopped.load()); - sched.start_next(); + TEST_CASE( + "sync_wait can wait on operations happening on different threads", + "[consumers][sync_wait]") { + auto square = [](int x) { + return x * x; + }; + + exec::static_thread_pool pool{3}; + ex::scheduler auto sched = pool.get_scheduler(); + ex::sender auto snd = ex::when_all( // + ex::transfer_just(sched, 2) | ex::then(square), // + ex::transfer_just(sched, 3) | ex::then(square), // + ex::transfer_just(sched, 5) | ex::then(square) // + ); + optional> res = sync_wait(std::move(snd)); + CHECK(res.has_value()); + CHECK(std::get<0>(res.value()) == 4); + CHECK(std::get<1>(res.value()) == 9); + CHECK(std::get<2>(res.value()) == 25); + } - // Now, the thread should exit - waiting_thread.join(); - CHECK(thread_stopped.load()); -} + using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); + + optional> + tag_invoke(decltype(sync_wait), inline_scheduler sched, my_string_sender_t&& s) { + std::string res; + auto op = ex::connect(std::move(s), expect_value_receiver_ex{res}); + ex::start(op); + CHECK(res == "hello"); + // change the string + res = "hallo"; + return {res}; + } -TEST_CASE( - "sync_wait can wait on operations happening on different threads", - "[consumers][sync_wait]") { - auto square = [](int x) { - return x * x; - }; + struct my_other_string_sender_t { + using is_sender = void; + std::string str_; - exec::static_thread_pool pool{3}; - ex::scheduler auto sched = pool.get_scheduler(); - ex::sender auto snd = ex::when_all( // - ex::transfer_just(sched, 2) | ex::then(square), // - ex::transfer_just(sched, 3) | ex::then(square), // - ex::transfer_just(sched, 5) | ex::then(square) // - ); - optional> res = sync_wait(std::move(snd)); - CHECK(res.has_value()); - CHECK(std::get<0>(res.value()) == 4); - CHECK(std::get<1>(res.value()) == 9); - CHECK(std::get<2>(res.value()) == 25); -} + using completion_signatures = ex::completion_signatures_of_t; -using my_string_sender_t = decltype(ex::transfer_just(inline_scheduler{}, std::string{})); - -optional> - tag_invoke(decltype(sync_wait), inline_scheduler sched, my_string_sender_t&& s) { - std::string res; - auto op = ex::connect(std::move(s), expect_value_receiver_ex{res}); - ex::start(op); - CHECK(res == "hello"); - // change the string - res = "hallo"; - return {res}; -} + template + friend auto tag_invoke(ex::connect_t, my_other_string_sender_t&& self, Recv&& recv) { + return ex::connect(ex::just(std::move(self.str_)), std::forward(recv)); + } -struct my_other_string_sender_t { - using is_sender = void; - std::string str_; + template + friend auto tag_invoke(ex::connect_t, const my_other_string_sender_t& self, Recv&& recv) { + return ex::connect(ex::just(self.str_), std::forward(recv)); + } - using completion_signatures = ex::completion_signatures_of_t; + friend empty_env tag_invoke(ex::get_env_t, const my_other_string_sender_t&) noexcept { + return {}; + } + }; - template - friend auto tag_invoke(ex::connect_t, my_other_string_sender_t&& self, Recv&& recv) { - return ex::connect(ex::just(std::move(self.str_)), std::forward(recv)); + optional> tag_invoke(decltype(sync_wait), my_other_string_sender_t s) { + CHECK(s.str_ == "hello"); + return {std::string{"ciao"}}; } - template - friend auto tag_invoke(ex::connect_t, const my_other_string_sender_t& self, Recv&& recv) { - return ex::connect(ex::just(self.str_), std::forward(recv)); + TEST_CASE("sync_wait can be customized with scheduler", "[consumers][sync_wait]") { + // The customization will return a different value + auto snd = ex::transfer_just(inline_scheduler{}, std::string{"hello"}); + optional> res = sync_wait(std::move(snd)); + CHECK(res.has_value()); + CHECK(std::get<0>(res.value()) == "hallo"); } - friend empty_env tag_invoke(ex::get_env_t, const my_other_string_sender_t&) noexcept { - return {}; + TEST_CASE("sync_wait can be customized without scheduler", "[consumers][sync_wait]") { + // The customization will return a different value + my_other_string_sender_t snd{std::string{"hello"}}; + optional> res = sync_wait(std::move(snd)); + CHECK(res.has_value()); + CHECK(std::get<0>(res.value()) == "ciao"); } -}; -optional> tag_invoke(decltype(sync_wait), my_other_string_sender_t s) { - CHECK(s.str_ == "hello"); - return {std::string{"ciao"}}; -} + using multi_value_impl_t = + decltype(fallible_just{std::string{}} | ex::let_error(always(ex::just(0)))); -TEST_CASE("sync_wait can be customized with scheduler", "[consumers][sync_wait]") { - // The customization will return a different value - auto snd = ex::transfer_just(inline_scheduler{}, std::string{"hello"}); - optional> res = sync_wait(std::move(snd)); - CHECK(res.has_value()); - CHECK(std::get<0>(res.value()) == "hallo"); -} + struct my_multi_value_sender_t { + using is_sender = void; + std::string str_; + using completion_signatures = ex::completion_signatures_of_t; -TEST_CASE("sync_wait can be customized without scheduler", "[consumers][sync_wait]") { - // The customization will return a different value - my_other_string_sender_t snd{std::string{"hello"}}; - optional> res = sync_wait(std::move(snd)); - CHECK(res.has_value()); - CHECK(std::get<0>(res.value()) == "ciao"); -} + template + friend auto tag_invoke(ex::connect_t, my_multi_value_sender_t&& self, Recv&& recv) { + return ex::connect(ex::just(std::move(self.str_)), std::forward(recv)); + } -using multi_value_impl_t = - decltype(fallible_just{std::string{}} | ex::let_error(always(ex::just(0)))); + template + friend auto tag_invoke(ex::connect_t, const my_multi_value_sender_t& self, Recv&& recv) { + return ex::connect(ex::just(self.str_), std::forward(recv)); + } -struct my_multi_value_sender_t { - using is_sender = void; - std::string str_; - using completion_signatures = ex::completion_signatures_of_t; + friend empty_env tag_invoke(ex::get_env_t, const my_multi_value_sender_t&) noexcept { + return {}; + } + }; - template - friend auto tag_invoke(ex::connect_t, my_multi_value_sender_t&& self, Recv&& recv) { - return ex::connect(ex::just(std::move(self.str_)), std::forward(recv)); + using my_transfered_multi_value_sender_t = + decltype(ex::transfer(my_multi_value_sender_t{}, inline_scheduler{})); + + optional, std::tuple>>> tag_invoke( + decltype(sync_wait_with_variant), + inline_scheduler sched, + my_transfered_multi_value_sender_t&& s) { + std::string res; + auto op = ex::connect(std::move(s), expect_value_receiver_ex{res}); + ex::start(op); + CHECK(res == "hello_multi"); + // change the string + res = "hallo_multi"; + return {res}; } - template - friend auto tag_invoke(ex::connect_t, const my_multi_value_sender_t& self, Recv&& recv) { - return ex::connect(ex::just(self.str_), std::forward(recv)); + optional, std::tuple>>> + tag_invoke(decltype(sync_wait_with_variant), my_multi_value_sender_t s) { + CHECK(s.str_ == "hello_multi"); + return {std::string{"ciao_multi"}}; } - friend empty_env tag_invoke(ex::get_env_t, const my_multi_value_sender_t&) noexcept { - return {}; + TEST_CASE( + "sync_wait_with_variant can be customized with scheduler", + "[consumers][sync_wait_with_variant]") { + // The customization will return a different value + auto snd = ex::transfer(my_multi_value_sender_t{"hello_multi"}, inline_scheduler{}); + auto snd2 = ex::transfer_just(inline_scheduler{}, std::string{"hello"}); + optional, std::tuple>>> res = + sync_wait_with_variant(std::move(snd)); + CHECK(res.has_value()); + CHECK(std::get<0>(std::get<0>(res.value())) == std::make_tuple(std::string{"hallo_multi"})); } -}; - -using my_transfered_multi_value_sender_t = - decltype(ex::transfer(my_multi_value_sender_t{}, inline_scheduler{})); - -optional, std::tuple>>> tag_invoke( - decltype(sync_wait_with_variant), - inline_scheduler sched, - my_transfered_multi_value_sender_t&& s) { - std::string res; - auto op = ex::connect(std::move(s), expect_value_receiver_ex{res}); - ex::start(op); - CHECK(res == "hello_multi"); - // change the string - res = "hallo_multi"; - return {res}; -} - -optional, std::tuple>>> - tag_invoke(decltype(sync_wait_with_variant), my_multi_value_sender_t s) { - CHECK(s.str_ == "hello_multi"); - return {std::string{"ciao_multi"}}; -} -TEST_CASE( - "sync_wait_with_variant can be customized with scheduler", - "[consumers][sync_wait_with_variant]") { - // The customization will return a different value - auto snd = ex::transfer(my_multi_value_sender_t{"hello_multi"}, inline_scheduler{}); - auto snd2 = ex::transfer_just(inline_scheduler{}, std::string{"hello"}); - optional, std::tuple>>> res = - sync_wait_with_variant(std::move(snd)); - CHECK(res.has_value()); - CHECK(std::get<0>(std::get<0>(res.value())) == std::make_tuple(std::string{"hallo_multi"})); -} - -TEST_CASE( - "sync_wait_with_variant can be customized without scheduler", - "[consumers][sync_wait_with_variant]") { - // The customization will return a different value - my_multi_value_sender_t snd{std::string{"hello_multi"}}; - optional, std::tuple>>> res = - sync_wait_with_variant(std::move(snd)); - CHECK(res.has_value()); - CHECK(std::get<0>(std::get<0>(res.value())) == std::make_tuple(std::string{"ciao_multi"})); -} + TEST_CASE( + "sync_wait_with_variant can be customized without scheduler", + "[consumers][sync_wait_with_variant]") { + // The customization will return a different value + my_multi_value_sender_t snd{std::string{"hello_multi"}}; + optional, std::tuple>>> res = + sync_wait_with_variant(std::move(snd)); + CHECK(res.has_value()); + CHECK(std::get<0>(std::get<0>(res.value())) == std::make_tuple(std::string{"ciao_multi"})); + } -template -using decayed_tuple = std::tuple...>; - -TEST_CASE( - "sync_wait spec's return type defined in terms of value_types_of_t", - "[consumers][sync_wait]") { - static_assert( - std::is_same_v< - std::tuple<>, - ex::value_types_of_t< - decltype(ex::just()), - ex::__default_env, - decayed_tuple, - std::type_identity_t>>); + template + using decayed_tuple = std::tuple...>; + + TEST_CASE( + "sync_wait spec's return type defined in terms of value_types_of_t", + "[consumers][sync_wait]") { + static_assert( + std::is_same_v< + std::tuple<>, + ex::value_types_of_t< + decltype(ex::just()), + ex::__default_env, + decayed_tuple, + std::type_identity_t>>); + } } diff --git a/test/stdexec/algos/factories/test_just.cpp b/test/stdexec/algos/factories/test_just.cpp index 871c74897..f954401de 100644 --- a/test/stdexec/algos/factories/test_just.cpp +++ b/test/stdexec/algos/factories/test_just.cpp @@ -21,100 +21,105 @@ namespace ex = stdexec; -TEST_CASE("Simple test for just", "[factories][just]") { - auto o1 = ex::connect(ex::just(1), expect_value_receiver(1)); - ex::start(o1); - auto o2 = ex::connect(ex::just(2), expect_value_receiver(2)); - ex::start(o2); - auto o3 = ex::connect(ex::just(3), expect_value_receiver(3)); - ex::start(o3); - - auto o4 = ex::connect(ex::just(std::string("this")), expect_value_receiver(std::string("this"))); - ex::start(o4); - auto o5 = ex::connect(ex::just(std::string("that")), expect_value_receiver(std::string("that"))); - ex::start(o5); -} - -TEST_CASE("just returns a sender", "[factories][just]") { - using t = decltype(ex::just(1)); - static_assert(ex::sender, "ex::just must return a sender"); - REQUIRE(ex::sender); - REQUIRE(ex::enable_sender); -} - -TEST_CASE("just can handle multiple values", "[factories][just]") { - bool executed{false}; - auto f = [&](int x, double d) { - CHECK(x == 3); - CHECK(d == 0.14); - executed = true; - }; - auto op = ex::connect(ex::just(3, 0.14), make_fun_receiver(std::move(f))); - ex::start(op); - CHECK(executed); -} - -TEST_CASE("value types are properly set for just", "[factories][just]") { - check_val_types>>(ex::just(1)); - check_val_types>>(ex::just(3.14)); - check_val_types>>(ex::just(std::string{})); - - check_val_types>>(ex::just(1, 3.14)); - check_val_types>>( - ex::just(1, 3.14, std::string{})); -} - -TEST_CASE("error types are properly set for just", "[factories][just]") { - check_err_types>(ex::just(1)); -} - -TEST_CASE("just cannot call set_stopped", "[factories][just]") { - check_sends_stopped(ex::just(1)); -} - -TEST_CASE("just works with value type", "[factories][just]") { - auto snd = ex::just(std::string{"hello"}); - - // Check reported type - check_val_types>>(snd); - - // Check received value - std::string res; - typecat cat{typecat::undefined}; - auto op = ex::connect(std::move(snd), typecat_receiver{&res, &cat}); - ex::start(op); - CHECK(res == "hello"); - CHECK(cat == typecat::rvalref); -} - -TEST_CASE("just works with ref type", "[factories][just]") { - std::string original{"hello"}; - auto snd = ex::just(original); - - // Check reported type - check_val_types>>(snd); - - // Check received value - std::string res; - typecat cat{typecat::undefined}; - auto op = ex::connect(std::move(snd), typecat_receiver{&res, &cat}); - ex::start(op); - CHECK(res == original); - CHECK(cat == typecat::rvalref); -} - -TEST_CASE("just works with const-ref type", "[factories][just]") { - const std::string original{"hello"}; - auto snd = ex::just(original); - - // Check reported type - check_val_types>>(snd); - - // Check received value - std::string res; - typecat cat{typecat::undefined}; - auto op = ex::connect(std::move(snd), typecat_receiver{&res, &cat}); - ex::start(op); - CHECK(res == original); - CHECK(cat == typecat::rvalref); +namespace { + + TEST_CASE("Simple test for just", "[factories][just]") { + auto o1 = ex::connect(ex::just(1), expect_value_receiver(1)); + ex::start(o1); + auto o2 = ex::connect(ex::just(2), expect_value_receiver(2)); + ex::start(o2); + auto o3 = ex::connect(ex::just(3), expect_value_receiver(3)); + ex::start(o3); + + auto o4 = ex::connect( + ex::just(std::string("this")), expect_value_receiver(std::string("this"))); + ex::start(o4); + auto o5 = ex::connect( + ex::just(std::string("that")), expect_value_receiver(std::string("that"))); + ex::start(o5); + } + + TEST_CASE("just returns a sender", "[factories][just]") { + using t = decltype(ex::just(1)); + static_assert(ex::sender, "ex::just must return a sender"); + REQUIRE(ex::sender); + REQUIRE(ex::enable_sender); + } + + TEST_CASE("just can handle multiple values", "[factories][just]") { + bool executed{false}; + auto f = [&](int x, double d) { + CHECK(x == 3); + CHECK(d == 0.14); + executed = true; + }; + auto op = ex::connect(ex::just(3, 0.14), make_fun_receiver(std::move(f))); + ex::start(op); + CHECK(executed); + } + + TEST_CASE("value types are properly set for just", "[factories][just]") { + check_val_types>>(ex::just(1)); + check_val_types>>(ex::just(3.14)); + check_val_types>>(ex::just(std::string{})); + + check_val_types>>(ex::just(1, 3.14)); + check_val_types>>( + ex::just(1, 3.14, std::string{})); + } + + TEST_CASE("error types are properly set for just", "[factories][just]") { + check_err_types>(ex::just(1)); + } + + TEST_CASE("just cannot call set_stopped", "[factories][just]") { + check_sends_stopped(ex::just(1)); + } + + TEST_CASE("just works with value type", "[factories][just]") { + auto snd = ex::just(std::string{"hello"}); + + // Check reported type + check_val_types>>(snd); + + // Check received value + std::string res; + typecat cat{typecat::undefined}; + auto op = ex::connect(std::move(snd), typecat_receiver{&res, &cat}); + ex::start(op); + CHECK(res == "hello"); + CHECK(cat == typecat::rvalref); + } + + TEST_CASE("just works with ref type", "[factories][just]") { + std::string original{"hello"}; + auto snd = ex::just(original); + + // Check reported type + check_val_types>>(snd); + + // Check received value + std::string res; + typecat cat{typecat::undefined}; + auto op = ex::connect(std::move(snd), typecat_receiver{&res, &cat}); + ex::start(op); + CHECK(res == original); + CHECK(cat == typecat::rvalref); + } + + TEST_CASE("just works with const-ref type", "[factories][just]") { + const std::string original{"hello"}; + auto snd = ex::just(original); + + // Check reported type + check_val_types>>(snd); + + // Check received value + std::string res; + typecat cat{typecat::undefined}; + auto op = ex::connect(std::move(snd), typecat_receiver{&res, &cat}); + ex::start(op); + CHECK(res == original); + CHECK(cat == typecat::rvalref); + } } diff --git a/test/stdexec/algos/factories/test_just_error.cpp b/test/stdexec/algos/factories/test_just_error.cpp index 92ab63ab0..07a582e02 100644 --- a/test/stdexec/algos/factories/test_just_error.cpp +++ b/test/stdexec/algos/factories/test_just_error.cpp @@ -21,43 +21,48 @@ namespace ex = stdexec; -TEST_CASE("Simple test for just_error", "[factories][just_error]") { - auto op = ex::connect(ex::just_error(std::exception_ptr{}), expect_error_receiver{}); - ex::start(op); - // receiver ensures that set_error() is called -} +namespace { -TEST_CASE("just_error returns a sender", "[factories][just_error]") { - using t = decltype(ex::just_error(std::exception_ptr{})); - static_assert(ex::sender, "ex::just_error must return a sender"); -} + TEST_CASE("Simple test for just_error", "[factories][just_error]") { + auto op = ex::connect(ex::just_error(std::exception_ptr{}), expect_error_receiver{}); + ex::start(op); + // receiver ensures that set_error() is called + } -TEST_CASE("just_error returns a typed sender", "[factories][just_error]") { - using t = decltype(ex::just_error(std::exception_ptr{})); - static_assert(ex::sender_in, "ex::just_error must return a sender"); -} + TEST_CASE("just_error returns a sender", "[factories][just_error]") { + using t = decltype(ex::just_error(std::exception_ptr{})); + static_assert(ex::sender, "ex::just_error must return a sender"); + } -TEST_CASE("error types are properly set for just_error", "[factories][just_error]") { - check_err_types>(ex::just_error(1)); -} + TEST_CASE("just_error returns a typed sender", "[factories][just_error]") { + using t = decltype(ex::just_error(std::exception_ptr{})); + static_assert(ex::sender_in, "ex::just_error must return a sender"); + } -TEST_CASE("error types are properly set for just_error", "[factories][just_error]") { - // we should not get std::exception_ptr twice - check_err_types>(ex::just_error(std::exception_ptr())); -} + TEST_CASE("error types are properly set for just_error", "[factories][just_error]") { + check_err_types>(ex::just_error(1)); + } -TEST_CASE("value types are properly set for just_error", "[factories][just_error]") { - // there is no variant of calling `set_value(recv)` - check_val_types>(ex::just_error(1)); -} + TEST_CASE( + "error types are properly set for just_error", + "[factories][just_error]") { + // we should not get std::exception_ptr twice + check_err_types>(ex::just_error(std::exception_ptr())); + } -TEST_CASE("just_error cannot call set_stopped", "[factories][just_error]") { - check_sends_stopped(ex::just_error(1)); -} + TEST_CASE("value types are properly set for just_error", "[factories][just_error]") { + // there is no variant of calling `set_value(recv)` + check_val_types>(ex::just_error(1)); + } + + TEST_CASE("just_error cannot call set_stopped", "[factories][just_error]") { + check_sends_stopped(ex::just_error(1)); + } -TEST_CASE("just_error removes cv qualifier for the given type", "[factories][just_error]") { - std::string str{"hello"}; - const std::string& crefstr = str; - auto snd = ex::just_error(crefstr); - check_err_types>(snd); + TEST_CASE("just_error removes cv qualifier for the given type", "[factories][just_error]") { + std::string str{"hello"}; + const std::string& crefstr = str; + auto snd = ex::just_error(crefstr); + check_err_types>(snd); + } } diff --git a/test/stdexec/algos/factories/test_just_stopped.cpp b/test/stdexec/algos/factories/test_just_stopped.cpp index 0f477b76c..64ff026fc 100644 --- a/test/stdexec/algos/factories/test_just_stopped.cpp +++ b/test/stdexec/algos/factories/test_just_stopped.cpp @@ -19,32 +19,35 @@ #include #include -TEST_CASE("Simple test for just_stopped", "[factories][just_stopped]") { - auto op = ex::connect(ex::just_stopped(), expect_stopped_receiver{}); - ex::start(op); - // receiver ensures that set_stopped() is called -} +namespace { -TEST_CASE("just_stopped returns a sender", "[factories][just_stopped]") { - using t = decltype(ex::just_stopped()); - static_assert(ex::sender, "ex::just_stopped must return a sender"); - REQUIRE(ex::sender); -} + TEST_CASE("Simple test for just_stopped", "[factories][just_stopped]") { + auto op = ex::connect(ex::just_stopped(), expect_stopped_receiver{}); + ex::start(op); + // receiver ensures that set_stopped() is called + } -TEST_CASE("just_stopped returns a typed sender", "[factories][just_stopped]") { - using t = decltype(ex::just_stopped()); - static_assert(ex::sender_in, "ex::just_stopped must return a sender"); -} + TEST_CASE("just_stopped returns a sender", "[factories][just_stopped]") { + using t = decltype(ex::just_stopped()); + static_assert(ex::sender, "ex::just_stopped must return a sender"); + REQUIRE(ex::sender); + } -TEST_CASE("value types are properly set for just_stopped", "[factories][just_stopped]") { - check_val_types>(ex::just_stopped()); -} + TEST_CASE("just_stopped returns a typed sender", "[factories][just_stopped]") { + using t = decltype(ex::just_stopped()); + static_assert(ex::sender_in, "ex::just_stopped must return a sender"); + } -TEST_CASE("error types are properly set for just_stopped", "[factories][just_stopped]") { - // no errors sent by just_stopped - check_err_types>(ex::just_stopped()); -} + TEST_CASE("value types are properly set for just_stopped", "[factories][just_stopped]") { + check_val_types>(ex::just_stopped()); + } + + TEST_CASE("error types are properly set for just_stopped", "[factories][just_stopped]") { + // no errors sent by just_stopped + check_err_types>(ex::just_stopped()); + } -TEST_CASE("just_stopped advertises that it can call set_stopped", "[factories][just_stopped]") { - check_sends_stopped(ex::just_stopped()); + TEST_CASE("just_stopped advertises that it can call set_stopped", "[factories][just_stopped]") { + check_sends_stopped(ex::just_stopped()); + } } diff --git a/test/stdexec/algos/factories/test_read.cpp b/test/stdexec/algos/factories/test_read.cpp index db0727b74..5797a34c8 100644 --- a/test/stdexec/algos/factories/test_read.cpp +++ b/test/stdexec/algos/factories/test_read.cpp @@ -18,6 +18,9 @@ namespace ex = stdexec; -TEST_CASE("read returns empty env", "[factories][read]") { - check_env_type(ex::read(ex::get_allocator)); +namespace { + + TEST_CASE("read returns empty env", "[factories][read]") { + check_env_type(ex::read(ex::get_allocator)); + } } diff --git a/test/stdexec/algos/factories/test_transfer_just.cpp b/test/stdexec/algos/factories/test_transfer_just.cpp index b919bd5f5..8f11717bf 100644 --- a/test/stdexec/algos/factories/test_transfer_just.cpp +++ b/test/stdexec/algos/factories/test_transfer_just.cpp @@ -22,174 +22,179 @@ namespace ex = stdexec; -TEST_CASE("transfer_just returns a sender", "[factories][transfer_just]") { - auto snd = ex::transfer_just(inline_scheduler{}, 13); - static_assert(ex::sender); - (void) snd; -} - -TEST_CASE("transfer_just with environment returns a sender", "[factories][transfer_just]") { - auto snd = ex::transfer_just(inline_scheduler{}, 13); - static_assert(ex::sender_in); - (void) snd; -} - -TEST_CASE("transfer_just simple example", "[factories][transfer_just]") { - inline_scheduler sched; - auto snd = ex::transfer_just(sched, 13); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE( - "transfer_just calls the receiver when the scheduler dictates", - "[factories][transfer_just]") { - int recv_value{0}; - impulse_scheduler sched; - auto snd = ex::transfer_just(sched, 13); - auto op = ex::connect(snd, expect_value_receiver_ex{recv_value}); - ex::start(op); - // Up until this point, the scheduler didn't start any task; no effect expected - CHECK(recv_value == 0); - - // Tell the scheduler to start executing one task - sched.start_next(); - CHECK(recv_value == 13); -} - -TEST_CASE("transfer_just can be called with value type scheduler", "[factories][transfer_just]") { - auto snd = ex::transfer_just(inline_scheduler{}, 13); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("transfer_just can be called with rvalue ref scheduler", "[factories][transfer_just]") { - auto snd = ex::transfer_just(inline_scheduler{}, 13); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("transfer_just can be called with const ref scheduler", "[factories][transfer_just]") { - const inline_scheduler sched; - auto snd = ex::transfer_just(sched, 13); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("transfer_just can be called with ref scheduler", "[factories][transfer_just]") { - inline_scheduler sched; - auto snd = ex::transfer_just(sched, 13); - auto op = ex::connect(std::move(snd), expect_value_receiver{13}); - ex::start(op); - // The receiver checks if we receive the right value -} - -TEST_CASE("transfer_just forwards set_error calls", "[factories][transfer_just]") { - error_scheduler sched{std::exception_ptr{}}; - auto snd = ex::transfer_just(sched, 13); - auto op = ex::connect(std::move(snd), expect_error_receiver{}); - ex::start(op); - // The receiver checks if we receive an error -} - -TEST_CASE("transfer_just forwards set_error calls of other types", "[factories][transfer_just]") { - error_scheduler sched{std::string{"error"}}; - auto snd = ex::transfer_just(sched, 13); - auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"error"}}); - ex::start(op); - // The receiver checks if we receive an error -} - -TEST_CASE("transfer_just forwards set_stopped calls", "[factories][transfer_just]") { - stopped_scheduler sched{}; - auto snd = ex::transfer_just(sched, 13); - auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); - ex::start(op); - // The receiver checks if we receive the stopped signal -} - -TEST_CASE( - "transfer_just has the values_type corresponding to the given values", - "[factories][transfer_just]") { - inline_scheduler sched{}; - - check_val_types>>(ex::transfer_just(sched, 1)); - check_val_types>>(ex::transfer_just(sched, 3, 0.14)); - check_val_types>>( - ex::transfer_just(sched, 3, 0.14, std::string{"pi"})); -} - -TEST_CASE("transfer_just keeps error_types from scheduler's sender", "[factories][transfer_just]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - error_scheduler sched3{43}; - - check_err_types>(ex::transfer_just(sched1, 1)); - check_err_types>(ex::transfer_just(sched2, 2)); - check_err_types>(ex::transfer_just(sched3, 3)); -} - -TEST_CASE( - "transfer_just sends an exception_ptr if value types are potentially throwing when copied", - "[factories][transfer_just]") { - inline_scheduler sched{}; - - check_err_types>(ex::transfer_just(sched, potentially_throwing{})); -} - -TEST_CASE( - "transfer_just keeps sends_stopped from scheduler's sender", - "[factories][transfer_just]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - stopped_scheduler sched3{}; - - check_sends_stopped(ex::transfer_just(sched1, 1)); - check_sends_stopped(ex::transfer_just(sched2, 2)); - check_sends_stopped(ex::transfer_just(sched3, 3)); -} - -TEST_CASE("transfer_just advertises its completion scheduler", "[factories][transfer_just]") { - inline_scheduler sched1{}; - error_scheduler sched2{}; - stopped_scheduler sched3{}; - - REQUIRE( - ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched1, 1))) - == sched1); - REQUIRE( - ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched1, 1))) - == sched1); - - REQUIRE( - ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched2, 2))) - == sched2); - REQUIRE( - ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched2, 2))) - == sched2); - - REQUIRE( - ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched3, 3))) - == sched3); - REQUIRE( - ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched3, 3))) - == sched3); -} - -// Modify the value when we invoke this custom defined transfer_just implementation -auto tag_invoke(decltype(ex::transfer_just), inline_scheduler sched, std::string value) { - return ex::transfer(ex::just("Hello, " + value), sched); -} - -TEST_CASE("transfer_just can be customized", "[factories][transfer_just]") { - // The customization will alter the value passed in - auto snd = ex::transfer_just(inline_scheduler{}, std::string{"world"}); - std::string res; - auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); - ex::start(op); - REQUIRE(res == "Hello, world"); +namespace { + TEST_CASE("transfer_just returns a sender", "[factories][transfer_just]") { + auto snd = ex::transfer_just(inline_scheduler{}, 13); + static_assert(ex::sender); + (void) snd; + } + + TEST_CASE("transfer_just with environment returns a sender", "[factories][transfer_just]") { + auto snd = ex::transfer_just(inline_scheduler{}, 13); + static_assert(ex::sender_in); + (void) snd; + } + + TEST_CASE("transfer_just simple example", "[factories][transfer_just]") { + inline_scheduler sched; + auto snd = ex::transfer_just(sched, 13); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE( + "transfer_just calls the receiver when the scheduler dictates", + "[factories][transfer_just]") { + int recv_value{0}; + impulse_scheduler sched; + auto snd = ex::transfer_just(sched, 13); + auto op = ex::connect(snd, expect_value_receiver_ex{recv_value}); + ex::start(op); + // Up until this point, the scheduler didn't start any task; no effect expected + CHECK(recv_value == 0); + + // Tell the scheduler to start executing one task + sched.start_next(); + CHECK(recv_value == 13); + } + + TEST_CASE("transfer_just can be called with value type scheduler", "[factories][transfer_just]") { + auto snd = ex::transfer_just(inline_scheduler{}, 13); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("transfer_just can be called with rvalue ref scheduler", "[factories][transfer_just]") { + auto snd = ex::transfer_just(inline_scheduler{}, 13); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("transfer_just can be called with const ref scheduler", "[factories][transfer_just]") { + const inline_scheduler sched; + auto snd = ex::transfer_just(sched, 13); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("transfer_just can be called with ref scheduler", "[factories][transfer_just]") { + inline_scheduler sched; + auto snd = ex::transfer_just(sched, 13); + auto op = ex::connect(std::move(snd), expect_value_receiver{13}); + ex::start(op); + // The receiver checks if we receive the right value + } + + TEST_CASE("transfer_just forwards set_error calls", "[factories][transfer_just]") { + error_scheduler sched{std::exception_ptr{}}; + auto snd = ex::transfer_just(sched, 13); + auto op = ex::connect(std::move(snd), expect_error_receiver{}); + ex::start(op); + // The receiver checks if we receive an error + } + + TEST_CASE("transfer_just forwards set_error calls of other types", "[factories][transfer_just]") { + error_scheduler sched{std::string{"error"}}; + auto snd = ex::transfer_just(sched, 13); + auto op = ex::connect(std::move(snd), expect_error_receiver{std::string{"error"}}); + ex::start(op); + // The receiver checks if we receive an error + } + + TEST_CASE("transfer_just forwards set_stopped calls", "[factories][transfer_just]") { + stopped_scheduler sched{}; + auto snd = ex::transfer_just(sched, 13); + auto op = ex::connect(std::move(snd), expect_stopped_receiver{}); + ex::start(op); + // The receiver checks if we receive the stopped signal + } + + TEST_CASE( + "transfer_just has the values_type corresponding to the given values", + "[factories][transfer_just]") { + inline_scheduler sched{}; + + check_val_types>>(ex::transfer_just(sched, 1)); + check_val_types>>(ex::transfer_just(sched, 3, 0.14)); + check_val_types>>( + ex::transfer_just(sched, 3, 0.14, std::string{"pi"})); + } + + TEST_CASE( + "transfer_just keeps error_types from scheduler's sender", + "[factories][transfer_just]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + error_scheduler sched3{43}; + + check_err_types>(ex::transfer_just(sched1, 1)); + check_err_types>(ex::transfer_just(sched2, 2)); + check_err_types>(ex::transfer_just(sched3, 3)); + } + + TEST_CASE( + "transfer_just sends an exception_ptr if value types are potentially throwing when copied", + "[factories][transfer_just]") { + inline_scheduler sched{}; + + check_err_types>( + ex::transfer_just(sched, potentially_throwing{})); + } + + TEST_CASE( + "transfer_just keeps sends_stopped from scheduler's sender", + "[factories][transfer_just]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + stopped_scheduler sched3{}; + + check_sends_stopped(ex::transfer_just(sched1, 1)); + check_sends_stopped(ex::transfer_just(sched2, 2)); + check_sends_stopped(ex::transfer_just(sched3, 3)); + } + + TEST_CASE("transfer_just advertises its completion scheduler", "[factories][transfer_just]") { + inline_scheduler sched1{}; + error_scheduler sched2{}; + stopped_scheduler sched3{}; + + REQUIRE( + ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched1, 1))) + == sched1); + REQUIRE( + ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched1, 1))) + == sched1); + + REQUIRE( + ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched2, 2))) + == sched2); + REQUIRE( + ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched2, 2))) + == sched2); + + REQUIRE( + ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched3, 3))) + == sched3); + REQUIRE( + ex::get_completion_scheduler(ex::get_env(ex::transfer_just(sched3, 3))) + == sched3); + } + + // Modify the value when we invoke this custom defined transfer_just implementation + auto tag_invoke(decltype(ex::transfer_just), inline_scheduler sched, std::string value) { + return ex::transfer(ex::just("Hello, " + value), sched); + } + + TEST_CASE("transfer_just can be customized", "[factories][transfer_just]") { + // The customization will alter the value passed in + auto snd = ex::transfer_just(inline_scheduler{}, std::string{"world"}); + std::string res; + auto op = ex::connect(std::move(snd), expect_value_receiver_ex{res}); + ex::start(op); + REQUIRE(res == "Hello, world"); + } } diff --git a/test/stdexec/algos/other/test_execute.cpp b/test/stdexec/algos/other/test_execute.cpp index e31c0679b..c71876df2 100644 --- a/test/stdexec/algos/other/test_execute.cpp +++ b/test/stdexec/algos/other/test_execute.cpp @@ -34,38 +34,43 @@ using namespace std::chrono_literals; // Trying to test `execute` with error flows will result in calling `std::terminate()`. // We don't want that -TEST_CASE("execute works with inline scheduler", "[other][execute]") { - inline_scheduler sched; - bool called{false}; - ex::execute(sched, [&] { called = true; }); - // The function should already have been called - CHECK(called); -} +namespace { -TEST_CASE("execute works with schedulers that need to be triggered manually", "[other][execute]") { - impulse_scheduler sched; - bool called{false}; - ex::execute(sched, [&] { called = true; }); - // The function has not yet been called - CHECK_FALSE(called); - // After an impulse to the scheduler, the function should have been called - sched.start_next(); - CHECK(called); -} + TEST_CASE("execute works with inline scheduler", "[other][execute]") { + inline_scheduler sched; + bool called{false}; + ex::execute(sched, [&] { called = true; }); + // The function should already have been called + CHECK(called); + } -TEST_CASE("execute works on a thread pool", "[other][execute]") { - exec::static_thread_pool pool{2}; - std::atomic called{false}; - { - // launch some work on the thread pool - ex::execute(pool.get_scheduler(), [&] { called.store(true, std::memory_order_relaxed); }); + TEST_CASE( + "execute works with schedulers that need to be triggered manually", + "[other][execute]") { + impulse_scheduler sched; + bool called{false}; + ex::execute(sched, [&] { called = true; }); + // The function has not yet been called + CHECK_FALSE(called); + // After an impulse to the scheduler, the function should have been called + sched.start_next(); + CHECK(called); } - // wait for the work to be executed, with timeout - // perform a poor-man's sync - // NOTE: it's a shame that the `join` method in static_thread_pool is not public - for (int i = 0; i < 1000 && !called.load(std::memory_order_relaxed); i++) { - std::this_thread::sleep_for(1ms); + + TEST_CASE("execute works on a thread pool", "[other][execute]") { + exec::static_thread_pool pool{2}; + std::atomic called{false}; + { + // launch some work on the thread pool + ex::execute(pool.get_scheduler(), [&] { called.store(true, std::memory_order_relaxed); }); + } + // wait for the work to be executed, with timeout + // perform a poor-man's sync + // NOTE: it's a shame that the `join` method in static_thread_pool is not public + for (int i = 0; i < 1000 && !called.load(std::memory_order_relaxed); i++) { + std::this_thread::sleep_for(1ms); + } + // the work should be executed + REQUIRE(called); } - // the work should be executed - REQUIRE(called); } diff --git a/test/stdexec/concepts/test_awaitables.cpp b/test/stdexec/concepts/test_awaitables.cpp index 2ba1e5b81..6edd978c3 100644 --- a/test/stdexec/concepts/test_awaitables.cpp +++ b/test/stdexec/concepts/test_awaitables.cpp @@ -28,238 +28,255 @@ namespace ex = stdexec; -template -concept sender_with_env = // - ex::sender && // - requires(const Sender& s) { // - ex::get_env(s); +namespace { + + template + concept sender_with_env = // + ex::sender && // + requires(const Sender& s) { // + ex::get_env(s); + }; + + template + struct promise { + __coro::coroutine_handle get_return_object() { + return {__coro::coroutine_handle::from_promise(*this)}; + } + + __coro::suspend_always initial_suspend() noexcept { + return {}; + } + + __coro::suspend_always final_suspend() noexcept { + return {}; + } + + void return_void() { + } + + void unhandled_exception() { + } + + template + auto await_transform(T&&...) noexcept { + return Awaiter{}; + } }; -template -struct promise { - __coro::coroutine_handle get_return_object() { - return {__coro::coroutine_handle::from_promise(*this)}; - } - - __coro::suspend_always initial_suspend() noexcept { - return {}; - } + struct awaiter { + bool await_ready() { + return true; + } - __coro::suspend_always final_suspend() noexcept { - return {}; - } + bool await_suspend(__coro::coroutine_handle<>) { + return false; + } - void return_void() { - } - - void unhandled_exception() { - } + bool await_resume() { + return false; + } + }; - template - auto await_transform(T&&...) noexcept { - return Awaiter{}; - } -}; + using dependent = ex::dependent_completion_signatures; -struct awaiter { - bool await_ready() { - return true; - } + STDEXEC_PRAGMA_PUSH() + STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas") + STDEXEC_PRAGMA_IGNORE_GNU("-Wundefined-internal") - bool await_suspend(__coro::coroutine_handle<>) { - return false; - } + template + struct awaitable_sender_1 { + Awaiter operator co_await() { + return {}; + } + }; - bool await_resume() { - return false; - } -}; + struct awaitable_sender_2 { + using promise_type = promise<__coro::suspend_always>; -using dependent = ex::dependent_completion_signatures; + private: + friend dependent operator co_await(awaitable_sender_2) { + return {}; + } + }; -template -struct awaitable_sender_1 { - Awaiter operator co_await(); -}; + struct awaitable_sender_3 { + using promise_type = promise; -struct awaitable_sender_2 { - using promise_type = promise<__coro::suspend_always>; + private: + friend dependent operator co_await(awaitable_sender_3) { + return {}; + } + }; - private: - friend dependent operator co_await(awaitable_sender_2); -}; + STDEXEC_PRAGMA_POP() -struct awaitable_sender_3 { - using promise_type = promise; + struct awaitable_sender_4 { + using promise_type = promise<__coro::suspend_always>; - private: - friend dependent operator co_await(awaitable_sender_3); -}; + private: + template + friend awaiter tag_invoke(ex::as_awaitable_t, awaitable_sender_4, Promise&) { + return {}; + } -struct awaitable_sender_4 { - using promise_type = promise<__coro::suspend_always>; + friend dependent tag_invoke(ex::as_awaitable_t, awaitable_sender_4, ex::no_env_promise&) { + return {}; + } + }; - private: - template - friend awaiter tag_invoke(ex::as_awaitable_t, awaitable_sender_4, Promise&) { - return {}; - } + struct awaitable_sender_5 { + private: + template + friend awaiter tag_invoke(ex::as_awaitable_t, awaitable_sender_5, Promise&) { + return {}; + } + }; - friend dependent tag_invoke(ex::as_awaitable_t, awaitable_sender_4, ex::no_env_promise&) { - return {}; - } -}; + template + void test_awaitable_sender1(Signatures*, Awaiter&&) { + static_assert(ex::sender>); + static_assert(sender_with_env>); + static_assert(ex::__awaitable>); -struct awaitable_sender_5 { - private: - template - friend awaiter tag_invoke(ex::as_awaitable_t, awaitable_sender_5, Promise&) { - return {}; + static_assert(!ex::__get_completion_signatures:: + __with_member_alias, ex::empty_env>); + static_assert( + std::is_same_v>, Signatures>); } -}; - -template -void test_awaitable_sender1(Signatures*, Awaiter&&) { - static_assert(ex::sender>); - static_assert(sender_with_env>); - static_assert(ex::__awaitable>); - - static_assert(!ex::__get_completion_signatures:: - __with_member_alias, ex::empty_env>); - static_assert( - std::is_same_v>, Signatures>); -} -void test_awaitable_sender2() { - static_assert(ex::sender); - static_assert(sender_with_env); - static_assert(!ex::sender_in); + void test_awaitable_sender2() { + static_assert(ex::sender); + static_assert(sender_with_env); + static_assert(!ex::sender_in); - static_assert(ex::__awaitable); - static_assert(ex::__awaitable>); + static_assert(ex::__awaitable); + static_assert(ex::__awaitable>); - static_assert( - !ex::__get_completion_signatures::__with_member_alias); + static_assert( + !ex::__get_completion_signatures::__with_member_alias); #if STDEXEC_LEGACY_R5_CONCEPTS() - static_assert(std::is_same_v, dependent>); + static_assert(std::is_same_v, dependent>); #endif -} + } -void test_awaitable_sender3() { - static_assert(ex::sender); - static_assert(sender_with_env); - static_assert(!ex::sender_in); + void test_awaitable_sender3() { + static_assert(ex::sender); + static_assert(sender_with_env); + static_assert(!ex::sender_in); - static_assert(ex::__awaiter); - static_assert(ex::__awaitable); - static_assert(ex::__awaitable>); + static_assert(ex::__awaiter); + static_assert(ex::__awaitable); + static_assert(ex::__awaitable>); - static_assert( - !ex::__get_completion_signatures::__with_member_alias); + static_assert( + !ex::__get_completion_signatures::__with_member_alias); #if STDEXEC_LEGACY_R5_CONCEPTS() - static_assert(std::is_same_v, dependent>); + static_assert(std::is_same_v, dependent>); #endif -} + } -template -void test_awaitable_sender4(Signatures*) { - static_assert(ex::sender); - static_assert(sender_with_env); - static_assert(ex::sender_in); + template + void test_awaitable_sender4(Signatures*) { + static_assert(ex::sender); + static_assert(sender_with_env); + static_assert(ex::sender_in); - static_assert(ex::__awaiter); - static_assert(!ex::__awaitable); - static_assert(ex::__awaitable>); - static_assert(ex::__awaitable); - static_assert(ex::__awaitable>); + static_assert(ex::__awaiter); + static_assert(!ex::__awaitable); + static_assert(ex::__awaitable>); + static_assert(ex::__awaitable); + static_assert(ex::__awaitable>); - static_assert( - !ex::__get_completion_signatures::__with_member_alias); + static_assert( + !ex::__get_completion_signatures::__with_member_alias); #if STDEXEC_LEGACY_R5_CONCEPTS() - static_assert(std::is_same_v, dependent>); + static_assert(std::is_same_v, dependent>); #endif - static_assert( - !ex::__get_completion_signatures::__with_member_alias); + static_assert( + !ex::__get_completion_signatures::__with_member_alias); - static_assert( - std::is_same_v< ex::completion_signatures_of_t, Signatures>); -} + static_assert( + std:: + is_same_v< ex::completion_signatures_of_t, Signatures>); + } -struct connect_awaitable_promise : ex::with_awaitable_senders { }; + struct connect_awaitable_promise : ex::with_awaitable_senders { }; -template -void test_awaitable_sender5(Signatures*) { - static_assert(ex::sender); - static_assert(sender_with_env); - static_assert(ex::sender_in); + template + void test_awaitable_sender5(Signatures*) { + static_assert(ex::sender); + static_assert(sender_with_env); + static_assert(ex::sender_in); - static_assert(ex::__awaiter); - static_assert(!ex::__awaitable); - static_assert(ex::__awaitable>); - static_assert(ex::__awaitable); - static_assert(ex::__awaitable>); + static_assert(ex::__awaiter); + static_assert(!ex::__awaitable); + static_assert(ex::__awaitable>); + static_assert(ex::__awaitable); + static_assert(ex::__awaitable>); - static_assert( - !ex::__get_completion_signatures::__with_member_alias); + static_assert( + !ex::__get_completion_signatures::__with_member_alias); - static_assert(std::is_same_v, Signatures>); - static_assert( - std::is_same_v, Signatures>); -} + static_assert(std::is_same_v, Signatures>); + static_assert( + std::is_same_v, Signatures>); + } -template -auto signature_error_values(Error, Values...) -> ex:: - completion_signatures* { - return {}; -} + template + auto signature_error_values(Error, Values...) -> ex:: + completion_signatures* { + return {}; + } -TEST_CASE("get completion_signatures for awaitables", "[sndtraits][awaitables]") { - ::test_awaitable_sender1(signature_error_values(std::exception_ptr()), __coro::suspend_always{}); - ::test_awaitable_sender1( - signature_error_values( - std::exception_ptr(), ex::__await_result_t>()), - awaiter{}); + TEST_CASE("get completion_signatures for awaitables", "[sndtraits][awaitables]") { + ::test_awaitable_sender1( + signature_error_values(std::exception_ptr()), __coro::suspend_always{}); + ::test_awaitable_sender1( + signature_error_values( + std::exception_ptr(), ex::__await_result_t>()), + awaiter{}); - ::test_awaitable_sender2(); + ::test_awaitable_sender2(); - ::test_awaitable_sender3(); + ::test_awaitable_sender3(); - ::test_awaitable_sender4(signature_error_values( - std::exception_ptr(), ex::__await_result_t>())); + ::test_awaitable_sender4(signature_error_values( + std::exception_ptr(), ex::__await_result_t>())); - ::test_awaitable_sender5(signature_error_values( - std::exception_ptr(), ex::__await_result_t())); -} + ::test_awaitable_sender5(signature_error_values( + std::exception_ptr(), ex::__await_result_t())); + } -struct awaitable_env { }; + struct awaitable_env { }; -template -struct awaitable_with_get_env { - Awaiter operator co_await(); + template + struct awaitable_with_get_env { + Awaiter operator co_await(); - friend awaitable_env tag_invoke(ex::get_env_t, const awaitable_with_get_env&) noexcept { - return {}; - } -}; + friend awaitable_env tag_invoke(ex::get_env_t, const awaitable_with_get_env&) noexcept { + return {}; + } + }; -TEST_CASE("get_env for awaitables", "[sndtraits][awaitables]") { - check_env_type(awaitable_sender_1{}); - check_env_type(awaitable_sender_2{}); - check_env_type(awaitable_sender_3{}); - check_env_type(awaitable_with_get_env{}); -} + TEST_CASE("get_env for awaitables", "[sndtraits][awaitables]") { + check_env_type(awaitable_sender_1{}); + check_env_type(awaitable_sender_2{}); + check_env_type(awaitable_sender_3{}); + check_env_type(awaitable_with_get_env{}); + } -TEST_CASE("env_promise bug when CWG 2369 is fixed", "[sndtraits][awaitables]") { - exec::static_thread_pool ctx{1}; - ex::scheduler auto sch = ctx.get_scheduler(); - ex::sender auto snd = ex::when_all(ex::then(ex::schedule(sch), []() {})); + TEST_CASE("env_promise bug when CWG 2369 is fixed", "[sndtraits][awaitables]") { + exec::static_thread_pool ctx{1}; + ex::scheduler auto sch = ctx.get_scheduler(); + ex::sender auto snd = ex::when_all(ex::then(ex::schedule(sch), []() {})); - using _Awaitable = decltype(snd); - using _Promise = ex::__env_promise; - static_assert(!ex::__awaitable<_Awaitable, _Promise>); + using _Awaitable = decltype(snd); + using _Promise = ex::__env_promise; + static_assert(!ex::__awaitable<_Awaitable, _Promise>); + } } #endif // !STDEXEC_STD_NO_COROUTINES_ diff --git a/test/stdexec/concepts/test_concept_operation_state.cpp b/test/stdexec/concepts/test_concept_operation_state.cpp index e6c2cde31..c760ee3b3 100644 --- a/test/stdexec/concepts/test_concept_operation_state.cpp +++ b/test/stdexec/concepts/test_concept_operation_state.cpp @@ -19,36 +19,46 @@ namespace ex = stdexec; -struct op_except { - op_except() = default; - op_except(op_except&&) = delete; +namespace { - friend void tag_invoke(ex::start_t, op_except&) { - } -}; + STDEXEC_PRAGMA_PUSH() + STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas") + STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-function") + STDEXEC_PRAGMA_IGNORE_GNU("-Wunneeded-internal-declaration") -struct op_noexcept { - op_noexcept() = default; - op_noexcept(op_noexcept&&) = delete; + struct op_except { + op_except() = default; + op_except(op_except&&) = delete; - friend void tag_invoke(ex::start_t, op_noexcept&) noexcept { - } -}; + friend void tag_invoke(ex::start_t, op_except&) { + } + }; -// TEST_CASE( -// "type with start CPO that throws is not an operation_state", -// "[concepts][operation_state]") { -// REQUIRE(!ex::operation_state); -// } + struct op_noexcept { + op_noexcept() = default; + op_noexcept(op_noexcept&&) = delete; -TEST_CASE("type with start CPO noexcept is an operation_state", "[concepts][operation_state]") { - REQUIRE(ex::operation_state); -} + friend void tag_invoke(ex::start_t, op_noexcept&) noexcept { + } + }; -TEST_CASE("reference type is not an operation_state", "[concepts][operation_state]") { - REQUIRE(!ex::operation_state); -} + STDEXEC_PRAGMA_POP() + + // TEST_CASE( + // "type with start CPO that throws is not an operation_state", + // "[concepts][operation_state]") { + // REQUIRE(!ex::operation_state); + // } -TEST_CASE("pointer type is not an operation_state", "[concepts][operation_state]") { - REQUIRE(!ex::operation_state); + TEST_CASE("type with start CPO noexcept is an operation_state", "[concepts][operation_state]") { + REQUIRE(ex::operation_state); + } + + TEST_CASE("reference type is not an operation_state", "[concepts][operation_state]") { + REQUIRE(!ex::operation_state); + } + + TEST_CASE("pointer type is not an operation_state", "[concepts][operation_state]") { + REQUIRE(!ex::operation_state); + } } diff --git a/test/stdexec/concepts/test_concept_scheduler.cpp b/test/stdexec/concepts/test_concept_scheduler.cpp index 8bbe50f79..d156b2595 100644 --- a/test/stdexec/concepts/test_concept_scheduler.cpp +++ b/test/stdexec/concepts/test_concept_scheduler.cpp @@ -19,181 +19,191 @@ namespace ex = stdexec; -template -struct default_env { - template - friend Scheduler tag_invoke(ex::get_completion_scheduler_t, const default_env&) { - return {}; - } -}; +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-function") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunneeded-internal-declaration") -struct my_scheduler { - struct my_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(), // - ex::set_error_t(std::exception_ptr), // - ex::set_stopped_t()>; +namespace { - friend default_env tag_invoke(ex::get_env_t, const my_sender&) noexcept { + template + struct default_env { + template + friend Scheduler tag_invoke(ex::get_completion_scheduler_t, const default_env&) { return {}; } }; - friend my_sender tag_invoke(ex::schedule_t, my_scheduler) { - return {}; - } + struct my_scheduler { + struct my_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(), // + ex::set_error_t(std::exception_ptr), // + ex::set_stopped_t()>; - friend bool operator==(my_scheduler, my_scheduler) noexcept { - return true; - } + friend default_env tag_invoke(ex::get_env_t, const my_sender&) noexcept { + return {}; + } + }; + + friend my_sender tag_invoke(ex::schedule_t, my_scheduler) { + return {}; + } + + friend bool operator==(my_scheduler, my_scheduler) noexcept { + return true; + } + + friend bool operator!=(my_scheduler, my_scheduler) noexcept { + return false; + } + }; - friend bool operator!=(my_scheduler, my_scheduler) noexcept { - return false; + TEST_CASE("type with schedule CPO models scheduler", "[concepts][scheduler]") { + REQUIRE(ex::scheduler); + REQUIRE(ex::sender); } -}; -TEST_CASE("type with schedule CPO models scheduler", "[concepts][scheduler]") { - REQUIRE(ex::scheduler); - REQUIRE(ex::sender); -} + struct no_schedule_cpo { + friend void tag_invoke(int, no_schedule_cpo) { + } + }; -struct no_schedule_cpo { - friend void tag_invoke(int, no_schedule_cpo) { + TEST_CASE("type without schedule CPO doesn't model scheduler", "[concepts][scheduler]") { + REQUIRE(!ex::scheduler); } -}; -TEST_CASE("type without schedule CPO doesn't model scheduler", "[concepts][scheduler]") { - REQUIRE(!ex::scheduler); -} + struct my_scheduler_except { + struct my_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(), // + ex::set_error_t(std::exception_ptr), // + ex::set_stopped_t()>; -struct my_scheduler_except { - struct my_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(), // - ex::set_error_t(std::exception_ptr), // - ex::set_stopped_t()>; + friend default_env tag_invoke(ex::get_env_t, const my_sender&) noexcept { + return {}; + } + }; - friend default_env tag_invoke(ex::get_env_t, const my_sender&) noexcept { + friend my_sender tag_invoke(ex::schedule_t, my_scheduler_except) { + throw std::logic_error("err"); return {}; } - }; - friend my_sender tag_invoke(ex::schedule_t, my_scheduler_except) { - throw std::logic_error("err"); - return {}; - } + friend bool operator==(my_scheduler_except, my_scheduler_except) noexcept { + return true; + } - friend bool operator==(my_scheduler_except, my_scheduler_except) noexcept { - return true; - } + friend bool operator!=(my_scheduler_except, my_scheduler_except) noexcept { + return false; + } + }; - friend bool operator!=(my_scheduler_except, my_scheduler_except) noexcept { - return false; + TEST_CASE("type with schedule that throws is a scheduler", "[concepts][scheduler]") { + REQUIRE(ex::scheduler); } -}; -TEST_CASE("type with schedule that throws is a scheduler", "[concepts][scheduler]") { - REQUIRE(ex::scheduler); -} + struct noeq_sched { + struct my_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(), // + ex::set_error_t(std::exception_ptr), // + ex::set_stopped_t()>; -struct noeq_sched { - struct my_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(), // - ex::set_error_t(std::exception_ptr), // - ex::set_stopped_t()>; + friend default_env tag_invoke(ex::get_env_t, const my_sender&) noexcept { + return {}; + } + }; - friend default_env tag_invoke(ex::get_env_t, const my_sender&) noexcept { + friend my_sender tag_invoke(ex::schedule_t, noeq_sched) { return {}; } }; - friend my_sender tag_invoke(ex::schedule_t, noeq_sched) { - return {}; + TEST_CASE("type w/o equality operations do not model scheduler", "[concepts][scheduler]") { + REQUIRE(!ex::scheduler); } -}; -TEST_CASE("type w/o equality operations do not model scheduler", "[concepts][scheduler]") { - REQUIRE(!ex::scheduler); -} - -struct sched_no_completion { - struct my_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(), // - ex::set_error_t(std::exception_ptr), // - ex::set_stopped_t()>; - - struct env { - friend sched_no_completion tag_invoke( // - ex::get_completion_scheduler_t, // - const env&) noexcept { + struct sched_no_completion { + struct my_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(), // + ex::set_error_t(std::exception_ptr), // + ex::set_stopped_t()>; + + struct env { + friend sched_no_completion tag_invoke( // + ex::get_completion_scheduler_t, // + const env&) noexcept { + return {}; + } + }; + + friend env tag_invoke(ex::get_env_t, const my_sender&) noexcept { return {}; } }; - friend env tag_invoke(ex::get_env_t, const my_sender&) noexcept { + friend my_sender tag_invoke(ex::schedule_t, sched_no_completion) { return {}; } - }; - friend my_sender tag_invoke(ex::schedule_t, sched_no_completion) { - return {}; - } + friend bool operator==(sched_no_completion, sched_no_completion) noexcept { + return true; + } - friend bool operator==(sched_no_completion, sched_no_completion) noexcept { - return true; - } + friend bool operator!=(sched_no_completion, sched_no_completion) noexcept { + return false; + } + }; - friend bool operator!=(sched_no_completion, sched_no_completion) noexcept { - return false; + TEST_CASE( + "not a scheduler if the returned sender doesn't have get_completion_scheduler of set_value", + "[concepts][scheduler]") { + REQUIRE(!ex::scheduler); } -}; - -TEST_CASE( - "not a scheduler if the returned sender doesn't have get_completion_scheduler of set_value", - "[concepts][scheduler]") { - REQUIRE(!ex::scheduler); -} #if STDEXEC_LEGACY_R5_CONCEPTS() -struct sched_no_env { - // P2300R5 senders defined sender queries on the sender itself. - struct my_sender { - // Intentionally left out: - //using is_sender = void; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(), // - ex::set_error_t(std::exception_ptr), // - ex::set_stopped_t()>; + struct sched_no_env { + // P2300R5 senders defined sender queries on the sender itself. + struct my_sender { + // Intentionally left out: + //using is_sender = void; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(), // + ex::set_error_t(std::exception_ptr), // + ex::set_stopped_t()>; + + template + friend sched_no_env tag_invoke(ex::get_completion_scheduler_t, my_sender) { + return {}; + } + }; - template - friend sched_no_env tag_invoke(ex::get_completion_scheduler_t, my_sender) { + friend my_sender tag_invoke(ex::schedule_t, sched_no_env) { return {}; } - }; - friend my_sender tag_invoke(ex::schedule_t, sched_no_env) { - return {}; - } + friend bool operator==(sched_no_env, sched_no_env) noexcept { + return true; + } - friend bool operator==(sched_no_env, sched_no_env) noexcept { - return true; - } + friend bool operator!=(sched_no_env, sched_no_env) noexcept { + return false; + } + }; - friend bool operator!=(sched_no_env, sched_no_env) noexcept { - return false; + TEST_CASE( + "type without sender get_env is still a scheduler", + "[concepts][scheduler][r5_backwards_compatibility]") { + static_assert(ex::scheduler); + REQUIRE(ex::scheduler); } -}; - -TEST_CASE( - "type without sender get_env is still a scheduler", - "[concepts][scheduler][r5_backwards_compatibility]") { - static_assert(ex::scheduler); - REQUIRE(ex::scheduler); -} #endif +} + +STDEXEC_PRAGMA_POP() \ No newline at end of file diff --git a/test/stdexec/concepts/test_concepts_sender.cpp b/test/stdexec/concepts/test_concepts_sender.cpp index 803a25365..862a3dc0d 100644 --- a/test/stdexec/concepts/test_concepts_sender.cpp +++ b/test/stdexec/concepts/test_concepts_sender.cpp @@ -21,263 +21,279 @@ namespace ex = stdexec; -struct not_a_sender { }; +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-function") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunneeded-internal-declaration") -TEST_CASE("Sender concept rejects non-sender types", "[concepts][sender]") { - REQUIRE(!ex::sender); - REQUIRE(!ex::sender); -} +namespace { + + struct not_a_sender { }; + + TEST_CASE("Sender concept rejects non-sender types", "[concepts][sender]") { + REQUIRE(!ex::sender); + REQUIRE(!ex::sender); + } -struct P2300r7_sender_1 { - using is_sender = void; -}; + struct P2300r7_sender_1 { + using is_sender = void; + }; -struct P2300r7_sender_2 { }; + struct P2300r7_sender_2 { }; +} // namespace template <> inline constexpr bool stdexec::enable_sender = true; -TEST_CASE("Sender concept accepts P2300R7-style senders", "[concepts][sender]") { - REQUIRE(ex::sender); - REQUIRE(ex::sender); -} +namespace { -#if !STDEXEC_STD_NO_COROUTINES_ -struct awaiter { - bool await_ready(); - void await_suspend(__coro::coroutine_handle<>); - void await_resume(); -}; - -struct awaitable { - friend awaiter operator co_await(awaitable) { - return {}; + TEST_CASE("Sender concept accepts P2300R7-style senders", "[concepts][sender]") { + REQUIRE(ex::sender); + REQUIRE(ex::sender); } -}; -struct as_awaitable { - template - friend awaitable tag_invoke(ex::as_awaitable_t, as_awaitable, Promise&) { - return {}; +#if !STDEXEC_STD_NO_COROUTINES_ + struct awaiter { + bool await_ready(); + void await_suspend(__coro::coroutine_handle<>); + void await_resume(); + }; + + struct awaitable { + friend awaiter operator co_await(awaitable) { + return {}; + } + }; + + struct as_awaitable { + template + friend awaitable tag_invoke(ex::as_awaitable_t, as_awaitable, Promise&) { + return {}; + } + }; + + TEST_CASE("Sender concept accepts awaiters and awaitables", "[concepts][sender]") { + REQUIRE(ex::sender); + REQUIRE(ex::sender); + REQUIRE(ex::sender); } -}; - -TEST_CASE("Sender concept accepts awaiters and awaitables", "[concepts][sender]") { - REQUIRE(ex::sender); - REQUIRE(ex::sender); - REQUIRE(ex::sender); -} #endif -struct oper { - oper() = default; - oper(oper&&) = delete; - - friend void tag_invoke(ex::start_t, oper&) noexcept { + struct oper { + oper() = default; + oper(oper&&) = delete; + + friend void tag_invoke(ex::start_t, oper&) noexcept { + } + }; + + struct my_sender0 { + using is_sender = void; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(), // + ex::set_error_t(std::exception_ptr), // + ex::set_stopped_t()>; + + friend oper tag_invoke(ex::connect_t, my_sender0, empty_recv::recv0&& r) { + return {}; + } + + friend empty_env tag_invoke(ex::get_env_t, const my_sender0&) noexcept { + return {}; + } + }; + + TEST_CASE("type w/ proper types, is a sender", "[concepts][sender]") { + REQUIRE(ex::sender); + REQUIRE(ex::sender_in); + + REQUIRE(ex::sender_of); + REQUIRE(ex::sender_of); + REQUIRE(ex::sender_of); + REQUIRE(ex::sender_of); + REQUIRE(ex::sender_of); + REQUIRE(ex::sender_of); } -}; -struct my_sender0 { - using is_sender = void; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(), // - ex::set_error_t(std::exception_ptr), // - ex::set_stopped_t()>; + TEST_CASE( + "sender that accepts a void sender models sender_to the given sender", + "[concepts][sender]") { + REQUIRE(ex::sender_to); + } - friend oper tag_invoke(ex::connect_t, my_sender0, empty_recv::recv0&& r) { - return {}; + struct my_sender_int { + using is_sender = void; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(int), // + ex::set_error_t(std::exception_ptr), // + ex::set_stopped_t()>; + + friend oper tag_invoke(ex::connect_t, my_sender_int, empty_recv::recv_int&& r) { + return {}; + } + + friend empty_env tag_invoke(ex::get_env_t, const my_sender_int&) noexcept { + return {}; + } + }; + + TEST_CASE("my_sender_int is a sender", "[concepts][sender]") { + REQUIRE(ex::sender); + REQUIRE(ex::sender_in); + REQUIRE(ex::sender_of); + REQUIRE(ex::sender_of); } - friend empty_env tag_invoke(ex::get_env_t, const my_sender0&) noexcept { - return {}; + TEST_CASE( + "sender that accepts an int receiver models sender_to the given receiver", + "[concepts][sender]") { + REQUIRE(ex::sender_to); } -}; - -TEST_CASE("type w/ proper types, is a sender", "[concepts][sender]") { - REQUIRE(ex::sender); - REQUIRE(ex::sender_in); - - REQUIRE(ex::sender_of); - REQUIRE(ex::sender_of); - REQUIRE(ex::sender_of); - REQUIRE(ex::sender_of); - REQUIRE(ex::sender_of); - REQUIRE(ex::sender_of); -} -TEST_CASE( - "sender that accepts a void sender models sender_to the given sender", - "[concepts][sender]") { - REQUIRE(ex::sender_to); -} + TEST_CASE( + "not all combinations of senders & receivers satisfy the sender_to concept", + "[concepts][sender]") { + REQUIRE_FALSE(ex::sender_to); + REQUIRE_FALSE(ex::sender_to); + REQUIRE_FALSE(ex::sender_to); + REQUIRE_FALSE(ex::sender_to); + REQUIRE_FALSE(ex::sender_to); + REQUIRE_FALSE(ex::sender_to); + } -struct my_sender_int { - using is_sender = void; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(int), // - ex::set_error_t(std::exception_ptr), // - ex::set_stopped_t()>; + TEST_CASE( + "can query completion signatures for a typed sender that sends nothing", + "[concepts][sender]") { + check_val_types>>(my_sender0{}); + check_err_types>(my_sender0{}); + check_sends_stopped(my_sender0{}); + REQUIRE(ex::sender_of); + } - friend oper tag_invoke(ex::connect_t, my_sender_int, empty_recv::recv_int&& r) { - return {}; + TEST_CASE( + "can query completion signatures for a typed sender that sends int", + "[concepts][sender]") { + check_val_types>>(my_sender_int{}); + check_err_types>(my_sender_int{}); + check_sends_stopped(my_sender_int{}); + REQUIRE(ex::sender_of); } - friend empty_env tag_invoke(ex::get_env_t, const my_sender_int&) noexcept { - return {}; + struct multival_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(int, double), // + ex::set_value_t(short, long), // + ex::set_error_t(std::exception_ptr)>; + + friend oper tag_invoke(ex::connect_t, multival_sender, empty_recv::recv_int&& r) { + return {}; + } + + friend empty_env tag_invoke(ex::get_env_t, const multival_sender&) noexcept { + return {}; + } + }; + + TEST_CASE( + "check completion signatures for sender that advertises multiple sets of values", + "[concepts][sender]") { + check_val_types, type_array>>( + multival_sender{}); + check_err_types>(multival_sender{}); + check_sends_stopped(multival_sender{}); + REQUIRE_FALSE(ex::sender_of); } -}; -TEST_CASE("my_sender_int is a sender", "[concepts][sender]") { - REQUIRE(ex::sender); - REQUIRE(ex::sender_in); - REQUIRE(ex::sender_of); - REQUIRE(ex::sender_of); -} + struct ec_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(), // + ex::set_error_t(std::exception_ptr), // + ex::set_error_t(int)>; + + friend oper tag_invoke(ex::connect_t, ec_sender, empty_recv::recv_int&& r) { + return {}; + } + + friend empty_env tag_invoke(ex::get_env_t, const ec_sender&) noexcept { + return {}; + } + }; + + TEST_CASE( + "check completion signatures for sender that also supports error codes", + "[concepts][sender]") { + check_val_types>>(ec_sender{}); + check_err_types>(ec_sender{}); + check_sends_stopped(ec_sender{}); + REQUIRE(ex::sender_of); + } -TEST_CASE( - "sender that accepts an int receiver models sender_to the given receiver", - "[concepts][sender]") { - REQUIRE(ex::sender_to); -} + struct my_r5_sender0 { + using is_sender = void; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(), // + ex::set_error_t(std::exception_ptr), // + ex::set_stopped_t()>; + + friend oper tag_invoke(ex::connect_t, my_r5_sender0, empty_recv::recv0&& r) { + return {}; + } + }; + + TEST_CASE("r5 sender emits deprecated diagnostics", "[concepts][sender]") { + ex::get_env(my_r5_sender0{}); + static_assert(ex::sender); + static_assert(std::same_as< + decltype(ex::get_completion_signatures(my_r5_sender0{}, ex::__default_env{})), + my_r5_sender0::completion_signatures>); + } -TEST_CASE( - "not all combinations of senders & receivers satisfy the sender_to concept", - "[concepts][sender]") { - REQUIRE_FALSE(ex::sender_to); - REQUIRE_FALSE(ex::sender_to); - REQUIRE_FALSE(ex::sender_to); - REQUIRE_FALSE(ex::sender_to); - REQUIRE_FALSE(ex::sender_to); - REQUIRE_FALSE(ex::sender_to); -} +#if !STDEXEC_NVHPC() + // nvc++ doesn't yet implement subsumption correctly + struct not_a_sender_tag { }; -TEST_CASE( - "can query completion signatures for a typed sender that sends nothing", - "[concepts][sender]") { - check_val_types>>(my_sender0{}); - check_err_types>(my_sender0{}); - check_sends_stopped(my_sender0{}); - REQUIRE(ex::sender_of); -} + struct sender_no_env_tag { }; -TEST_CASE( - "can query completion signatures for a typed sender that sends int", - "[concepts][sender]") { - check_val_types>>(my_sender_int{}); - check_err_types>(my_sender_int{}); - check_sends_stopped(my_sender_int{}); - REQUIRE(ex::sender_of); -} + struct sender_env_tag { }; -struct multival_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(int, double), // - ex::set_value_t(short, long), // - ex::set_error_t(std::exception_ptr)>; + struct sender_of_tag { }; - friend oper tag_invoke(ex::connect_t, multival_sender, empty_recv::recv_int&& r) { + template + not_a_sender_tag test_subsumption(T&&) { return {}; } - friend empty_env tag_invoke(ex::get_env_t, const multival_sender&) noexcept { + template + sender_no_env_tag test_subsumption(T&&) { return {}; } -}; - -TEST_CASE( - "check completion signatures for sender that advertises multiple sets of values", - "[concepts][sender]") { - check_val_types, type_array>>(multival_sender{}); - check_err_types>(multival_sender{}); - check_sends_stopped(multival_sender{}); - REQUIRE_FALSE(ex::sender_of); -} -struct ec_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(), // - ex::set_error_t(std::exception_ptr), // - ex::set_error_t(int)>; - - friend oper tag_invoke(ex::connect_t, ec_sender, empty_recv::recv_int&& r) { + template T> + sender_env_tag test_subsumption(T&&) { return {}; } - friend empty_env tag_invoke(ex::get_env_t, const ec_sender&) noexcept { + template T> + sender_of_tag test_subsumption(T&&) { return {}; } -}; - -TEST_CASE( - "check completion signatures for sender that also supports error codes", - "[concepts][sender]") { - check_val_types>>(ec_sender{}); - check_err_types>(ec_sender{}); - check_sends_stopped(ec_sender{}); - REQUIRE(ex::sender_of); -} - -struct my_r5_sender0 { - using is_sender = void; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(), // - ex::set_error_t(std::exception_ptr), // - ex::set_stopped_t()>; - friend oper tag_invoke(ex::connect_t, my_r5_sender0, empty_recv::recv0&& r) { - return {}; + template + void has_type(T&&) { + REQUIRE(ex::same_as); } -}; - -TEST_CASE("r5 sender emits deprecated diagnostics", "[concepts][sender]") { - ex::get_env(my_r5_sender0{}); - static_assert(ex::sender); - static_assert(std::same_as< - decltype(ex::get_completion_signatures(my_r5_sender0{}, ex::__default_env{})), - my_r5_sender0::completion_signatures>); -} - -#if !STDEXEC_NVHPC() -// nvc++ doesn't yet implement subsumption correctly -struct not_a_sender_tag { }; - -struct sender_no_env_tag { }; - -struct sender_env_tag { }; -struct sender_of_tag { }; - -template -not_a_sender_tag test_subsumption(T&&) { - return {}; -} - -template -sender_no_env_tag test_subsumption(T&&) { - return {}; -} - -template T> -sender_env_tag test_subsumption(T&&) { - return {}; -} - -template T> -sender_of_tag test_subsumption(T&&) { - return {}; -} - -template -void has_type(T&&) { - REQUIRE(ex::same_as); + TEST_CASE( + "check for subsumption relationships between the sender concepts", + "[concepts][sender]") { + ::has_type(::test_subsumption(42)); + ::has_type(::test_subsumption(ex::get_scheduler())); + ::has_type(::test_subsumption(ex::just(42))); + ::has_type(::test_subsumption(ex::just())); + } +#endif } -TEST_CASE("check for subsumption relationships between the sender concepts", "[concepts][sender]") { - ::has_type(::test_subsumption(42)); - ::has_type(::test_subsumption(ex::get_scheduler())); - ::has_type(::test_subsumption(ex::just(42))); - ::has_type(::test_subsumption(ex::just())); -} -#endif +STDEXEC_PRAGMA_POP() diff --git a/test/stdexec/cpos/cpo_helpers.cuh b/test/stdexec/cpos/cpo_helpers.cuh index d59c6a5da..e6aeaff66 100644 --- a/test/stdexec/cpos/cpo_helpers.cuh +++ b/test/stdexec/cpos/cpo_helpers.cuh @@ -19,86 +19,89 @@ namespace ex = stdexec; -enum class scope_t { - free_standing, - scheduler -}; - -template -struct cpo_t { - using is_sender = void; - constexpr static scope_t scope = Scope; - - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(), // - ex::set_error_t(std::exception_ptr), // - ex::set_stopped_t()>; - - friend empty_env tag_invoke(ex::get_env_t, const cpo_t&) noexcept { - return {}; - } -}; - -template -struct free_standing_sender_t { - using is_sender = void; - using __id = free_standing_sender_t; - using __t = free_standing_sender_t; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(), // - ex::set_error_t(std::exception_ptr), // - ex::set_stopped_t()>; - - template - friend auto tag_invoke(CPO, const free_standing_sender_t& self, Ts&&...) noexcept { - return cpo_t{}; - } - - friend empty_env tag_invoke(ex::get_env_t, const free_standing_sender_t&) noexcept { - return {}; - } -}; - -template -struct scheduler_t { - using __id = scheduler_t; - using __t = scheduler_t; - - struct env_t { - template Tag> - friend scheduler_t tag_invoke(ex::get_completion_scheduler_t, const env_t&) noexcept { +namespace { + + enum class scope_t { + free_standing, + scheduler + }; + + template + struct cpo_t { + using is_sender = void; + constexpr static scope_t scope = Scope; + + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(), // + ex::set_error_t(std::exception_ptr), // + ex::set_stopped_t()>; + + friend empty_env tag_invoke(ex::get_env_t, const cpo_t&) noexcept { return {}; } }; - struct sender_t { + template + struct free_standing_sender_t { using is_sender = void; - using __id = sender_t; - using __t = sender_t; + using __id = free_standing_sender_t; + using __t = free_standing_sender_t; using completion_signatures = ex::completion_signatures< // ex::set_value_t(), // ex::set_error_t(std::exception_ptr), // ex::set_stopped_t()>; - friend env_t tag_invoke(ex::get_env_t, const sender_t&) noexcept { + template + friend auto tag_invoke(CPO, const free_standing_sender_t& self, Ts&&...) noexcept { + return cpo_t{}; + } + + friend empty_env tag_invoke(ex::get_env_t, const free_standing_sender_t&) noexcept { return {}; } }; - template - friend auto tag_invoke(CPO, const scheduler_t&, Ts&&...) noexcept { - return cpo_t{}; - } + template + struct scheduler_t { + using __id = scheduler_t; + using __t = scheduler_t; + + struct env_t { + template Tag> + friend scheduler_t tag_invoke(ex::get_completion_scheduler_t, const env_t&) noexcept { + return {}; + } + }; + + struct sender_t { + using is_sender = void; + using __id = sender_t; + using __t = sender_t; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(), // + ex::set_error_t(std::exception_ptr), // + ex::set_stopped_t()>; + + friend env_t tag_invoke(ex::get_env_t, const sender_t&) noexcept { + return {}; + } + }; + + template + friend auto tag_invoke(CPO, const scheduler_t&, Ts&&...) noexcept { + return cpo_t{}; + } - friend sender_t tag_invoke(ex::schedule_t, scheduler_t) { - return sender_t{}; - } + friend sender_t tag_invoke(ex::schedule_t, scheduler_t) { + return sender_t{}; + } - friend bool operator==(scheduler_t, scheduler_t) noexcept { - return true; - } + friend bool operator==(scheduler_t, scheduler_t) noexcept { + return true; + } - friend bool operator!=(scheduler_t, scheduler_t) noexcept { - return false; - } -}; + friend bool operator!=(scheduler_t, scheduler_t) noexcept { + return false; + } + }; +} diff --git a/test/stdexec/cpos/test_cpo_bulk.cpp b/test/stdexec/cpos/test_cpo_bulk.cpp index d825dd71c..c6cc43b9f 100644 --- a/test/stdexec/cpos/test_cpo_bulk.cpp +++ b/test/stdexec/cpos/test_cpo_bulk.cpp @@ -17,36 +17,39 @@ #include "cpo_helpers.cuh" #include -TEST_CASE("bulk is customizable", "[cpo][cpo_bulk]") { - const auto n = 42; - const auto f = [](int) { - }; +namespace { - SECTION("by free standing sender") { - free_standing_sender_t snd{}; + TEST_CASE("bulk is customizable", "[cpo][cpo_bulk]") { + const auto n = 42; + const auto f = [](int) { + }; - { - constexpr scope_t scope = decltype(snd | ex::bulk(n, f))::scope; - STATIC_REQUIRE(scope == scope_t::free_standing); - } + SECTION("by free standing sender") { + free_standing_sender_t snd{}; + + { + constexpr scope_t scope = decltype(snd | ex::bulk(n, f))::scope; + STATIC_REQUIRE(scope == scope_t::free_standing); + } - { - constexpr scope_t scope = decltype(ex::bulk(snd, n, f))::scope; - STATIC_REQUIRE(scope == scope_t::free_standing); + { + constexpr scope_t scope = decltype(ex::bulk(snd, n, f))::scope; + STATIC_REQUIRE(scope == scope_t::free_standing); + } } - } - SECTION("by completion scheduler") { - scheduler_t::sender_t snd{}; + SECTION("by completion scheduler") { + scheduler_t::sender_t snd{}; - { - constexpr scope_t scope = decltype(snd | ex::bulk(n, f))::scope; - STATIC_REQUIRE(scope == scope_t::scheduler); - } + { + constexpr scope_t scope = decltype(snd | ex::bulk(n, f))::scope; + STATIC_REQUIRE(scope == scope_t::scheduler); + } - { - constexpr scope_t scope = decltype(ex::bulk(snd, n, f))::scope; - STATIC_REQUIRE(scope == scope_t::scheduler); + { + constexpr scope_t scope = decltype(ex::bulk(snd, n, f))::scope; + STATIC_REQUIRE(scope == scope_t::scheduler); + } } } } diff --git a/test/stdexec/cpos/test_cpo_connect.cpp b/test/stdexec/cpos/test_cpo_connect.cpp index 25935677e..808bdbe93 100644 --- a/test/stdexec/cpos/test_cpo_connect.cpp +++ b/test/stdexec/cpos/test_cpo_connect.cpp @@ -23,92 +23,102 @@ namespace ex = stdexec; -template -struct op_state : immovable { - int val_; - R recv_; - - friend void tag_invoke(ex::start_t, op_state& self) noexcept { - ex::set_value((R&&) self.recv_, (int) self.val_); - } -}; - -struct my_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures; - - int value_{0}; - - template - friend op_state tag_invoke(ex::connect_t, my_sender&& s, R&& r) { - return {{}, s.value_, (R&&) r}; +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-function") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunneeded-internal-declaration") + +namespace { + + template + struct op_state : immovable { + int val_; + R recv_; + + friend void tag_invoke(ex::start_t, op_state& self) noexcept { + ex::set_value((R&&) self.recv_, (int) self.val_); + } + }; + + struct my_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures; + + int value_{0}; + + template + friend op_state tag_invoke(ex::connect_t, my_sender&& s, R&& r) { + return {{}, s.value_, (R&&) r}; + } + + friend empty_env tag_invoke(ex::get_env_t, const my_sender&) noexcept { + return {}; + } + }; + + struct my_sender_unconstrained { + using is_sender = void; + using completion_signatures = ex::completion_signatures; + + int value_{0}; + + template // accept any type here + friend op_state tag_invoke(ex::connect_t, my_sender_unconstrained&& s, R&& r) { + return {{}, s.value_, (R&&) r}; + } + + friend empty_env tag_invoke(ex::get_env_t, const my_sender_unconstrained&) noexcept { + return {}; + } + }; + + TEST_CASE("can call connect on an appropriate types", "[cpo][cpo_connect]") { + auto op = ex::connect(my_sender{10}, expect_value_receiver{10}); + ex::start(op); + // the receiver will check the received value } - friend empty_env tag_invoke(ex::get_env_t, const my_sender&) noexcept { - return {}; + TEST_CASE("cannot connect sender with invalid receiver", "[cpo][cpo_connect]") { + static_assert(ex::sender); + REQUIRE_FALSE(std::invocable); } -}; - -struct my_sender_unconstrained { - using is_sender = void; - using completion_signatures = ex::completion_signatures; - int value_{0}; - - template // accept any type here - friend op_state tag_invoke(ex::connect_t, my_sender_unconstrained&& s, R&& r) { - return {{}, s.value_, (R&&) r}; + struct strange_receiver { + bool* called_; + + friend inline op_state + tag_invoke(ex::connect_t, my_sender, strange_receiver self) { + *self.called_ = true; + // NOLINTNEXTLINE + return {{}, 19, std::move(self)}; + } + + friend inline void tag_invoke(ex::set_value_t, strange_receiver, int val) noexcept { + REQUIRE(val == 19); + } + + friend void tag_invoke(ex::set_stopped_t, strange_receiver) noexcept { + } + + friend void tag_invoke(ex::set_error_t, strange_receiver, std::exception_ptr) noexcept { + } + + friend empty_env tag_invoke(ex::get_env_t, const strange_receiver&) noexcept { + return {}; + } + }; + + TEST_CASE("connect can be defined in the receiver", "[cpo][cpo_connect]") { + static_assert(ex::sender_to); + bool called{false}; + auto op = ex::connect(my_sender{10}, strange_receiver{&called}); + ex::start(op); + REQUIRE(called); } - friend empty_env tag_invoke(ex::get_env_t, const my_sender_unconstrained&) noexcept { - return {}; + TEST_CASE("tag types can be deduced from ex::connect", "[cpo][cpo_connect]") { + static_assert(std::is_same_v, "type mismatch"); } -}; - -TEST_CASE("can call connect on an appropriate types", "[cpo][cpo_connect]") { - auto op = ex::connect(my_sender{10}, expect_value_receiver{10}); - ex::start(op); - // the receiver will check the received value -} - -TEST_CASE("cannot connect sender with invalid receiver", "[cpo][cpo_connect]") { - static_assert(ex::sender); - REQUIRE_FALSE(std::invocable); } -struct strange_receiver { - bool* called_; - - friend inline op_state - tag_invoke(ex::connect_t, my_sender, strange_receiver self) { - *self.called_ = true; - // NOLINTNEXTLINE - return {{}, 19, std::move(self)}; - } - - friend inline void tag_invoke(ex::set_value_t, strange_receiver, int val) noexcept { - REQUIRE(val == 19); - } - - friend void tag_invoke(ex::set_stopped_t, strange_receiver) noexcept { - } - - friend void tag_invoke(ex::set_error_t, strange_receiver, std::exception_ptr) noexcept { - } - - friend empty_env tag_invoke(ex::get_env_t, const strange_receiver&) noexcept { - return {}; - } -}; - -TEST_CASE("connect can be defined in the receiver", "[cpo][cpo_connect]") { - static_assert(ex::sender_to); - bool called{false}; - auto op = ex::connect(my_sender{10}, strange_receiver{&called}); - ex::start(op); - REQUIRE(called); -} - -TEST_CASE("tag types can be deduced from ex::connect", "[cpo][cpo_connect]") { - static_assert(std::is_same_v, "type mismatch"); -} +STDEXEC_PRAGMA_POP() \ No newline at end of file diff --git a/test/stdexec/cpos/test_cpo_ensure_started.cpp b/test/stdexec/cpos/test_cpo_ensure_started.cpp index c60d0fa4a..a6984508d 100644 --- a/test/stdexec/cpos/test_cpo_ensure_started.cpp +++ b/test/stdexec/cpos/test_cpo_ensure_started.cpp @@ -17,16 +17,19 @@ #include "cpo_helpers.cuh" #include -TEST_CASE("ensure started is customizable", "[cpo][cpo_ensure_started]") { - SECTION("by free standing sender") { - free_standing_sender_t snd{}; - constexpr scope_t scope = decltype(ex::ensure_started(snd))::scope; - STATIC_REQUIRE(scope == scope_t::free_standing); - } +namespace { + + TEST_CASE("ensure started is customizable", "[cpo][cpo_ensure_started]") { + SECTION("by free standing sender") { + free_standing_sender_t snd{}; + constexpr scope_t scope = decltype(ex::ensure_started(snd))::scope; + STATIC_REQUIRE(scope == scope_t::free_standing); + } - SECTION("by completion scheduler") { - scheduler_t::sender_t snd{}; - constexpr scope_t scope = decltype(ex::ensure_started(snd))::scope; - STATIC_REQUIRE(scope == scope_t::scheduler); + SECTION("by completion scheduler") { + scheduler_t::sender_t snd{}; + constexpr scope_t scope = decltype(ex::ensure_started(snd))::scope; + STATIC_REQUIRE(scope == scope_t::scheduler); + } } } diff --git a/test/stdexec/cpos/test_cpo_receiver.cpp b/test/stdexec/cpos/test_cpo_receiver.cpp index c77427fce..99347150f 100644 --- a/test/stdexec/cpos/test_cpo_receiver.cpp +++ b/test/stdexec/cpos/test_cpo_receiver.cpp @@ -23,243 +23,252 @@ namespace ex = stdexec; -struct recv_value { - int* target_; +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-function") - friend void tag_invoke(ex::set_value_t, recv_value self, int val) noexcept { - *self.target_ = val; - } +namespace { - friend void tag_invoke(ex::set_error_t, recv_value self, int ec) noexcept { - *self.target_ = -ec; - } + struct recv_value { + int* target_; - friend void tag_invoke(ex::set_stopped_t, recv_value self) noexcept { - *self.target_ = INT_MAX; - } + friend void tag_invoke(ex::set_value_t, recv_value self, int val) noexcept { + *self.target_ = val; + } - friend empty_env tag_invoke(ex::get_env_t, const recv_value&) noexcept { - return {}; - } -}; + friend void tag_invoke(ex::set_error_t, recv_value self, int ec) noexcept { + *self.target_ = -ec; + } -struct recv_rvalref { - int* target_; + friend void tag_invoke(ex::set_stopped_t, recv_value self) noexcept { + *self.target_ = INT_MAX; + } - friend void tag_invoke(ex::set_value_t, recv_rvalref&& self, int val) noexcept { - *self.target_ = val; - } + friend empty_env tag_invoke(ex::get_env_t, const recv_value&) noexcept { + return {}; + } + }; - friend void tag_invoke(ex::set_error_t, recv_rvalref&& self, int ec) noexcept { - *self.target_ = -ec; - } + struct recv_rvalref { + int* target_; - friend void tag_invoke(ex::set_stopped_t, recv_rvalref&& self) noexcept { - *self.target_ = INT_MAX; - } + friend void tag_invoke(ex::set_value_t, recv_rvalref&& self, int val) noexcept { + *self.target_ = val; + } - friend empty_env tag_invoke(ex::get_env_t, const recv_rvalref&) noexcept { - return {}; - } -}; + friend void tag_invoke(ex::set_error_t, recv_rvalref&& self, int ec) noexcept { + *self.target_ = -ec; + } -struct recv_ref { - int* target_; + friend void tag_invoke(ex::set_stopped_t, recv_rvalref&& self) noexcept { + *self.target_ = INT_MAX; + } - friend void tag_invoke(ex::set_value_t, recv_ref& self, int val) noexcept { - *self.target_ = val; - } + friend empty_env tag_invoke(ex::get_env_t, const recv_rvalref&) noexcept { + return {}; + } + }; - friend void tag_invoke(ex::set_error_t, recv_ref& self, int ec) noexcept { - *self.target_ = -ec; - } + struct recv_ref { + int* target_; - friend void tag_invoke(ex::set_stopped_t, recv_ref& self) noexcept { - *self.target_ = INT_MAX; - } + friend void tag_invoke(ex::set_value_t, recv_ref& self, int val) noexcept { + *self.target_ = val; + } - friend empty_env tag_invoke(ex::get_env_t, const recv_ref&) noexcept { - return {}; - } -}; + friend void tag_invoke(ex::set_error_t, recv_ref& self, int ec) noexcept { + *self.target_ = -ec; + } -struct recv_cref { - int* target_; + friend void tag_invoke(ex::set_stopped_t, recv_ref& self) noexcept { + *self.target_ = INT_MAX; + } - friend void tag_invoke(ex::set_value_t, const recv_cref& self, int val) noexcept { - *self.target_ = val; - } + friend empty_env tag_invoke(ex::get_env_t, const recv_ref&) noexcept { + return {}; + } + }; - friend void tag_invoke(ex::set_error_t, const recv_cref& self, int ec) noexcept { - *self.target_ = -ec; - } + struct recv_cref { + int* target_; - friend void tag_invoke(ex::set_stopped_t, const recv_cref& self) noexcept { - *self.target_ = INT_MAX; - } + friend void tag_invoke(ex::set_value_t, const recv_cref& self, int val) noexcept { + *self.target_ = val; + } + + friend void tag_invoke(ex::set_error_t, const recv_cref& self, int ec) noexcept { + *self.target_ = -ec; + } - friend empty_env tag_invoke(ex::get_env_t, const recv_cref&) noexcept { - return {}; + friend void tag_invoke(ex::set_stopped_t, const recv_cref& self) noexcept { + *self.target_ = INT_MAX; + } + + friend empty_env tag_invoke(ex::get_env_t, const recv_cref&) noexcept { + return {}; + } + }; + + TEST_CASE("can call set_value on a void receiver", "[cpo][cpo_receiver]") { + ex::set_value(expect_void_receiver{}); } -}; -TEST_CASE("can call set_value on a void receiver", "[cpo][cpo_receiver]") { - ex::set_value(expect_void_receiver{}); -} + TEST_CASE("can call set_value on a int receiver", "[cpo][cpo_receiver]") { + ex::set_value(expect_value_receiver{10}, 10); + } -TEST_CASE("can call set_value on a int receiver", "[cpo][cpo_receiver]") { - ex::set_value(expect_value_receiver{10}, 10); -} + TEST_CASE("can call set_value on a string receiver", "[cpo][cpo_receiver]") { + ex::set_value(expect_value_receiver{std::string{"hello"}}, std::string{"hello"}); + } -TEST_CASE("can call set_value on a string receiver", "[cpo][cpo_receiver]") { - ex::set_value(expect_value_receiver{std::string{"hello"}}, std::string{"hello"}); -} + TEST_CASE("can call set_stopped on a receiver", "[cpo][cpo_receiver]") { + ex::set_stopped(expect_stopped_receiver{}); + } -TEST_CASE("can call set_stopped on a receiver", "[cpo][cpo_receiver]") { - ex::set_stopped(expect_stopped_receiver{}); -} + TEST_CASE("can call set_error on a receiver", "[cpo][cpo_receiver]") { + std::exception_ptr ex; + try { + throw std::bad_alloc{}; + } catch (...) { + ex = std::current_exception(); + } + ex::set_error(expect_error_receiver{}, ex); + } -TEST_CASE("can call set_error on a receiver", "[cpo][cpo_receiver]") { - std::exception_ptr ex; - try { - throw std::bad_alloc{}; - } catch (...) { - ex = std::current_exception(); + TEST_CASE("can call set_error with an error code on a receiver", "[cpo][cpo_receiver]") { + std::error_code errCode{100, std::generic_category()}; + ex::set_error(expect_error_receiver{errCode}, errCode); } - ex::set_error(expect_error_receiver{}, ex); -} -TEST_CASE("can call set_error with an error code on a receiver", "[cpo][cpo_receiver]") { - std::error_code errCode{100, std::generic_category()}; - ex::set_error(expect_error_receiver{errCode}, errCode); -} + TEST_CASE("set_value with a value passes the value to the receiver", "[cpo][cpo_receiver]") { + ex::set_value(expect_value_receiver{10}, 10); + } -TEST_CASE("set_value with a value passes the value to the receiver", "[cpo][cpo_receiver]") { - ex::set_value(expect_value_receiver{10}, 10); -} + TEST_CASE("can call set_value on a receiver with plain value type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_value on recv_value"); + int val = 0; + ex::set_value(recv_value{&val}, 10); + REQUIRE(val == 10); + } -TEST_CASE("can call set_value on a receiver with plain value type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_value on recv_value"); - int val = 0; - ex::set_value(recv_value{&val}, 10); - REQUIRE(val == 10); -} + TEST_CASE("can call set_value on a receiver with r-value ref type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_value on recv_rvalref"); + int val = 0; + ex::set_value(recv_rvalref{&val}, 10); + REQUIRE(val == 10); + } -TEST_CASE("can call set_value on a receiver with r-value ref type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_value on recv_rvalref"); - int val = 0; - ex::set_value(recv_rvalref{&val}, 10); - REQUIRE(val == 10); -} + TEST_CASE("can call set_value on a receiver with ref type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_value on recv_ref"); + int val = 0; + recv_ref recv{&val}; + ex::set_value(recv, 10); + REQUIRE(val == 10); + } -TEST_CASE("can call set_value on a receiver with ref type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_value on recv_ref"); - int val = 0; - recv_ref recv{&val}; - ex::set_value(recv, 10); - REQUIRE(val == 10); -} + TEST_CASE("can call set_value on a receiver with const ref type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_value on recv_cref"); + int val = 0; + ex::set_value(recv_cref{&val}, 10); + REQUIRE(val == 10); + } -TEST_CASE("can call set_value on a receiver with const ref type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_value on recv_cref"); - int val = 0; - ex::set_value(recv_cref{&val}, 10); - REQUIRE(val == 10); -} + TEST_CASE("can call set_error on a receiver with plain value type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_error on recv_value"); + int val = 0; + ex::set_error(recv_value{&val}, 10); + REQUIRE(val == -10); + } -TEST_CASE("can call set_error on a receiver with plain value type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_error on recv_value"); - int val = 0; - ex::set_error(recv_value{&val}, 10); - REQUIRE(val == -10); -} + TEST_CASE("can call set_error on a receiver with r-value ref type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_error on recv_rvalref"); + int val = 0; + ex::set_error(recv_rvalref{&val}, 10); + REQUIRE(val == -10); + } -TEST_CASE("can call set_error on a receiver with r-value ref type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_error on recv_rvalref"); - int val = 0; - ex::set_error(recv_rvalref{&val}, 10); - REQUIRE(val == -10); -} + TEST_CASE("can call set_error on a receiver with ref type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_error on recv_ref"); + int val = 0; + recv_ref recv{&val}; + ex::set_error(recv, 10); + REQUIRE(val == -10); + } -TEST_CASE("can call set_error on a receiver with ref type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_error on recv_ref"); - int val = 0; - recv_ref recv{&val}; - ex::set_error(recv, 10); - REQUIRE(val == -10); -} + TEST_CASE("can call set_error on a receiver with const ref type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_error on recv_cref"); + int val = 0; + ex::set_error(recv_cref{&val}, 10); + REQUIRE(val == -10); + } -TEST_CASE("can call set_error on a receiver with const ref type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_error on recv_cref"); - int val = 0; - ex::set_error(recv_cref{&val}, 10); - REQUIRE(val == -10); -} + TEST_CASE("can call set_stopped on a receiver with plain value type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_stopped on recv_value"); + int val = 0; + ex::set_stopped(recv_value{&val}); + REQUIRE(val == INT_MAX); + } -TEST_CASE("can call set_stopped on a receiver with plain value type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_stopped on recv_value"); - int val = 0; - ex::set_stopped(recv_value{&val}); - REQUIRE(val == INT_MAX); -} + TEST_CASE("can call set_stopped on a receiver with r-value ref type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_stopped on recv_rvalref"); + int val = 0; + ex::set_stopped(recv_rvalref{&val}); + REQUIRE(val == INT_MAX); + } -TEST_CASE("can call set_stopped on a receiver with r-value ref type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_stopped on recv_rvalref"); - int val = 0; - ex::set_stopped(recv_rvalref{&val}); - REQUIRE(val == INT_MAX); -} + TEST_CASE("can call set_stopped on a receiver with ref type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_stopped on recv_ref"); + int val = 0; + recv_ref recv{&val}; + ex::set_stopped(recv); + REQUIRE(val == INT_MAX); + } -TEST_CASE("can call set_stopped on a receiver with ref type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_stopped on recv_ref"); - int val = 0; - recv_ref recv{&val}; - ex::set_stopped(recv); - REQUIRE(val == INT_MAX); -} + TEST_CASE("can call set_stopped on a receiver with const ref type", "[cpo][cpo_receiver]") { + static_assert( + std::invocable, "cannot call set_stopped on recv_cref"); + int val = 0; + ex::set_stopped(recv_cref{&val}); + REQUIRE(val == INT_MAX); + } -TEST_CASE("can call set_stopped on a receiver with const ref type", "[cpo][cpo_receiver]") { - static_assert( - std::invocable, "cannot call set_stopped on recv_cref"); - int val = 0; - ex::set_stopped(recv_cref{&val}); - REQUIRE(val == INT_MAX); -} + TEST_CASE("set_value can be called through tag_invoke", "[cpo][cpo_receiver]") { + int val = 0; + tag_invoke(ex::set_value, recv_value{&val}, 10); + REQUIRE(val == 10); + } -TEST_CASE("set_value can be called through tag_invoke", "[cpo][cpo_receiver]") { - int val = 0; - tag_invoke(ex::set_value, recv_value{&val}, 10); - REQUIRE(val == 10); -} + TEST_CASE("set_error can be called through tag_invoke", "[cpo][cpo_receiver]") { + int val = 0; + tag_invoke(ex::set_error, recv_value{&val}, 10); + REQUIRE(val == -10); + } -TEST_CASE("set_error can be called through tag_invoke", "[cpo][cpo_receiver]") { - int val = 0; - tag_invoke(ex::set_error, recv_value{&val}, 10); - REQUIRE(val == -10); -} + TEST_CASE("set_stopped can be called through tag_invoke", "[cpo][cpo_receiver]") { + int val = 0; + tag_invoke(ex::set_stopped, recv_value{&val}); + REQUIRE(val == INT_MAX); + } -TEST_CASE("set_stopped can be called through tag_invoke", "[cpo][cpo_receiver]") { - int val = 0; - tag_invoke(ex::set_stopped, recv_value{&val}); - REQUIRE(val == INT_MAX); + TEST_CASE( + "tag types can be deduced from set_value, set_error and set_stopped", + "[cpo][cpo_receiver]") { + static_assert(std::is_same_v, "type mismatch"); + static_assert(std::is_same_v, "type mismatch"); + static_assert( + std::is_same_v, "type mismatch"); + } } -TEST_CASE( - "tag types can be deduced from set_value, set_error and set_stopped", - "[cpo][cpo_receiver]") { - static_assert(std::is_same_v, "type mismatch"); - static_assert(std::is_same_v, "type mismatch"); - static_assert( - std::is_same_v, "type mismatch"); -} +STDEXEC_PRAGMA_POP() diff --git a/test/stdexec/cpos/test_cpo_schedule.cpp b/test/stdexec/cpos/test_cpo_schedule.cpp index cde2355e9..1a4aa0492 100644 --- a/test/stdexec/cpos/test_cpo_schedule.cpp +++ b/test/stdexec/cpos/test_cpo_schedule.cpp @@ -20,33 +20,42 @@ namespace ex = stdexec; -struct my_sender { - using is_sender = void; - using completion_signatures = ex::completion_signatures< // - ex::set_value_t(), // - ex::set_error_t(std::exception_ptr), // - ex::set_stopped_t()>; - - bool from_scheduler_{false}; - - friend empty_env tag_invoke(ex::get_env_t, const my_sender&) noexcept { - return {}; +STDEXEC_PRAGMA_PUSH() +STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas") +STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-function") + +namespace { + + struct my_sender { + using is_sender = void; + using completion_signatures = ex::completion_signatures< // + ex::set_value_t(), // + ex::set_error_t(std::exception_ptr), // + ex::set_stopped_t()>; + + bool from_scheduler_{false}; + + friend empty_env tag_invoke(ex::get_env_t, const my_sender&) noexcept { + return {}; + } + }; + + struct my_scheduler { + friend my_sender tag_invoke(ex::schedule_t, my_scheduler) { + return my_sender{true}; + } + }; + + TEST_CASE("can call schedule on an appropriate type", "[cpo][cpo_schedule]") { + static_assert(std::invocable, "invalid scheduler type"); + my_scheduler sched; + auto snd = ex::schedule(sched); + CHECK(snd.from_scheduler_); } -}; -struct my_scheduler { - friend my_sender tag_invoke(ex::schedule_t, my_scheduler) { - return my_sender{true}; + TEST_CASE("tag types can be deduced from ex::schedule", "[cpo][cpo_schedule]") { + static_assert(std::is_same_v, "type mismatch"); } -}; - -TEST_CASE("can call schedule on an appropriate type", "[cpo][cpo_schedule]") { - static_assert(std::invocable, "invalid scheduler type"); - my_scheduler sched; - auto snd = ex::schedule(sched); - CHECK(snd.from_scheduler_); } -TEST_CASE("tag types can be deduced from ex::schedule", "[cpo][cpo_schedule]") { - static_assert(std::is_same_v, "type mismatch"); -} +STDEXEC_PRAGMA_POP() diff --git a/test/stdexec/cpos/test_cpo_split.cpp b/test/stdexec/cpos/test_cpo_split.cpp index 0a3b618a5..a1866ae8f 100644 --- a/test/stdexec/cpos/test_cpo_split.cpp +++ b/test/stdexec/cpos/test_cpo_split.cpp @@ -17,32 +17,35 @@ #include "cpo_helpers.cuh" #include -TEST_CASE("split is customizable", "[cpo][cpo_split]") { - SECTION("by free standing sender") { - free_standing_sender_t snd{}; +namespace { - { - constexpr scope_t scope = decltype(ex::split(snd))::scope; - STATIC_REQUIRE(scope == scope_t::free_standing); - } + TEST_CASE("split is customizable", "[cpo][cpo_split]") { + SECTION("by free standing sender") { + free_standing_sender_t snd{}; + + { + constexpr scope_t scope = decltype(ex::split(snd))::scope; + STATIC_REQUIRE(scope == scope_t::free_standing); + } - { - constexpr scope_t scope = decltype(snd | ex::split())::scope; - STATIC_REQUIRE(scope == scope_t::free_standing); + { + constexpr scope_t scope = decltype(snd | ex::split())::scope; + STATIC_REQUIRE(scope == scope_t::free_standing); + } } - } - SECTION("by completion scheduler") { - scheduler_t::sender_t snd{}; + SECTION("by completion scheduler") { + scheduler_t::sender_t snd{}; - { - constexpr scope_t scope = decltype(ex::split(snd))::scope; - STATIC_REQUIRE(scope == scope_t::scheduler); - } + { + constexpr scope_t scope = decltype(ex::split(snd))::scope; + STATIC_REQUIRE(scope == scope_t::scheduler); + } - { - constexpr scope_t scope = decltype(snd | ex::split())::scope; - STATIC_REQUIRE(scope == scope_t::scheduler); + { + constexpr scope_t scope = decltype(snd | ex::split())::scope; + STATIC_REQUIRE(scope == scope_t::scheduler); + } } } } diff --git a/test/stdexec/cpos/test_cpo_start.cpp b/test/stdexec/cpos/test_cpo_start.cpp index 9cc7d55c0..8f31f03ba 100644 --- a/test/stdexec/cpos/test_cpo_start.cpp +++ b/test/stdexec/cpos/test_cpo_start.cpp @@ -20,81 +20,89 @@ namespace ex = stdexec; -struct my_oper : immovable { - bool started_{false}; +namespace { - friend void tag_invoke(ex::start_t, my_oper& self) noexcept { - self.started_ = true; - } -}; + struct my_oper : immovable { + bool started_{false}; -struct op_value /*: immovable*/ { // Intentionally movable! - bool* started_; + friend void tag_invoke(ex::start_t, my_oper& self) noexcept { + self.started_ = true; + } + }; - friend void tag_invoke(ex::start_t, op_value self) noexcept { - *self.started_ = true; - } -}; + struct op_value /*: immovable*/ { // Intentionally movable! + bool* started_; -struct op_rvalref : immovable { - bool* started_; + friend void tag_invoke(ex::start_t, op_value self) noexcept { + *self.started_ = true; + } + }; - friend void tag_invoke(ex::start_t, op_rvalref&& self) noexcept { - *self.started_ = true; - } -}; + STDEXEC_PRAGMA_PUSH() + STDEXEC_PRAGMA_IGNORE_GNU("-Wpragmas") + STDEXEC_PRAGMA_IGNORE_GNU("-Wunused-function") -struct op_ref : immovable { - bool* started_; + struct op_rvalref : immovable { + bool* started_; - friend void tag_invoke(ex::start_t, op_ref& self) noexcept { - *self.started_ = true; - } -}; + friend void tag_invoke(ex::start_t, op_rvalref&& self) noexcept { + *self.started_ = true; + } + }; + STDEXEC_PRAGMA_POP() -struct op_cref : immovable { - bool* started_; + struct op_ref : immovable { + bool* started_; - friend void tag_invoke(ex::start_t, const op_cref& self) noexcept { - *self.started_ = true; - } -}; + friend void tag_invoke(ex::start_t, op_ref& self) noexcept { + *self.started_ = true; + } + }; -TEST_CASE("can call start on an operation state", "[cpo][cpo_start]") { - my_oper op; - ex::start(op); - REQUIRE(op.started_); -} + struct op_cref : immovable { + bool* started_; -TEST_CASE("can call start on an oper with plain value type", "[cpo][cpo_start]") { - static_assert(!std::invocable, "cannot call start on op_value"); - bool started{false}; - op_value op{/*{},*/ &started}; - ex::start(op); - REQUIRE(started); -} + friend void tag_invoke(ex::start_t, const op_cref& self) noexcept { + *self.started_ = true; + } + }; -TEST_CASE("can call start on an oper with r-value ref type", "[cpo][cpo_start]") { - static_assert( - !std::invocable, "should not be able to call start on op_rvalref"); -} + TEST_CASE("can call start on an operation state", "[cpo][cpo_start]") { + my_oper op; + ex::start(op); + REQUIRE(op.started_); + } -TEST_CASE("can call start on an oper with ref type", "[cpo][cpo_start]") { - static_assert(std::invocable, "cannot call start on op_ref"); - bool started{false}; - op_ref op{{}, &started}; - ex::start(op); - REQUIRE(started); -} + TEST_CASE("can call start on an oper with plain value type", "[cpo][cpo_start]") { + static_assert(!std::invocable, "cannot call start on op_value"); + bool started{false}; + op_value op{/*{},*/ &started}; + ex::start(op); + REQUIRE(started); + } -TEST_CASE("can call start on an oper with const ref type", "[cpo][cpo_start]") { - static_assert(std::invocable, "cannot call start on op_cref"); - bool started{false}; - const op_cref op{{}, &started}; - ex::start(op); - REQUIRE(started); -} + TEST_CASE("can call start on an oper with r-value ref type", "[cpo][cpo_start]") { + static_assert( + !std::invocable, "should not be able to call start on op_rvalref"); + } -TEST_CASE("tag types can be deduced from ex::start", "[cpo][cpo_start]") { - static_assert(std::is_same_v, "type mismatch"); + TEST_CASE("can call start on an oper with ref type", "[cpo][cpo_start]") { + static_assert(std::invocable, "cannot call start on op_ref"); + bool started{false}; + op_ref op{{}, &started}; + ex::start(op); + REQUIRE(started); + } + + TEST_CASE("can call start on an oper with const ref type", "[cpo][cpo_start]") { + static_assert(std::invocable, "cannot call start on op_cref"); + bool started{false}; + const op_cref op{{}, &started}; + ex::start(op); + REQUIRE(started); + } + + TEST_CASE("tag types can be deduced from ex::start", "[cpo][cpo_start]") { + static_assert(std::is_same_v, "type mismatch"); + } } diff --git a/test/stdexec/cpos/test_cpo_upon_error.cpp b/test/stdexec/cpos/test_cpo_upon_error.cpp index 8f5764da3..8fbc03b45 100644 --- a/test/stdexec/cpos/test_cpo_upon_error.cpp +++ b/test/stdexec/cpos/test_cpo_upon_error.cpp @@ -17,36 +17,39 @@ #include "cpo_helpers.cuh" #include -TEST_CASE("upon_error is customizable", "[cpo][cpo_upon_error]") { - const auto f = [](std::exception_ptr) { - }; +namespace { - SECTION("by free standing sender") { - free_standing_sender_t snd{}; + TEST_CASE("upon_error is customizable", "[cpo][cpo_upon_error]") { + const auto f = [](std::exception_ptr) { + }; - { - constexpr scope_t scope = decltype(snd | ex::upon_error(f))::scope; - STATIC_REQUIRE(scope == scope_t::free_standing); - } + SECTION("by free standing sender") { + free_standing_sender_t snd{}; + + { + constexpr scope_t scope = decltype(snd | ex::upon_error(f))::scope; + STATIC_REQUIRE(scope == scope_t::free_standing); + } - { - constexpr scope_t scope = decltype(ex::upon_error(snd, f))::scope; - STATIC_REQUIRE(scope == scope_t::free_standing); + { + constexpr scope_t scope = decltype(ex::upon_error(snd, f))::scope; + STATIC_REQUIRE(scope == scope_t::free_standing); + } } - } - SECTION("by completion scheduler") { - scheduler_t::sender_t snd{}; + SECTION("by completion scheduler") { + scheduler_t::sender_t snd{}; - { - constexpr scope_t scope = decltype(snd | ex::upon_error(f))::scope; - STATIC_REQUIRE(scope == scope_t::scheduler); - } + { + constexpr scope_t scope = decltype(snd | ex::upon_error(f))::scope; + STATIC_REQUIRE(scope == scope_t::scheduler); + } - { - ex::get_completion_scheduler(ex::get_env(snd)); - constexpr scope_t scope = decltype(ex::upon_error(snd, f))::scope; - STATIC_REQUIRE(scope == scope_t::scheduler); + { + ex::get_completion_scheduler(ex::get_env(snd)); + constexpr scope_t scope = decltype(ex::upon_error(snd, f))::scope; + STATIC_REQUIRE(scope == scope_t::scheduler); + } } } } diff --git a/test/stdexec/cpos/test_cpo_upon_stopped.cpp b/test/stdexec/cpos/test_cpo_upon_stopped.cpp index 776340f05..944a176e2 100644 --- a/test/stdexec/cpos/test_cpo_upon_stopped.cpp +++ b/test/stdexec/cpos/test_cpo_upon_stopped.cpp @@ -17,36 +17,39 @@ #include "cpo_helpers.cuh" #include -TEST_CASE("upon_stopped is customizable", "[cpo][cpo_upon_stopped]") { - const auto f = []() { - }; +namespace { - SECTION("by free standing sender") { - free_standing_sender_t snd{}; + TEST_CASE("upon_stopped is customizable", "[cpo][cpo_upon_stopped]") { + const auto f = []() { + }; - { - constexpr scope_t scope = decltype(snd | ex::upon_stopped(f))::scope; - STATIC_REQUIRE(scope == scope_t::free_standing); - } + SECTION("by free standing sender") { + free_standing_sender_t snd{}; + + { + constexpr scope_t scope = decltype(snd | ex::upon_stopped(f))::scope; + STATIC_REQUIRE(scope == scope_t::free_standing); + } - { - constexpr scope_t scope = decltype(ex::upon_stopped(snd, f))::scope; - STATIC_REQUIRE(scope == scope_t::free_standing); + { + constexpr scope_t scope = decltype(ex::upon_stopped(snd, f))::scope; + STATIC_REQUIRE(scope == scope_t::free_standing); + } } - } - SECTION("by completion scheduler") { - scheduler_t::sender_t snd{}; + SECTION("by completion scheduler") { + scheduler_t::sender_t snd{}; - { - constexpr scope_t scope = decltype(snd | ex::upon_stopped(f))::scope; - STATIC_REQUIRE(scope == scope_t::scheduler); - } + { + constexpr scope_t scope = decltype(snd | ex::upon_stopped(f))::scope; + STATIC_REQUIRE(scope == scope_t::scheduler); + } - { - ex::get_completion_scheduler(ex::get_env(snd)); - constexpr scope_t scope = decltype(ex::upon_stopped(snd, f))::scope; - STATIC_REQUIRE(scope == scope_t::scheduler); + { + ex::get_completion_scheduler(ex::get_env(snd)); + constexpr scope_t scope = decltype(ex::upon_stopped(snd, f))::scope; + STATIC_REQUIRE(scope == scope_t::scheduler); + } } } } diff --git a/test/stdexec/detail/test_completion_signatures.cpp b/test/stdexec/detail/test_completion_signatures.cpp index 4afa12215..e3097f04b 100644 --- a/test/stdexec/detail/test_completion_signatures.cpp +++ b/test/stdexec/detail/test_completion_signatures.cpp @@ -21,294 +21,300 @@ namespace ex = stdexec; using namespace std; -template -using set_value_sig = ex::set_value_t(Vs...); - -template -using set_error_sig = ex::set_error_t(E); - -TEST_CASE("test the internal __completion_signature concept", "[detail][completion_signatures]") { - static_assert(ex::__completion_signature); - static_assert(ex::__completion_signature); - static_assert(ex::__completion_signature); - static_assert(!ex::__completion_signature); - static_assert(!ex::__completion_signature); - static_assert(!ex::__completion_signature); - - static_assert(!ex::__completion_signature); - static_assert(ex::__completion_signature); - static_assert(!ex::__completion_signature); - static_assert(!ex::__completion_signature); - static_assert(!ex::__completion_signature); - - static_assert(ex::__completion_signature); - static_assert(!ex::__completion_signature); - static_assert(!ex::__completion_signature); - static_assert(!ex::__completion_signature); -} - -TEST_CASE( - "set_value_sig can be used to transform value types to corresponding completion signatures", - "[detail][completion_signatures]") { - using set_value_f = stdexec::__q; - - using tr = stdexec::__transform>; - - using res = stdexec::__minvoke; - using expected = stdexec::__types< // - ex::set_value_t(int), // - ex::set_value_t(double), // - ex::set_value_t(string) // - >; - static_assert(is_same_v); -} - -TEST_CASE( - "set_error_sig can be used to transform error types to corresponding completion signatures", - "[detail][completion_signatures]") { - using set_error_f = stdexec::__q; - - using tr = stdexec::__transform>; - - using res = stdexec::__minvoke; - using expected = stdexec::__types< // - ex::set_error_t(exception_ptr), // - ex::set_error_t(error_code), // - ex::set_error_t(string) // - >; - static_assert(is_same_v); -} - -TEST_CASE( - "set_error_sig can be used to transform exception_ptr", - "[detail][completion_signatures]") { - using set_error_f = stdexec::__q; - - using tr = stdexec::__transform>; - - using res = stdexec::__minvoke; - using expected = stdexec::__types< // - ex::set_error_t(exception_ptr) // - >; - static_assert(is_same_v); -} - -TEST_CASE( - "__error_types_of_t gets the error types from sender", - "[detail][completion_signatures]") { - using snd_eptr_t = decltype(ex::just_error(exception_ptr{})); - using snd_ec_t = decltype(ex::just_error(error_code{})); - using snd_str_t = decltype(ex::just_error(std::string{})); - using snd_tr_just_t = decltype(ex::transfer_just(error_scheduler{})); - - using err_types_eptr = stdexec::__error_types_of_t; - using err_types_ec = stdexec::__error_types_of_t; - using err_types_str = stdexec::__error_types_of_t; - using err_types_tr_just = stdexec::__error_types_of_t; - - static_assert(is_same_v>); - static_assert(is_same_v>); - static_assert(is_same_v>); - static_assert(is_same_v>); -} - -TEST_CASE("__error_types_of_t can also transform error types", "[detail][completion_signatures]") { - using snd_eptr_t = decltype(ex::just_error(exception_ptr{})); - using snd_ec_t = decltype(ex::just_error(error_code{})); - using snd_str_t = decltype(ex::just_error(std::string{})); - - using set_error_f = stdexec::__q; - using tr = stdexec::__transform; - - using sig_eptr = stdexec::__error_types_of_t; - using sig_ec = stdexec::__error_types_of_t; - using sig_str = stdexec::__error_types_of_t; - - static_assert(is_same_v>); - static_assert(is_same_v>); - static_assert(is_same_v>); -} - -template -void expect_val_types() { - using t = stdexec::__gather_signal< - stdexec::set_value_t, - CS, - stdexec::__q, - stdexec::__q>; - static_assert(is_same_v); -} - -template -void expect_err_types() { - using t = stdexec::__gather_signal< - stdexec::set_error_t, - CS, - stdexec::__q, - stdexec::__q>; - static_assert(is_same_v); -} - -TEST_CASE( - "make_completion_signatures can replicate the completion signatures of input senders", - "[detail][completion_signatures]") { - using snd_int_t = decltype(ex::just(0)); - using snd_double_char_t = decltype(ex::just(3.14, 'p')); - using snd_eptr_t = decltype(ex::just_error(exception_ptr{})); - using snd_ec_t = decltype(ex::just_error(error_code{})); - using snd_stopped_t = decltype(ex::just_stopped()); - - using cs_int = ex::make_completion_signatures; - using cs_double_char = ex::make_completion_signatures; - using cs_eptr = ex::make_completion_signatures; - using cs_ec = ex::make_completion_signatures; - using cs_stopped = ex::make_completion_signatures; - - expect_val_types>>(); - expect_val_types>>(); - expect_val_types>(); - expect_val_types>(); - expect_val_types>(); - - expect_err_types>(); - expect_err_types>(); - expect_err_types>(); - expect_err_types>(); - expect_err_types>(); -} - -TEST_CASE( - "make_completion_signatures with __default_env can replicate the completion signatures of input " - "senders", - "[detail][completion_signatures]") { - using snd_int_t = decltype(ex::just(0)); - using snd_double_char_t = decltype(ex::just(3.14, 'p')); - using snd_eptr_t = decltype(ex::just_error(exception_ptr{})); - using snd_ec_t = decltype(ex::just_error(error_code{})); - using snd_stopped_t = decltype(ex::just_stopped()); - - using cs_int = ex::make_completion_signatures; - using cs_double_char = ex::make_completion_signatures; - using cs_eptr = ex::make_completion_signatures; - using cs_ec = ex::make_completion_signatures; - using cs_stopped = ex::make_completion_signatures; - - expect_val_types>>(); - expect_val_types>>(); - expect_val_types>(); - expect_val_types>(); - expect_val_types>(); - - expect_err_types>(); - expect_err_types>(); - expect_err_types>(); - expect_err_types>(); - expect_err_types>(); -} - -TEST_CASE( - "make_completion_signatures can add other error signatures", - "[detail][completion_signatures]") { - using snd_double_t = decltype(ex::just_error(std::exception_ptr{})); - using cs_with_ec = ex::make_completion_signatures< - snd_double_t, - ex::__default_env, - ex::completion_signatures>; - - expect_val_types>(); - expect_err_types>(); -} - -TEST_CASE( - "make_completion_signatures can add other error signatures, but will dedup them", - "[detail][completion_signatures]") { - using snd_double_t = decltype(ex::just(3.14)); - using cs_with_ec = ex::make_completion_signatures< - snd_double_t, - ex::__default_env, - ex::completion_signatures>; - - // exception_ptr appears only once - expect_err_types>(); -} - -TEST_CASE( - "make_completion_signatures can add other value signatures", - "[detail][completion_signatures]") { - using snd_double_t = decltype(ex::just(3.14)); - using cs = ex::make_completion_signatures< - snd_double_t, - ex::__default_env, - ex::completion_signatures< // - ex::set_value_t(int), // - ex::set_value_t(double) // - >>; - - // will add int, double will appear only once - expect_val_types, stdexec::__types>>(); -} - -template -using add_int_set_value_sig = ex::completion_signatures; - -template -using optional_set_error_sig = ex::completion_signatures)>; - -TEST_CASE( - "make_completion_signatures can transform value types signatures", - "[detail][completion_signatures]") { - using snd_double_t = decltype(ex::just(3.14)); - using cs = ex::make_completion_signatures< - snd_double_t, - ex::__default_env, - ex::completion_signatures< // - ex::set_value_t(int), // - ex::set_value_t(double) // - >, // - add_int_set_value_sig // - >; - - // will transform the original "double" into - // then will add the other "int" and "double" - expect_val_types< - cs, - stdexec:: - __types, stdexec::__types, stdexec::__types>>(); -} - -TEST_CASE( - "make_completion_signatures can transform error types signatures", - "[detail][completion_signatures]") { - using snd_double_t = decltype(ex::just_error(std::exception_ptr{})); - using cs = ex::make_completion_signatures< - snd_double_t, - ex::__default_env, - ex::completion_signatures< // - ex::set_error_t(error_code) // - >, // - stdexec::__compl_sigs::__default_set_value, // - optional_set_error_sig>; - - // will transform the original "exception_ptr" into optional - // then will add the other "error_code" as specified in the additional signatures - expect_err_types>>(); -} - -template