From af392b812fedfd0ac71e875939d67d44abcd347c Mon Sep 17 00:00:00 2001 From: Amber Ehrlich Date: Tue, 15 Aug 2023 15:22:11 -0400 Subject: [PATCH] test: add enum for test status and type, make each test a variable, add coro tests --- .../classes/Generator/CoroGenerator.php | 2 +- include/dpp/cluster.h | 2 +- include/dpp/coro/async.h | 4 +- include/dpp/coro/awaitable.h | 6 +- include/dpp/coro/job.h | 24 +- include/dpp/coro/task.h | 4 +- library/CMakeLists.txt | 49 +- src/dpp/cluster/timer.cpp | 4 +- src/dpp/cluster_coro_calls.cpp | 376 +++++------ src/unittest/coro.cpp | 498 ++++++++++++++ src/unittest/test.cpp | 627 +++++++++--------- src/unittest/test.h | 256 ++++++- src/unittest/unittest.cpp | 263 ++------ 13 files changed, 1362 insertions(+), 753 deletions(-) create mode 100644 src/unittest/coro.cpp diff --git a/buildtools/classes/Generator/CoroGenerator.php b/buildtools/classes/Generator/CoroGenerator.php index 4ec6b5d932..9231312d7b 100644 --- a/buildtools/classes/Generator/CoroGenerator.php +++ b/buildtools/classes/Generator/CoroGenerator.php @@ -100,7 +100,7 @@ public function generateCppDef(string $returnType, string $currentFunction, stri { if (substr($parameterNames, 0, 2) === ", ") $parameterNames = substr($parameterNames, 2); - return "awaitable cluster::co_${currentFunction}($noDefaults) {\n\treturn [=, this] (auto &&cc) { this->$currentFunction($parameterNames" . (empty($parameterNames) ? "": ", ") . "cc); };\n}\n\n"; + return "awaitable cluster::co_${currentFunction}($noDefaults) {\n\treturn awaitable{ [=, this] (auto &&cc) { this->$currentFunction($parameterNames" . (empty($parameterNames) ? "": ", ") . "cc); } };\n}\n\n"; } /** diff --git a/include/dpp/cluster.h b/include/dpp/cluster.h index cc4465bd59..352a200f54 100644 --- a/include/dpp/cluster.h +++ b/include/dpp/cluster.h @@ -363,7 +363,7 @@ class DPP_EXPORT cluster { * @param seconds How long to wait for * @return awaitable Object that can be co_await-ed to suspend the function for a certain time */ - awaitable co_sleep(uint64_t seconds); + [[nodiscard]] awaitable co_sleep(uint64_t seconds); #endif /** diff --git a/include/dpp/coro/async.h b/include/dpp/coro/async.h index b2a4080abf..60aa037b07 100644 --- a/include/dpp/coro/async.h +++ b/include/dpp/coro/async.h @@ -216,7 +216,7 @@ class async { #ifndef _DOXYGEN_ requires std::invocable> #endif - async(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} { + explicit async(Obj &&obj, Fun &&fun, Args&&... args) : api_callback{} { std::invoke(std::forward(fun), std::forward(obj), std::forward(args)..., api_callback); } @@ -230,7 +230,7 @@ class async { #ifndef _DOXYGEN_ requires std::invocable> #endif - async(Fun &&fun, Args&&... args) : api_callback{} { + explicit async(Fun &&fun, Args&&... args) : api_callback{} { std::invoke(std::forward(fun), std::forward(args)..., api_callback); } diff --git a/include/dpp/coro/awaitable.h b/include/dpp/coro/awaitable.h index 907fd08a16..f2bcccb529 100644 --- a/include/dpp/coro/awaitable.h +++ b/include/dpp/coro/awaitable.h @@ -60,7 +60,7 @@ struct awaitable { * * @warning This callback is to be executed later, on co_await. Be mindful of reference captures. */ - awaitable(std::invocable> auto &&fun) : request{fun} {} + explicit awaitable(std::invocable> auto &&fun) : request{fun} {} /** * @brief Copy constructor. @@ -112,8 +112,8 @@ struct awaitable { */ template void await_suspend(detail::std_coroutine::coroutine_handle caller) noexcept(noexcept(std::invoke(fun, std::declval&&>()))) { - std::invoke(fun, [this, caller](auto &&api_result) { - result = api_result; + std::invoke(fun, [this, caller] (R_&& api_result) mutable { + result = std::forward(api_result); caller.resume(); }); } diff --git a/include/dpp/coro/job.h b/include/dpp/coro/job.h index 9868b04be4..72308b0c0a 100644 --- a/include/dpp/coro/job.h +++ b/include/dpp/coro/job.h @@ -46,6 +46,12 @@ struct job {}; namespace detail { +template +inline constexpr bool coroutine_has_ref_params_v = (std::is_reference_v || ... || false); + +template +inline constexpr bool coroutine_has_ref_params_v = (std::is_reference_v || ... || (std::is_reference_v && !std::is_invocable_v)); + #ifdef DPP_CORO_TEST struct job_promise_base{}; #endif @@ -53,7 +59,7 @@ namespace detail { /** * @brief Coroutine promise type for a job */ -template +template struct job_promise { #ifdef DPP_CORO_TEST @@ -118,7 +124,7 @@ struct job_promise { * If you must pass a reference, pass it as a pointer or with std::ref, but you must fully understand the reason behind this warning, and what to avoid. * If you prefer a safer type, use `coroutine` for synchronous execution, or `task` for parallel tasks, and co_await them. */ - static_assert(!has_reference_params, "co_await is disabled in dpp::job when taking parameters by reference. read comment above this line for more info"); + static_assert(!coroutine_has_ref_params_v, "co_await is disabled in dpp::job when taking parameters by reference. read comment above this line for more info"); return std::forward(expr); } @@ -128,26 +134,18 @@ struct job_promise { } // namespace dpp -template <> -struct dpp::detail::std_coroutine::coroutine_traits { - /** - * @brief Promise type for this coroutine signature. - */ - using promise_type = dpp::detail::job_promise; -}; - /** * @brief Specialization of std::coroutine_traits, helps the standard library figure out a promise type from a coroutine function. */ -template -struct dpp::detail::std_coroutine::coroutine_traits { +template +struct dpp::detail::std_coroutine::coroutine_traits { /** * @brief Promise type for this coroutine signature. * * When the coroutine is created from a lambda, that lambda is passed as a first parameter. * Not ideal but we'll allow any callable that takes the rest of the arguments passed */ - using promise_type = dpp::detail::job_promise<(std::is_reference_v || ... || (std::is_reference_v && !std::is_invocable_v))>; + using promise_type = dpp::detail::job_promise; }; #endif /* DPP_CORO */ diff --git a/include/dpp/coro/task.h b/include/dpp/coro/task.h index dbf639ab6f..d85dc13d06 100644 --- a/include/dpp/coro/task.h +++ b/include/dpp/coro/task.h @@ -261,7 +261,7 @@ struct task_promise_base { * Emulates the default behavior and sets is_sync to false if the awaited object is not ready. */ template - T await_transform(T&& expr) { + decltype(auto) await_transform(T&& expr) { if constexpr (requires { expr.operator co_await(); }) { auto awaiter = expr.operator co_await(); if (!awaiter.await_ready()) @@ -277,7 +277,7 @@ struct task_promise_base { else { if (!expr.await_ready()) is_sync = false; - return (expr); + return static_cast(expr); } } diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index bb078a462e..4a8624cab9 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -271,27 +271,6 @@ target_compile_features(dpp PRIVATE cxx_thread_local) target_compile_features(dpp PRIVATE cxx_variadic_templates) target_compile_features(dpp PRIVATE cxx_attribute_deprecated) -if (DPP_BUILD_TEST) - enable_testing(${CMAKE_CURRENT_SOURCE_DIR}/..) - file(GLOB testnamelist "${CMAKE_CURRENT_SOURCE_DIR}/../src/*") - foreach (fulltestname ${testnamelist}) - get_filename_component(testname ${fulltestname} NAME) - if (NOT "${testname}" STREQUAL "dpp") - message("-- Configuring test: ${Green}${testname}${ColourReset} with source: ${modules_dir}/${testname}/*.cpp") - set (testsrc "") - file(GLOB testsrc "${modules_dir}/${testname}/*.cpp") - add_executable(${testname} ${testsrc}) - target_compile_features(${testname} PRIVATE cxx_std_17) - target_link_libraries(${testname} PUBLIC ${modname}) - endif() - endforeach() - add_test( - NAME unittests - COMMAND library/unittest - WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/library - ) -endif() - if(HAVE_PRCTL) target_compile_definitions(dpp PRIVATE HAVE_PRCTL) endif() @@ -351,6 +330,34 @@ if(DPP_CORO) execute_process(WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.." COMMAND php buildtools/make_struct.php "\\Dpp\\Generator\\CoroGenerator") endif() + +if (DPP_BUILD_TEST) + enable_testing(${CMAKE_CURRENT_SOURCE_DIR}/..) + file(GLOB testnamelist "${CMAKE_CURRENT_SOURCE_DIR}/../src/*") + foreach (fulltestname ${testnamelist}) + get_filename_component(testname ${fulltestname} NAME) + if (NOT "${testname}" STREQUAL "dpp") + message("-- Configuring test: ${Green}${testname}${ColourReset} with source: ${modules_dir}/${testname}/*.cpp") + set (testsrc "") + file(GLOB testsrc "${modules_dir}/${testname}/*.cpp") + add_executable(${testname} ${testsrc}) + if (DPP_CORO) + target_compile_features(${testname} PRIVATE cxx_std_20) + else() + target_compile_features(${testname} PRIVATE cxx_std_17) + endif() + if (MSVC) + target_compile_options(${testname} PRIVATE /utf-8) + endif() + target_link_libraries(${testname} PUBLIC ${modname}) + endif() + endforeach() + add_test( + NAME unittests + COMMAND library/unittest + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/library + ) +endif() if(WIN32 AND NOT MINGW) if (NOT WINDOWS_32_BIT) diff --git a/src/dpp/cluster/timer.cpp b/src/dpp/cluster/timer.cpp index 3f92d61e52..af767fff5a 100644 --- a/src/dpp/cluster/timer.cpp +++ b/src/dpp/cluster/timer.cpp @@ -106,8 +106,8 @@ void cluster::tick_timers() { #ifdef DPP_CORO awaitable cluster::co_sleep(uint64_t seconds) { - return {[this, seconds] (auto &&cb) { - start_timer([this, cb](dpp::timer handle) { + return awaitable{[this, seconds] (auto &&cb) mutable { + start_timer([this, cb] (dpp::timer handle) { cb(handle); stop_timer(handle); }, seconds); diff --git a/src/dpp/cluster_coro_calls.cpp b/src/dpp/cluster_coro_calls.cpp index ad23ec92c7..93cb2d2465 100644 --- a/src/dpp/cluster_coro_calls.cpp +++ b/src/dpp/cluster_coro_calls.cpp @@ -36,755 +36,755 @@ namespace dpp { awaitable cluster::co_global_bulk_command_create(const std::vector &commands) { - return [=, this] (auto &&cc) { this->global_bulk_command_create(commands, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->global_bulk_command_create(commands, cc); } }; } awaitable cluster::co_global_command_create(const slashcommand &s) { - return [=, this] (auto &&cc) { this->global_command_create(s, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->global_command_create(s, cc); } }; } awaitable cluster::co_global_command_get(snowflake id) { - return [=, this] (auto &&cc) { this->global_command_get(id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->global_command_get(id, cc); } }; } awaitable cluster::co_global_command_delete(snowflake id) { - return [=, this] (auto &&cc) { this->global_command_delete(id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->global_command_delete(id, cc); } }; } awaitable cluster::co_global_command_edit(const slashcommand &s) { - return [=, this] (auto &&cc) { this->global_command_edit(s, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->global_command_edit(s, cc); } }; } awaitable cluster::co_global_commands_get() { - return [=, this] (auto &&cc) { this->global_commands_get(cc); }; + return awaitable{ [=, this] (auto &&cc) { this->global_commands_get(cc); } }; } awaitable cluster::co_guild_bulk_command_create(const std::vector &commands, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_bulk_command_create(commands, guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_bulk_command_create(commands, guild_id, cc); } }; } awaitable cluster::co_guild_commands_get_permissions(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_commands_get_permissions(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_commands_get_permissions(guild_id, cc); } }; } awaitable cluster::co_guild_bulk_command_edit_permissions(const std::vector &commands, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_bulk_command_edit_permissions(commands, guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_bulk_command_edit_permissions(commands, guild_id, cc); } }; } awaitable cluster::co_guild_command_create(const slashcommand &s, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_create(s, guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_command_create(s, guild_id, cc); } }; } awaitable cluster::co_guild_command_delete(snowflake id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_delete(id, guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_command_delete(id, guild_id, cc); } }; } awaitable cluster::co_guild_command_edit_permissions(const slashcommand &s, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_edit_permissions(s, guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_command_edit_permissions(s, guild_id, cc); } }; } awaitable cluster::co_guild_command_get(snowflake id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_get(id, guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_command_get(id, guild_id, cc); } }; } awaitable cluster::co_guild_command_get_permissions(snowflake id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_get_permissions(id, guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_command_get_permissions(id, guild_id, cc); } }; } awaitable cluster::co_guild_command_edit(const slashcommand &s, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_command_edit(s, guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_command_edit(s, guild_id, cc); } }; } awaitable cluster::co_guild_commands_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_commands_get(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_commands_get(guild_id, cc); } }; } awaitable cluster::co_interaction_response_create(snowflake interaction_id, const std::string &token, const interaction_response &r) { - return [=, this] (auto &&cc) { this->interaction_response_create(interaction_id, token, r, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->interaction_response_create(interaction_id, token, r, cc); } }; } awaitable cluster::co_interaction_response_edit(const std::string &token, const message &m) { - return [=, this] (auto &&cc) { this->interaction_response_edit(token, m, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->interaction_response_edit(token, m, cc); } }; } awaitable cluster::co_interaction_response_get_original(const std::string &token) { - return [=, this] (auto &&cc) { this->interaction_response_get_original(token, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->interaction_response_get_original(token, cc); } }; } awaitable cluster::co_interaction_followup_create(const std::string &token, const message &m) { - return [=, this] (auto &&cc) { this->interaction_followup_create(token, m, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->interaction_followup_create(token, m, cc); } }; } awaitable cluster::co_interaction_followup_edit_original(const std::string &token, const message &m) { - return [=, this] (auto &&cc) { this->interaction_followup_edit_original(token, m, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->interaction_followup_edit_original(token, m, cc); } }; } awaitable cluster::co_interaction_followup_delete(const std::string &token) { - return [=, this] (auto &&cc) { this->interaction_followup_delete(token, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->interaction_followup_delete(token, cc); } }; } awaitable cluster::co_interaction_followup_edit(const std::string &token, const message &m) { - return [=, this] (auto &&cc) { this->interaction_followup_edit(token, m, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->interaction_followup_edit(token, m, cc); } }; } awaitable cluster::co_interaction_followup_get(const std::string &token, snowflake message_id) { - return [=, this] (auto &&cc) { this->interaction_followup_get(token, message_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->interaction_followup_get(token, message_id, cc); } }; } awaitable cluster::co_interaction_followup_get_original(const std::string &token) { - return [=, this] (auto &&cc) { this->interaction_followup_get_original(token, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->interaction_followup_get_original(token, cc); } }; } awaitable cluster::co_automod_rules_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->automod_rules_get(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->automod_rules_get(guild_id, cc); } }; } awaitable cluster::co_automod_rule_get(snowflake guild_id, snowflake rule_id) { - return [=, this] (auto &&cc) { this->automod_rule_get(guild_id, rule_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->automod_rule_get(guild_id, rule_id, cc); } }; } awaitable cluster::co_automod_rule_create(snowflake guild_id, const automod_rule& r) { - return [=, this] (auto &&cc) { this->automod_rule_create(guild_id, r, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->automod_rule_create(guild_id, r, cc); } }; } awaitable cluster::co_automod_rule_edit(snowflake guild_id, const automod_rule& r) { - return [=, this] (auto &&cc) { this->automod_rule_edit(guild_id, r, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->automod_rule_edit(guild_id, r, cc); } }; } awaitable cluster::co_automod_rule_delete(snowflake guild_id, snowflake rule_id) { - return [=, this] (auto &&cc) { this->automod_rule_delete(guild_id, rule_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->automod_rule_delete(guild_id, rule_id, cc); } }; } awaitable cluster::co_channel_create(const class channel &c) { - return [=, this] (auto &&cc) { this->channel_create(c, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_create(c, cc); } }; } awaitable cluster::co_channel_delete_permission(const class channel &c, snowflake overwrite_id) { - return [=, this] (auto &&cc) { this->channel_delete_permission(c, overwrite_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_delete_permission(c, overwrite_id, cc); } }; } awaitable cluster::co_channel_delete(snowflake channel_id) { - return [=, this] (auto &&cc) { this->channel_delete(channel_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_delete(channel_id, cc); } }; } awaitable cluster::co_channel_edit_permissions(const class channel &c, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { - return [=, this] (auto &&cc) { this->channel_edit_permissions(c, overwrite_id, allow, deny, member, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_edit_permissions(c, overwrite_id, allow, deny, member, cc); } }; } awaitable cluster::co_channel_edit_permissions(const snowflake channel_id, const snowflake overwrite_id, const uint64_t allow, const uint64_t deny, const bool member) { - return [=, this] (auto &&cc) { this->channel_edit_permissions(channel_id, overwrite_id, allow, deny, member, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_edit_permissions(channel_id, overwrite_id, allow, deny, member, cc); } }; } awaitable cluster::co_channel_edit_positions(const std::vector &c) { - return [=, this] (auto &&cc) { this->channel_edit_positions(c, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_edit_positions(c, cc); } }; } awaitable cluster::co_channel_edit(const class channel &c) { - return [=, this] (auto &&cc) { this->channel_edit(c, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_edit(c, cc); } }; } awaitable cluster::co_channel_follow_news(const class channel &c, snowflake target_channel_id) { - return [=, this] (auto &&cc) { this->channel_follow_news(c, target_channel_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_follow_news(c, target_channel_id, cc); } }; } awaitable cluster::co_channel_get(snowflake c) { - return [=, this] (auto &&cc) { this->channel_get(c, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_get(c, cc); } }; } awaitable cluster::co_channel_invite_create(const class channel &c, const class invite &i) { - return [=, this] (auto &&cc) { this->channel_invite_create(c, i, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_invite_create(c, i, cc); } }; } awaitable cluster::co_channel_invites_get(const class channel &c) { - return [=, this] (auto &&cc) { this->channel_invites_get(c, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_invites_get(c, cc); } }; } awaitable cluster::co_channel_typing(const class channel &c) { - return [=, this] (auto &&cc) { this->channel_typing(c, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_typing(c, cc); } }; } awaitable cluster::co_channel_typing(snowflake cid) { - return [=, this] (auto &&cc) { this->channel_typing(cid, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_typing(cid, cc); } }; } awaitable cluster::co_channels_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->channels_get(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channels_get(guild_id, cc); } }; } awaitable cluster::co_create_dm_channel(snowflake user_id) { - return [=, this] (auto &&cc) { this->create_dm_channel(user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->create_dm_channel(user_id, cc); } }; } awaitable cluster::co_current_user_get_dms() { - return [=, this] (auto &&cc) { this->current_user_get_dms(cc); }; + return awaitable{ [=, this] (auto &&cc) { this->current_user_get_dms(cc); } }; } awaitable cluster::co_direct_message_create(snowflake user_id, const message &m) { - return [=, this] (auto &&cc) { this->direct_message_create(user_id, m, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->direct_message_create(user_id, m, cc); } }; } awaitable cluster::co_gdm_add(snowflake channel_id, snowflake user_id, const std::string &access_token, const std::string &nick) { - return [=, this] (auto &&cc) { this->gdm_add(channel_id, user_id, access_token, nick, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->gdm_add(channel_id, user_id, access_token, nick, cc); } }; } awaitable cluster::co_gdm_remove(snowflake channel_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->gdm_remove(channel_id, user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->gdm_remove(channel_id, user_id, cc); } }; } awaitable cluster::co_guild_emoji_create(snowflake guild_id, const class emoji& newemoji) { - return [=, this] (auto &&cc) { this->guild_emoji_create(guild_id, newemoji, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_emoji_create(guild_id, newemoji, cc); } }; } awaitable cluster::co_guild_emoji_delete(snowflake guild_id, snowflake emoji_id) { - return [=, this] (auto &&cc) { this->guild_emoji_delete(guild_id, emoji_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_emoji_delete(guild_id, emoji_id, cc); } }; } awaitable cluster::co_guild_emoji_edit(snowflake guild_id, const class emoji& newemoji) { - return [=, this] (auto &&cc) { this->guild_emoji_edit(guild_id, newemoji, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_emoji_edit(guild_id, newemoji, cc); } }; } awaitable cluster::co_guild_emoji_get(snowflake guild_id, snowflake emoji_id) { - return [=, this] (auto &&cc) { this->guild_emoji_get(guild_id, emoji_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_emoji_get(guild_id, emoji_id, cc); } }; } awaitable cluster::co_guild_emojis_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_emojis_get(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_emojis_get(guild_id, cc); } }; } awaitable cluster::co_get_gateway_bot() { - return [=, this] (auto &&cc) { this->get_gateway_bot(cc); }; + return awaitable{ [=, this] (auto &&cc) { this->get_gateway_bot(cc); } }; } awaitable cluster::co_guild_current_member_edit(snowflake guild_id, const std::string &nickname) { - return [=, this] (auto &&cc) { this->guild_current_member_edit(guild_id, nickname, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_current_member_edit(guild_id, nickname, cc); } }; } awaitable cluster::co_guild_auditlog_get(snowflake guild_id, snowflake user_id, uint32_t action_type, snowflake before, snowflake after, uint32_t limit) { - return [=, this] (auto &&cc) { this->guild_auditlog_get(guild_id, user_id, action_type, before, after, limit, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_auditlog_get(guild_id, user_id, action_type, before, after, limit, cc); } }; } awaitable cluster::co_guild_ban_add(snowflake guild_id, snowflake user_id, uint32_t delete_message_seconds) { - return [=, this] (auto &&cc) { this->guild_ban_add(guild_id, user_id, delete_message_seconds, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_ban_add(guild_id, user_id, delete_message_seconds, cc); } }; } awaitable cluster::co_guild_ban_delete(snowflake guild_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_ban_delete(guild_id, user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_ban_delete(guild_id, user_id, cc); } }; } awaitable cluster::co_guild_create(const class guild &g) { - return [=, this] (auto &&cc) { this->guild_create(g, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_create(g, cc); } }; } awaitable cluster::co_guild_delete(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_delete(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_delete(guild_id, cc); } }; } awaitable cluster::co_guild_delete_integration(snowflake guild_id, snowflake integration_id) { - return [=, this] (auto &&cc) { this->guild_delete_integration(guild_id, integration_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_delete_integration(guild_id, integration_id, cc); } }; } awaitable cluster::co_guild_edit(const class guild &g) { - return [=, this] (auto &&cc) { this->guild_edit(g, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_edit(g, cc); } }; } awaitable cluster::co_guild_edit_widget(snowflake guild_id, const class guild_widget &gw) { - return [=, this] (auto &&cc) { this->guild_edit_widget(guild_id, gw, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_edit_widget(guild_id, gw, cc); } }; } awaitable cluster::co_guild_get_ban(snowflake guild_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_get_ban(guild_id, user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_ban(guild_id, user_id, cc); } }; } awaitable cluster::co_guild_get_bans(snowflake guild_id, snowflake before, snowflake after, snowflake limit) { - return [=, this] (auto &&cc) { this->guild_get_bans(guild_id, before, after, limit, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_bans(guild_id, before, after, limit, cc); } }; } awaitable cluster::co_guild_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get(guild_id, cc); } }; } awaitable cluster::co_guild_get_integrations(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_integrations(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_integrations(guild_id, cc); } }; } awaitable cluster::co_guild_get_preview(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_preview(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_preview(guild_id, cc); } }; } awaitable cluster::co_guild_get_vanity(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_vanity(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_vanity(guild_id, cc); } }; } awaitable cluster::co_guild_get_widget(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_widget(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_widget(guild_id, cc); } }; } awaitable cluster::co_guild_modify_integration(snowflake guild_id, const class integration &i) { - return [=, this] (auto &&cc) { this->guild_modify_integration(guild_id, i, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_modify_integration(guild_id, i, cc); } }; } awaitable cluster::co_guild_get_prune_counts(snowflake guild_id, const struct prune& pruneinfo) { - return [=, this] (auto &&cc) { this->guild_get_prune_counts(guild_id, pruneinfo, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_prune_counts(guild_id, pruneinfo, cc); } }; } awaitable cluster::co_guild_begin_prune(snowflake guild_id, const struct prune& pruneinfo) { - return [=, this] (auto &&cc) { this->guild_begin_prune(guild_id, pruneinfo, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_begin_prune(guild_id, pruneinfo, cc); } }; } awaitable cluster::co_guild_set_nickname(snowflake guild_id, const std::string &nickname) { - return [=, this] (auto &&cc) { this->guild_set_nickname(guild_id, nickname, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_set_nickname(guild_id, nickname, cc); } }; } awaitable cluster::co_guild_sync_integration(snowflake guild_id, snowflake integration_id) { - return [=, this] (auto &&cc) { this->guild_sync_integration(guild_id, integration_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_sync_integration(guild_id, integration_id, cc); } }; } awaitable cluster::co_guild_get_onboarding(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_onboarding(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_onboarding(guild_id, cc); } }; } awaitable cluster::co_guild_edit_onboarding(const struct onboarding& o) { - return [=, this] (auto &&cc) { this->guild_edit_onboarding(o, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_edit_onboarding(o, cc); } }; } awaitable cluster::co_guild_get_welcome_screen(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_welcome_screen(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_welcome_screen(guild_id, cc); } }; } awaitable cluster::co_guild_edit_welcome_screen(snowflake guild_id, const struct welcome_screen& welcome_screen, bool enabled) { - return [=, this] (auto &&cc) { this->guild_edit_welcome_screen(guild_id, welcome_screen, enabled, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_edit_welcome_screen(guild_id, welcome_screen, enabled, cc); } }; } awaitable cluster::co_guild_add_member(const guild_member& gm, const std::string &access_token) { - return [=, this] (auto &&cc) { this->guild_add_member(gm, access_token, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_add_member(gm, access_token, cc); } }; } awaitable cluster::co_guild_edit_member(const guild_member& gm) { - return [=, this] (auto &&cc) { this->guild_edit_member(gm, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_edit_member(gm, cc); } }; } awaitable cluster::co_guild_get_member(snowflake guild_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_get_member(guild_id, user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_member(guild_id, user_id, cc); } }; } awaitable cluster::co_guild_get_members(snowflake guild_id, uint16_t limit, snowflake after) { - return [=, this] (auto &&cc) { this->guild_get_members(guild_id, limit, after, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_members(guild_id, limit, after, cc); } }; } awaitable cluster::co_guild_member_add_role(snowflake guild_id, snowflake user_id, snowflake role_id) { - return [=, this] (auto &&cc) { this->guild_member_add_role(guild_id, user_id, role_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_member_add_role(guild_id, user_id, role_id, cc); } }; } awaitable cluster::co_guild_member_delete(snowflake guild_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_member_delete(guild_id, user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_member_delete(guild_id, user_id, cc); } }; } awaitable cluster::co_guild_member_kick(snowflake guild_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_member_kick(guild_id, user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_member_kick(guild_id, user_id, cc); } }; } awaitable cluster::co_guild_member_timeout(snowflake guild_id, snowflake user_id, time_t communication_disabled_until) { - return [=, this] (auto &&cc) { this->guild_member_timeout(guild_id, user_id, communication_disabled_until, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_member_timeout(guild_id, user_id, communication_disabled_until, cc); } }; } awaitable cluster::co_guild_member_delete_role(snowflake guild_id, snowflake user_id, snowflake role_id) { - return [=, this] (auto &&cc) { this->guild_member_delete_role(guild_id, user_id, role_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_member_delete_role(guild_id, user_id, role_id, cc); } }; } awaitable cluster::co_guild_member_remove_role(snowflake guild_id, snowflake user_id, snowflake role_id) { - return [=, this] (auto &&cc) { this->guild_member_remove_role(guild_id, user_id, role_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_member_remove_role(guild_id, user_id, role_id, cc); } }; } awaitable cluster::co_guild_member_move(const snowflake channel_id, const snowflake guild_id, const snowflake user_id) { - return [=, this] (auto &&cc) { this->guild_member_move(channel_id, guild_id, user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_member_move(channel_id, guild_id, user_id, cc); } }; } awaitable cluster::co_guild_search_members(snowflake guild_id, const std::string& query, uint16_t limit) { - return [=, this] (auto &&cc) { this->guild_search_members(guild_id, query, limit, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_search_members(guild_id, query, limit, cc); } }; } awaitable cluster::co_guild_get_invites(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_invites(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_invites(guild_id, cc); } }; } awaitable cluster::co_invite_delete(const std::string &invitecode) { - return [=, this] (auto &&cc) { this->invite_delete(invitecode, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->invite_delete(invitecode, cc); } }; } awaitable cluster::co_invite_get(const std::string &invite_code) { - return [=, this] (auto &&cc) { this->invite_get(invite_code, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->invite_get(invite_code, cc); } }; } awaitable cluster::co_message_add_reaction(const struct message &m, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_add_reaction(m, reaction, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_add_reaction(m, reaction, cc); } }; } awaitable cluster::co_message_add_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_add_reaction(message_id, channel_id, reaction, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_add_reaction(message_id, channel_id, reaction, cc); } }; } awaitable cluster::co_message_create(const message &m) { - return [=, this] (auto &&cc) { this->message_create(m, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_create(m, cc); } }; } awaitable cluster::co_message_crosspost(snowflake message_id, snowflake channel_id) { - return [=, this] (auto &&cc) { this->message_crosspost(message_id, channel_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_crosspost(message_id, channel_id, cc); } }; } awaitable cluster::co_message_delete_all_reactions(const struct message &m) { - return [=, this] (auto &&cc) { this->message_delete_all_reactions(m, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_delete_all_reactions(m, cc); } }; } awaitable cluster::co_message_delete_all_reactions(snowflake message_id, snowflake channel_id) { - return [=, this] (auto &&cc) { this->message_delete_all_reactions(message_id, channel_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_delete_all_reactions(message_id, channel_id, cc); } }; } awaitable cluster::co_message_delete_bulk(const std::vector& message_ids, snowflake channel_id) { - return [=, this] (auto &&cc) { this->message_delete_bulk(message_ids, channel_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_delete_bulk(message_ids, channel_id, cc); } }; } awaitable cluster::co_message_delete(snowflake message_id, snowflake channel_id) { - return [=, this] (auto &&cc) { this->message_delete(message_id, channel_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_delete(message_id, channel_id, cc); } }; } awaitable cluster::co_message_delete_own_reaction(const struct message &m, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_own_reaction(m, reaction, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_delete_own_reaction(m, reaction, cc); } }; } awaitable cluster::co_message_delete_own_reaction(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_own_reaction(message_id, channel_id, reaction, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_delete_own_reaction(message_id, channel_id, reaction, cc); } }; } awaitable cluster::co_message_delete_reaction(const struct message &m, snowflake user_id, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_reaction(m, user_id, reaction, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_delete_reaction(m, user_id, reaction, cc); } }; } awaitable cluster::co_message_delete_reaction(snowflake message_id, snowflake channel_id, snowflake user_id, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_reaction(message_id, channel_id, user_id, reaction, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_delete_reaction(message_id, channel_id, user_id, reaction, cc); } }; } awaitable cluster::co_message_delete_reaction_emoji(const struct message &m, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_reaction_emoji(m, reaction, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_delete_reaction_emoji(m, reaction, cc); } }; } awaitable cluster::co_message_delete_reaction_emoji(snowflake message_id, snowflake channel_id, const std::string &reaction) { - return [=, this] (auto &&cc) { this->message_delete_reaction_emoji(message_id, channel_id, reaction, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_delete_reaction_emoji(message_id, channel_id, reaction, cc); } }; } awaitable cluster::co_message_edit(const message &m) { - return [=, this] (auto &&cc) { this->message_edit(m, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_edit(m, cc); } }; } awaitable cluster::co_message_get(snowflake message_id, snowflake channel_id) { - return [=, this] (auto &&cc) { this->message_get(message_id, channel_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_get(message_id, channel_id, cc); } }; } awaitable cluster::co_message_get_reactions(const struct message &m, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { - return [=, this] (auto &&cc) { this->message_get_reactions(m, reaction, before, after, limit, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_get_reactions(m, reaction, before, after, limit, cc); } }; } awaitable cluster::co_message_get_reactions(snowflake message_id, snowflake channel_id, const std::string &reaction, snowflake before, snowflake after, snowflake limit) { - return [=, this] (auto &&cc) { this->message_get_reactions(message_id, channel_id, reaction, before, after, limit, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_get_reactions(message_id, channel_id, reaction, before, after, limit, cc); } }; } awaitable cluster::co_message_pin(snowflake channel_id, snowflake message_id) { - return [=, this] (auto &&cc) { this->message_pin(channel_id, message_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_pin(channel_id, message_id, cc); } }; } awaitable cluster::co_messages_get(snowflake channel_id, snowflake around, snowflake before, snowflake after, uint64_t limit) { - return [=, this] (auto &&cc) { this->messages_get(channel_id, around, before, after, limit, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->messages_get(channel_id, around, before, after, limit, cc); } }; } awaitable cluster::co_message_unpin(snowflake channel_id, snowflake message_id) { - return [=, this] (auto &&cc) { this->message_unpin(channel_id, message_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->message_unpin(channel_id, message_id, cc); } }; } awaitable cluster::co_channel_pins_get(snowflake channel_id) { - return [=, this] (auto &&cc) { this->channel_pins_get(channel_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->channel_pins_get(channel_id, cc); } }; } awaitable cluster::co_role_create(const class role &r) { - return [=, this] (auto &&cc) { this->role_create(r, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->role_create(r, cc); } }; } awaitable cluster::co_role_delete(snowflake guild_id, snowflake role_id) { - return [=, this] (auto &&cc) { this->role_delete(guild_id, role_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->role_delete(guild_id, role_id, cc); } }; } awaitable cluster::co_role_edit(const class role &r) { - return [=, this] (auto &&cc) { this->role_edit(r, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->role_edit(r, cc); } }; } awaitable cluster::co_roles_edit_position(snowflake guild_id, const std::vector &roles) { - return [=, this] (auto &&cc) { this->roles_edit_position(guild_id, roles, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->roles_edit_position(guild_id, roles, cc); } }; } awaitable cluster::co_roles_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->roles_get(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->roles_get(guild_id, cc); } }; } awaitable cluster::co_application_role_connection_get(snowflake application_id) { - return [=, this] (auto &&cc) { this->application_role_connection_get(application_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->application_role_connection_get(application_id, cc); } }; } awaitable cluster::co_application_role_connection_update(snowflake application_id, const std::vector &connection_metadata) { - return [=, this] (auto &&cc) { this->application_role_connection_update(application_id, connection_metadata, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->application_role_connection_update(application_id, connection_metadata, cc); } }; } awaitable cluster::co_user_application_role_connection_get(snowflake application_id) { - return [=, this] (auto &&cc) { this->user_application_role_connection_get(application_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->user_application_role_connection_get(application_id, cc); } }; } awaitable cluster::co_user_application_role_connection_update(snowflake application_id, const application_role_connection &connection) { - return [=, this] (auto &&cc) { this->user_application_role_connection_update(application_id, connection, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->user_application_role_connection_update(application_id, connection, cc); } }; } awaitable cluster::co_guild_events_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_events_get(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_events_get(guild_id, cc); } }; } awaitable cluster::co_guild_event_create(const scheduled_event& event) { - return [=, this] (auto &&cc) { this->guild_event_create(event, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_event_create(event, cc); } }; } awaitable cluster::co_guild_event_delete(snowflake event_id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_event_delete(event_id, guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_event_delete(event_id, guild_id, cc); } }; } awaitable cluster::co_guild_event_edit(const scheduled_event& event) { - return [=, this] (auto &&cc) { this->guild_event_edit(event, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_event_edit(event, cc); } }; } awaitable cluster::co_guild_event_get(snowflake guild_id, snowflake event_id) { - return [=, this] (auto &&cc) { this->guild_event_get(guild_id, event_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_event_get(guild_id, event_id, cc); } }; } awaitable cluster::co_stage_instance_create(const stage_instance& si) { - return [=, this] (auto &&cc) { this->stage_instance_create(si, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->stage_instance_create(si, cc); } }; } awaitable cluster::co_stage_instance_get(const snowflake channel_id) { - return [=, this] (auto &&cc) { this->stage_instance_get(channel_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->stage_instance_get(channel_id, cc); } }; } awaitable cluster::co_stage_instance_edit(const stage_instance& si) { - return [=, this] (auto &&cc) { this->stage_instance_edit(si, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->stage_instance_edit(si, cc); } }; } awaitable cluster::co_stage_instance_delete(const snowflake channel_id) { - return [=, this] (auto &&cc) { this->stage_instance_delete(channel_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->stage_instance_delete(channel_id, cc); } }; } awaitable cluster::co_guild_sticker_create(const sticker &s) { - return [=, this] (auto &&cc) { this->guild_sticker_create(s, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_sticker_create(s, cc); } }; } awaitable cluster::co_guild_sticker_delete(snowflake sticker_id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_sticker_delete(sticker_id, guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_sticker_delete(sticker_id, guild_id, cc); } }; } awaitable cluster::co_guild_sticker_get(snowflake id, snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_sticker_get(id, guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_sticker_get(id, guild_id, cc); } }; } awaitable cluster::co_guild_sticker_modify(const sticker &s) { - return [=, this] (auto &&cc) { this->guild_sticker_modify(s, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_sticker_modify(s, cc); } }; } awaitable cluster::co_guild_stickers_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_stickers_get(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_stickers_get(guild_id, cc); } }; } awaitable cluster::co_nitro_sticker_get(snowflake id) { - return [=, this] (auto &&cc) { this->nitro_sticker_get(id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->nitro_sticker_get(id, cc); } }; } awaitable cluster::co_sticker_packs_get() { - return [=, this] (auto &&cc) { this->sticker_packs_get(cc); }; + return awaitable{ [=, this] (auto &&cc) { this->sticker_packs_get(cc); } }; } awaitable cluster::co_guild_create_from_template(const std::string &code, const std::string &name) { - return [=, this] (auto &&cc) { this->guild_create_from_template(code, name, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_create_from_template(code, name, cc); } }; } awaitable cluster::co_guild_template_create(snowflake guild_id, const std::string &name, const std::string &description) { - return [=, this] (auto &&cc) { this->guild_template_create(guild_id, name, description, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_template_create(guild_id, name, description, cc); } }; } awaitable cluster::co_guild_template_delete(snowflake guild_id, const std::string &code) { - return [=, this] (auto &&cc) { this->guild_template_delete(guild_id, code, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_template_delete(guild_id, code, cc); } }; } awaitable cluster::co_guild_template_modify(snowflake guild_id, const std::string &code, const std::string &name, const std::string &description) { - return [=, this] (auto &&cc) { this->guild_template_modify(guild_id, code, name, description, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_template_modify(guild_id, code, name, description, cc); } }; } awaitable cluster::co_guild_templates_get(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_templates_get(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_templates_get(guild_id, cc); } }; } awaitable cluster::co_guild_template_sync(snowflake guild_id, const std::string &code) { - return [=, this] (auto &&cc) { this->guild_template_sync(guild_id, code, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_template_sync(guild_id, code, cc); } }; } awaitable cluster::co_template_get(const std::string &code) { - return [=, this] (auto &&cc) { this->template_get(code, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->template_get(code, cc); } }; } awaitable cluster::co_current_user_join_thread(snowflake thread_id) { - return [=, this] (auto &&cc) { this->current_user_join_thread(thread_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->current_user_join_thread(thread_id, cc); } }; } awaitable cluster::co_current_user_leave_thread(snowflake thread_id) { - return [=, this] (auto &&cc) { this->current_user_leave_thread(thread_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->current_user_leave_thread(thread_id, cc); } }; } awaitable cluster::co_threads_get_active(snowflake guild_id) { - return [=, this] (auto &&cc) { this->threads_get_active(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->threads_get_active(guild_id, cc); } }; } awaitable cluster::co_threads_get_joined_private_archived(snowflake channel_id, snowflake before_id, uint16_t limit) { - return [=, this] (auto &&cc) { this->threads_get_joined_private_archived(channel_id, before_id, limit, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->threads_get_joined_private_archived(channel_id, before_id, limit, cc); } }; } awaitable cluster::co_threads_get_private_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit) { - return [=, this] (auto &&cc) { this->threads_get_private_archived(channel_id, before_timestamp, limit, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->threads_get_private_archived(channel_id, before_timestamp, limit, cc); } }; } awaitable cluster::co_threads_get_public_archived(snowflake channel_id, time_t before_timestamp, uint16_t limit) { - return [=, this] (auto &&cc) { this->threads_get_public_archived(channel_id, before_timestamp, limit, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->threads_get_public_archived(channel_id, before_timestamp, limit, cc); } }; } awaitable cluster::co_thread_member_get(const snowflake thread_id, const snowflake user_id) { - return [=, this] (auto &&cc) { this->thread_member_get(thread_id, user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->thread_member_get(thread_id, user_id, cc); } }; } awaitable cluster::co_thread_members_get(snowflake thread_id) { - return [=, this] (auto &&cc) { this->thread_members_get(thread_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->thread_members_get(thread_id, cc); } }; } awaitable cluster::co_thread_create_in_forum(const std::string& thread_name, snowflake channel_id, const message& msg, auto_archive_duration_t auto_archive_duration, uint16_t rate_limit_per_user, std::vector applied_tags) { - return [=, this] (auto &&cc) { this->thread_create_in_forum(thread_name, channel_id, msg, auto_archive_duration, rate_limit_per_user, applied_tags, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->thread_create_in_forum(thread_name, channel_id, msg, auto_archive_duration, rate_limit_per_user, applied_tags, cc); } }; } awaitable cluster::co_thread_create(const std::string& thread_name, snowflake channel_id, uint16_t auto_archive_duration, channel_type thread_type, bool invitable, uint16_t rate_limit_per_user) { - return [=, this] (auto &&cc) { this->thread_create(thread_name, channel_id, auto_archive_duration, thread_type, invitable, rate_limit_per_user, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->thread_create(thread_name, channel_id, auto_archive_duration, thread_type, invitable, rate_limit_per_user, cc); } }; } awaitable cluster::co_thread_edit(const thread &t) { - return [=, this] (auto &&cc) { this->thread_edit(t, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->thread_edit(t, cc); } }; } awaitable cluster::co_thread_create_with_message(const std::string& thread_name, snowflake channel_id, snowflake message_id, uint16_t auto_archive_duration, uint16_t rate_limit_per_user) { - return [=, this] (auto &&cc) { this->thread_create_with_message(thread_name, channel_id, message_id, auto_archive_duration, rate_limit_per_user, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->thread_create_with_message(thread_name, channel_id, message_id, auto_archive_duration, rate_limit_per_user, cc); } }; } awaitable cluster::co_thread_member_add(snowflake thread_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->thread_member_add(thread_id, user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->thread_member_add(thread_id, user_id, cc); } }; } awaitable cluster::co_thread_member_remove(snowflake thread_id, snowflake user_id) { - return [=, this] (auto &&cc) { this->thread_member_remove(thread_id, user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->thread_member_remove(thread_id, user_id, cc); } }; } awaitable cluster::co_current_user_edit(const std::string &nickname, const std::string& image_blob, const image_type type) { - return [=, this] (auto &&cc) { this->current_user_edit(nickname, image_blob, type, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->current_user_edit(nickname, image_blob, type, cc); } }; } awaitable cluster::co_current_application_get() { - return [=, this] (auto &&cc) { this->current_application_get(cc); }; + return awaitable{ [=, this] (auto &&cc) { this->current_application_get(cc); } }; } awaitable cluster::co_current_user_get() { - return [=, this] (auto &&cc) { this->current_user_get(cc); }; + return awaitable{ [=, this] (auto &&cc) { this->current_user_get(cc); } }; } awaitable cluster::co_current_user_set_voice_state(snowflake guild_id, snowflake channel_id, bool suppress, time_t request_to_speak_timestamp) { - return [=, this] (auto &&cc) { this->current_user_set_voice_state(guild_id, channel_id, suppress, request_to_speak_timestamp, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->current_user_set_voice_state(guild_id, channel_id, suppress, request_to_speak_timestamp, cc); } }; } awaitable cluster::co_user_set_voice_state(snowflake user_id, snowflake guild_id, snowflake channel_id, bool suppress) { - return [=, this] (auto &&cc) { this->user_set_voice_state(user_id, guild_id, channel_id, suppress, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->user_set_voice_state(user_id, guild_id, channel_id, suppress, cc); } }; } awaitable cluster::co_current_user_connections_get() { - return [=, this] (auto &&cc) { this->current_user_connections_get(cc); }; + return awaitable{ [=, this] (auto &&cc) { this->current_user_connections_get(cc); } }; } awaitable cluster::co_current_user_get_guilds() { - return [=, this] (auto &&cc) { this->current_user_get_guilds(cc); }; + return awaitable{ [=, this] (auto &&cc) { this->current_user_get_guilds(cc); } }; } awaitable cluster::co_current_user_leave_guild(snowflake guild_id) { - return [=, this] (auto &&cc) { this->current_user_leave_guild(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->current_user_leave_guild(guild_id, cc); } }; } awaitable cluster::co_user_get(snowflake user_id) { - return [=, this] (auto &&cc) { this->user_get(user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->user_get(user_id, cc); } }; } awaitable cluster::co_user_get_cached(snowflake user_id) { - return [=, this] (auto &&cc) { this->user_get_cached(user_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->user_get_cached(user_id, cc); } }; } awaitable cluster::co_get_voice_regions() { - return [=, this] (auto &&cc) { this->get_voice_regions(cc); }; + return awaitable{ [=, this] (auto &&cc) { this->get_voice_regions(cc); } }; } awaitable cluster::co_guild_get_voice_regions(snowflake guild_id) { - return [=, this] (auto &&cc) { this->guild_get_voice_regions(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->guild_get_voice_regions(guild_id, cc); } }; } awaitable cluster::co_create_webhook(const class webhook &w) { - return [=, this] (auto &&cc) { this->create_webhook(w, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->create_webhook(w, cc); } }; } awaitable cluster::co_delete_webhook(snowflake webhook_id) { - return [=, this] (auto &&cc) { this->delete_webhook(webhook_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->delete_webhook(webhook_id, cc); } }; } awaitable cluster::co_delete_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id) { - return [=, this] (auto &&cc) { this->delete_webhook_message(wh, message_id, thread_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->delete_webhook_message(wh, message_id, thread_id, cc); } }; } awaitable cluster::co_delete_webhook_with_token(snowflake webhook_id, const std::string &token) { - return [=, this] (auto &&cc) { this->delete_webhook_with_token(webhook_id, token, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->delete_webhook_with_token(webhook_id, token, cc); } }; } awaitable cluster::co_edit_webhook(const class webhook& wh) { - return [=, this] (auto &&cc) { this->edit_webhook(wh, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->edit_webhook(wh, cc); } }; } awaitable cluster::co_edit_webhook_message(const class webhook &wh, const struct message& m, snowflake thread_id) { - return [=, this] (auto &&cc) { this->edit_webhook_message(wh, m, thread_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->edit_webhook_message(wh, m, thread_id, cc); } }; } awaitable cluster::co_edit_webhook_with_token(const class webhook& wh) { - return [=, this] (auto &&cc) { this->edit_webhook_with_token(wh, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->edit_webhook_with_token(wh, cc); } }; } awaitable cluster::co_execute_webhook(const class webhook &wh, const struct message& m, bool wait, snowflake thread_id, const std::string& thread_name) { - return [=, this] (auto &&cc) { this->execute_webhook(wh, m, wait, thread_id, thread_name, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->execute_webhook(wh, m, wait, thread_id, thread_name, cc); } }; } awaitable cluster::co_get_channel_webhooks(snowflake channel_id) { - return [=, this] (auto &&cc) { this->get_channel_webhooks(channel_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->get_channel_webhooks(channel_id, cc); } }; } awaitable cluster::co_get_guild_webhooks(snowflake guild_id) { - return [=, this] (auto &&cc) { this->get_guild_webhooks(guild_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->get_guild_webhooks(guild_id, cc); } }; } awaitable cluster::co_get_webhook(snowflake webhook_id) { - return [=, this] (auto &&cc) { this->get_webhook(webhook_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->get_webhook(webhook_id, cc); } }; } awaitable cluster::co_get_webhook_message(const class webhook &wh, snowflake message_id, snowflake thread_id) { - return [=, this] (auto &&cc) { this->get_webhook_message(wh, message_id, thread_id, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->get_webhook_message(wh, message_id, thread_id, cc); } }; } awaitable cluster::co_get_webhook_with_token(snowflake webhook_id, const std::string &token) { - return [=, this] (auto &&cc) { this->get_webhook_with_token(webhook_id, token, cc); }; + return awaitable{ [=, this] (auto &&cc) { this->get_webhook_with_token(webhook_id, token, cc); } }; } diff --git a/src/unittest/coro.cpp b/src/unittest/coro.cpp new file mode 100644 index 0000000000..fd9d1e1fbb --- /dev/null +++ b/src/unittest/coro.cpp @@ -0,0 +1,498 @@ +#include +#include +#include + +#include "test.h" + +#ifdef DPP_CORO + +namespace { + +namespace std_coroutine = dpp::detail::std_coroutine; + +template +struct test_exception : std::exception { +}; + +std::atomic exceptions_caught = 0; + +std::array, 10> job_data; +std::array, 10> task_data; + +void throw_job_exception () { + throw test_exception<42>{}; +} + + +struct simple_awaitable { + test_t &test_for; + volatile int value{}; + int result{}; + + bool await_ready() const noexcept { + return false; + } + + template + void await_suspend(std_coroutine::coroutine_handle handle) { + std::thread th([this, handle]() mutable { + auto *test = &test_for; + std::this_thread::sleep_for(std::chrono::seconds(5)); + result = value; + try { + handle.resume(); + } catch (const std::exception &) { + /* no exception should be caught */ + set_status(*test, ts_failed, "unexpected exception during resume"); + } + }); + th.detach(); + } + + int await_resume() const noexcept { + return result; + } +}; + +struct job_awaitable { + volatile int value{}; + int result{}; + + bool await_ready() const noexcept { + return false; + } + + template + void await_suspend(std_coroutine::coroutine_handle handle) { + std::thread th([this, handle]() mutable { + std::this_thread::sleep_for(std::chrono::seconds(5)); + result = value; + try { + handle.resume(); + } catch (const test_exception<42> &) { + /* intended exception to catch. we should have 2, one for the job_data test, one for the top-level job test */ + int exceptions = ++exceptions_caught; + if (exceptions == 2) + set_status(CORO_JOB_OFFLINE, ts_success); + else if (exceptions > 2) + set_status(CORO_JOB_OFFLINE, ts_failed, "resume() threw more than expected"); + } catch (const std::exception &) { + /* anything else should not be caught */ + set_status(CORO_JOB_OFFLINE, ts_failed, "resume() threw an exception it shouldn't have"); + } + }); + th.detach(); + } + + int await_resume() const noexcept { + return result; + } +}; + +dpp::job job_offline_test() { + if (int ret = co_await job_awaitable{42}; ret != 42) + set_status(CORO_JOB_OFFLINE, ts_failed, "failed simple awaitable"); + std::array jobs; + for (int i = 0; i < 10; ++i) { + jobs[i] = [](int i) -> dpp::job { + static std::atomic passed = false; + if (int ret = co_await job_awaitable{i}; ret != i) + set_status(CORO_JOB_OFFLINE, ts_failed, "failed in-loop awaitable"); + job_data[i] = i; + for (int j = 0; j < 10; ++j) { + if (job_data[j] != j) + co_return; + } + if (passed.exchange(true) == true) // another thread came here and already passed this check, skip the exception + co_return; + throw test_exception<42>(); // should be caught by simple_awaitable (since this is after resume) + }(i); + } + if (std::find_if(job_data.begin(), job_data.end(), [](int i){ return i > 0; }) != job_data.end()) { + set_status(CORO_JOB_OFFLINE, ts_failed, "job should not have modified the data at this point"); + } + + // verify that exceptions work as expected (dpp::job throws uncaught exceptions immediately to the caller / resumer) + try { + []() -> dpp::job { + throw test_exception<1>{}; + }(); + } catch (const test_exception<1> &) { + throw test_exception<42>{}; // should be caught by simple_awaitable (since this is after resume) + } +} + +dpp::task task_offline_test() { + namespace time = std::chrono; + using clock = time::system_clock; + static auto &test = CORO_TASK_OFFLINE; + + if (int ret = co_await simple_awaitable{test, 42}; ret != 42) + set_status(test, ts_failed, "failed simple awaitable"); + std::array, 10> tasks; + auto start = clock::now(); + for (int i = 0; i < 10; ++i) { + tasks[i] = [](int i) -> dpp::task { + if (int ret = co_await simple_awaitable{test, i}; ret != i) + set_status(test, ts_failed, "failed in-loop awaitable"); + job_data[i] = i; + co_return i; + }(i); + } + for (int i = 0; i < 10; ++i) { + if (co_await tasks[i] != i) + set_status(test, ts_failed, "failed to co_await expected value"); + } + auto diff = clock::now() - start; + if (diff > time::seconds(10)) { // task is async so 10 parallel tasks should all take 5 seconds + some overhead + set_status(test, ts_failed, "parallel tasks took longer than expected"); + } + + // verify that exceptions work as expected (dpp::task throws uncaught exceptions to co_await-er) + dpp::task task1; + dpp::task task2; + bool success1 = false; + bool success2 = false; + try { + task1 = []() -> dpp::task { // throws after co_await + if (int ret = co_await simple_awaitable{test, 69}; ret != 69) + set_status(test, ts_failed, "nested failed simple awaitable"); + dpp::task task2 = []() -> dpp::task { + throw test_exception<1>{}; + co_return; // needed to make sure the task is initialized properly + }(); + co_await task2; + }(); + task2 = []() -> dpp::task { // throws immediately + throw test_exception<2>{}; + co_return; // needed to make sure the task is initialized properly + }(); + } catch (const std::exception &e) { + /* SHOULD NOT throw. exceptions are sent when resuming from co_await */ + set_status(test, ts_failed, "task threw in constructor"); + } + try { + co_await task1; + } catch (const test_exception<1> &) { + success1 = true; + } + try { + co_await task2; + } + catch (const test_exception<2> &) { + success2 = true; + } + if (success1 && success2) + throw test_exception<0>{}; +} + +dpp::coroutine coroutine_offline_test() { + static auto &test = CORO_COROUTINE_OFFLINE; + std::array data; + int num = 0; + + auto factory = [&data](int &i) -> dpp::coroutine { + if (int ret = co_await simple_awaitable{test, 42}; ret != 42) + set_status(test, ts_failed, "failed simple awaitable"); + data[i] = i; + co_return i++; + }; + if (int ret = co_await factory(num); ret != 0) + set_status(test, ts_failed, "coroutine 1 to set expected values"); + if (int ret = co_await factory(num); ret != 1) + set_status(test, ts_failed, "coroutine 2 to set expected values"); + if (int ret = co_await factory(num); ret != 2) + set_status(test, ts_failed, "coroutine 3 to set expected values"); + if (data != std::to_array({0, 1, 2})) + set_status(test, ts_failed, "unexpected test data"); + + // verify that exceptions work as expected (dpp::coroutine throws uncaught exceptions to co_await-er) + co_await []() -> dpp::coroutine { + dpp::coroutine nested1; + dpp::coroutine nested2; + try { + nested1 = []() -> dpp::coroutine { + int n = rand(); + if (int ret = co_await simple_awaitable{test, n}; ret != n) { + set_status(test, ts_failed, "nested failed simple awaitable"); + } + throw test_exception<1>{}; + }(); + nested2 = []() -> dpp::coroutine { + throw test_exception<2>{}; + co_return; + }(); + } catch (const std::exception &e) { + /* SHOULD NOT throw. exceptions are sent when resuming from co_await */ + set_status(test, ts_failed, "threw before co_await"); + } + bool success1 = false; + bool success2 = false; + try { + co_await nested1; + } catch (const test_exception<1> &) { + success1 = true; + } + try { + co_await nested2; + } catch (const test_exception<2> &) { + success2 = true; + } + if (success1 && success2) + throw test_exception<0>{}; + else + set_status(test, ts_failed, "failed to throw at co_await"); + }(); // test_exception<0> escapes +} + +void sync_awaitable_fun(std::function callback) { + callback(42); +} + +void sync_awaitable_throw(std::function callback) { + throw test_exception<0>{}; +} + +void async_awaitable_wait5(std::function callback) { + std::thread th([cc = std::move(callback)]() noexcept { + std::this_thread::sleep_for(std::chrono::seconds(5)); + cc(69); + }); + th.detach(); +} + +void async_awaitable_wait5_catch(std::function callback) { + std::thread th([cc = std::move(callback)]() noexcept { + std::this_thread::sleep_for(std::chrono::seconds(5)); + bool caught = false; + try { + cc(69); + } catch (const test_exception<0> &){ + caught = true; + } + if (!caught) + set_status(CORO_AWAITABLE_OFFLINE, ts_failed); + }); + th.detach(); +} + +dpp::job awaitable_test() { + namespace time = std::chrono; + using clock = time::system_clock; + auto &test = CORO_AWAITABLE_OFFLINE; + try { + dpp::awaitable sync_simple{&sync_awaitable_fun}; + + if (int ret = co_await dpp::awaitable{std::move(sync_simple)}; ret != 42) + set_status(test, ts_failed, "failed simple sync return"); + try { + dpp::awaitable sync_throw(&sync_awaitable_throw); + try { + co_await sync_throw; // throws + set_status(test, ts_failed, "failed to propagate exception on co_await"); // normally unreachable + } catch (const test_exception<0> &) { + /* success */ + } + } catch (const test_exception<0> &) { + /* should not throw */ + set_status(test, ts_failed, "threw on constructor"); + } + auto now = clock::now(); + dpp::awaitable async_simple{&async_awaitable_wait5}; + + auto async_throw_test = []() -> dpp::job { + co_await dpp::awaitable{&async_awaitable_wait5_catch}; + + throw test_exception<0>{}; + co_return; + }(); + if (int ret = co_await async_simple; ret != 69) + set_status(test, ts_failed, "failed to co_await asynchronously"); + if (int ret = co_await async_simple; ret != 69) // second co_await on same object + set_status(test, ts_failed, "failed to co_await a second time"); + auto diff = clock::now() - now; + if (diff < time::seconds(10)) // awaitable starts on co_await so 5 + 5 seconds should have passed + set_status(test, ts_failed, "execution took less time than would be slept"); + set_status(test, ts_success); + } catch (const std::exception &e) { + // no exception should be thrown at all + set_status(test, ts_failed, "unhandled exception thrown"); + } +} + +dpp::job async_test() { + namespace time = std::chrono; + using clock = time::system_clock; + test_t &test = CORO_ASYNC_OFFLINE; + try { + std::array, 10> arr; + + std::generate(arr.begin(), arr.end(), [] { return dpp::async{&sync_awaitable_fun}; }); + for (auto &async : arr) { + if (int ret = co_await async; ret != 42) + set_status(test, ts_failed, "failed to sync resume with expected value"); + } + bool success = false; + try { + dpp::async throws{dpp::awaitable{&sync_awaitable_throw}}; + } catch (const test_exception<0> &) { + success = true; + } + if (!success) + set_status(test, ts_failed, "failed to propagate exception"); + auto now = clock::now(); + std::generate(arr.begin(), arr.end(), [] { return dpp::async{&async_awaitable_wait5}; }); + for (auto &async : arr) { + if (int ret = co_await async; ret != 69) + set_status(test, ts_failed, "failed to async resume with expected value"); + } + auto diff = clock::now() - now; + if (diff > time::seconds(10)) { + // async executes asynchronously so we should be waiting 5 seconds + some overhead + set_status(test, ts_failed, "parallel asyncs took more time than expected"); + } + set_status(test, ts_success); + } catch (const std::exception &e) { + /* no exception should be caught here */ + set_status(test, ts_failed, "unknown exception thrown"); + } +} + +} + +void coro_offline_tests() +{ + start_test(CORO_JOB_OFFLINE); + // Initialize all job data to -1 + std::fill(job_data.begin(), job_data.end(), -1); + job_offline_test(); + + start_test(CORO_TASK_OFFLINE); + std::fill(task_data.begin(), task_data.end(), -1); + []() -> dpp::job { + dpp::task task = task_offline_test(); + + try { + co_await dpp::task{std::move(task)}; + } catch (const test_exception<0> &) { // exception thrown at the end of the task test + set_status(CORO_TASK_OFFLINE, ts_success); + } catch (const std::exception &e) { // anything else should not escape + set_status(CORO_TASK_OFFLINE, ts_failed, "unknown exception thrown"); + } + }(); + + start_test(CORO_COROUTINE_OFFLINE); + []() -> dpp::job { + dpp::coroutine task = coroutine_offline_test(); + + try { + co_await dpp::coroutine{std::move(task)}; + } catch (const test_exception<0> &) { + set_status(CORO_COROUTINE_OFFLINE, ts_success); + } catch (const std::exception &e) { // anything else should not escape + set_status(CORO_COROUTINE_OFFLINE, ts_failed, "unknown exception thrown"); + } + }(); + + start_test(CORO_AWAITABLE_OFFLINE); + awaitable_test(); + + start_test(CORO_ASYNC_OFFLINE); + async_test(); +} + +void event_handler_test(dpp::cluster *bot) { + static std::array messages_received; + + bot->on_message_create.co_attach([](dpp::message_create_t event) -> dpp::job { + if (event.msg.content == "coro test") { + dpp::cluster *bot = event.from->creator; + + set_status(CORO_EVENT_HANDLER, ts_success); + start_test(CORO_API_CALLS); + dpp::confirmation_callback_t result = co_await bot->co_message_edit(dpp::message{event.msg}.set_content("coro 👍")); + dpp::message *confirm = std::get_if(&(result.value)); + set_status(CORO_API_CALLS, (confirm == nullptr || confirm->content != "coro 👍") ? ts_failed : ts_success); + + if (extended) { + start_test(CORO_MUMBO_JUMBO); + std::array, 3> tasks; + for (int i = 0; i < 3; ++i) { + tasks[i] = [](dpp::cluster *bot, dpp::snowflake channel, int i) -> dpp::task { + using user_member = std::pair, std::optional>; + constexpr auto get_member_user = [](dpp::cluster *bot) -> dpp::task { + std::pair, std::optional> ret{}; + dpp::confirmation_callback_t result; + + try { + ret.second = dpp::find_guild_member(TEST_GUILD_ID, TEST_USER_ID); + } catch (const dpp::cache_exception &e) {} + if (!ret.second.has_value()) { + result = co_await bot->co_guild_get_member(TEST_GUILD_ID, TEST_USER_ID); + if (!result.is_error()) { + ret.second = std::get(std::move(result).value); + } + } + result = co_await bot->co_user_get_cached(TEST_USER_ID); + if (!result.is_error()) { + ret.first = std::get(std::move(result).value); + } + co_return ret; + }; + auto get_member_task = get_member_user(bot); + std::string content = "coro " + std::to_string(i); + dpp::confirmation_callback_t result = co_await bot->co_message_create(dpp::message{channel, content}); + + if (result.is_error()) + co_return {}; + dpp::message msg = std::get(std::move(result).value); + if (msg.content != content) + co_return {}; + user_member pair = co_await get_member_task; + if (!pair.first.has_value()) + co_return {}; + const std::string& member_nick = pair.second.has_value() ? pair.second->nickname : ""; + const std::string& user_nick = pair.first->username; + result = co_await bot->co_message_edit(msg.set_content("coro " + (member_nick.empty() ? user_nick : member_nick) + " " + std::to_string(i))); + co_return result.is_error() ? dpp::snowflake{} : std::get(result.value).id; + }(bot, event.msg.channel_id, i); + } + std::array msg_ids; + std::array, 3> reacts; + for (int i = 0; i < 3; ++i) { + try { + msg_ids[i] = co_await tasks[i]; + + if (msg_ids[i] == dpp::snowflake{}) { + set_status(CORO_MUMBO_JUMBO, ts_failed); + reacts[i] = dpp::async{[](auto &&cc) { cc(dpp::confirmation_callback_t{}); }}; + } + else + reacts[i] = bot->co_message_add_reaction(msg_ids[i], event.msg.channel_id, "✅"); + } catch (const std::exception &e) { + set_status(CORO_MUMBO_JUMBO, ts_failed, "task threw unknown exception"); + reacts[i] = dpp::async{[](auto &&cc) { cc(dpp::confirmation_callback_t{}); }}; + } + } + for (int i = 0; i < 3; ++i) { + dpp::confirmation_callback_t result = co_await reacts[i]; + } + for (int i = 0; i < 3; ++i) { + bot->message_delete(msg_ids[i], event.msg.channel_id); + } + set_status(CORO_MUMBO_JUMBO, ts_success); + } + } + co_return; + }); + bot->message_create(dpp::message{"coro test"}.set_channel_id(TEST_TEXT_CHANNEL_ID)); +} + +void coro_online_tests(dpp::cluster *bot) { + start_test(CORO_EVENT_HANDLER); + event_handler_test(bot); +} +#else +void coro_offline_tests() {} +void coro_online_tests(dpp::cluster *bot) {} +#endif diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 3ad4b56288..5fd2325c9f 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -54,20 +54,20 @@ int main() {\n\ ```\n\ Markdown lol ||spoiler|| ~~strikethrough~~ `small *code* block`\n"; - set_test("COMPARISON", false); + set_test(COMPARISON, false); dpp::user u1; dpp::user u2; dpp::user u3; u1.id = u2.id = 666; u3.id = 777; - set_test("COMPARISON", u1 == u2 && u1 != u3); + set_test(COMPARISON, u1 == u2 && u1 != u3); - set_test("MD_ESC_1", false); - set_test("MD_ESC_2", false); + set_test(MD_ESC_1, false); + set_test(MD_ESC_2, false); std::string escaped1 = dpp::utility::markdown_escape(test_to_escape); std::string escaped2 = dpp::utility::markdown_escape(test_to_escape, true); - set_test("MD_ESC_1", escaped1 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ + set_test(MD_ESC_1, escaped1 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ ```cpp\n\ int main() {\n\ /* Comment */\n\ @@ -76,7 +76,7 @@ int main() {\n\ };\n\ ```\n\ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ `small *code* block`\n"); - set_test("MD_ESC_2", escaped2 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ + set_test(MD_ESC_2, escaped2 == "\\*\\*\\* \\_This is a test\\_ \\*\\*\\*\n\ \\`\\`\\`cpp\n\ int main\\(\\) {\n\ /\\* Comment \\*/\n\ @@ -86,11 +86,11 @@ int main\\(\\) {\n\ \\`\\`\\`\n\ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* block\\`\n"); - set_test("URLENC", false); - set_test("URLENC", dpp::utility::url_encode("ABC123_+\\|$*/AAA[]😄") == "ABC123_%2B%5C%7C%24%2A%2FAAA%5B%5D%F0%9F%98%84"); + set_test(URLENC, false); + set_test(URLENC, dpp::utility::url_encode("ABC123_+\\|$*/AAA[]😄") == "ABC123_%2B%5C%7C%24%2A%2FAAA%5B%5D%F0%9F%98%84"); dpp::http_connect_info hci; - set_test("HOSTINFO", false); + set_test(HOSTINFO, false); hci = dpp::https_client::get_host_info("https://test.com:444"); bool hci_test = (hci.scheme == "https" && hci.hostname == "test.com" && hci.port == 444 && hci.is_ssl == true); @@ -110,9 +110,9 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b hci = dpp::https_client::get_host_info("test.com"); hci_test = hci_test && (hci.scheme == "http" && hci.hostname == "test.com" && hci.port == 80 && hci.is_ssl == false); - set_test("HOSTINFO", hci_test); + set_test(HOSTINFO, hci_test); - set_test("HTTPS", false); + set_test(HTTPS, false); if (!offline) { dpp::multipart_content multipart = dpp::https_client::build_multipart( "{\"content\":\"test\"}", {"test.txt", "blob.blob"}, {"ABCDEFGHI", "BLOB!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"}, {"text/plain", "application/octet-stream"} @@ -126,69 +126,69 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b ); std::string hdr1 = c.get_header("server"); std::string content1 = c.get_content(); - set_test("HTTPS", hdr1 == "cloudflare" && c.get_status() == 200); + set_test(HTTPS, hdr1 == "cloudflare" && c.get_status() == 200); } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; - set_test("HTTPS", false); + set_test(HTTPS, false); } } - set_test("HTTP", false); + set_test(HTTP, false); try { dpp::https_client c2("github.com", 80, "/", "GET", "", {}, true); std::string hdr2 = c2.get_header("location"); std::string content2 = c2.get_content(); - set_test("HTTP", hdr2 == "https://github.com/" && c2.get_status() == 301); + set_test(HTTP, hdr2 == "https://github.com/" && c2.get_status() == 301); } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; - set_test("HTTP", false); + set_test(HTTP, false); } - set_test("MULTIHEADER", false); + set_test(MULTIHEADER, false); try { dpp::https_client c2("www.google.com", 80, "/", "GET", "", {}, true); size_t count = c2.get_header_count("set-cookie"); size_t count_list = c2.get_header_list("set-cookie").size(); // Google sets a bunch of cookies when we start accessing it. - set_test("MULTIHEADER", c2.get_status() == 200 && count > 1 && count == count_list); + set_test(MULTIHEADER, c2.get_status() == 200 && count > 1 && count == count_list); } catch (const dpp::exception& e) { std::cout << e.what() << "\n"; - set_test("MULTIHEADER", false); + set_test(MULTIHEADER, false); } std::vector testaudio = load_test_audio(); - set_test("READFILE", false); + set_test(READFILE, false); std::string rf_test = dpp::utility::read_file(SHARED_OBJECT); FILE* fp = fopen(SHARED_OBJECT, "rb"); fseek(fp, 0, SEEK_END); size_t off = (size_t)ftell(fp); fclose(fp); - set_test("READFILE", off == rf_test.length()); + set_test(READFILE, off == rf_test.length()); - set_test("TIMESTAMPTOSTRING", false); + set_test(TIMESTAMPTOSTRING, false); #ifndef _WIN32 - set_test("TIMESTAMPTOSTRING", dpp::ts_to_string(1642611864) == "2022-01-19T17:04:24Z"); + set_test(TIMESTAMPTOSTRING, dpp::ts_to_string(1642611864) == "2022-01-19T17:04:24Z"); #else - set_test("TIMESTAMPTOSTRING", true); + set_test(TIMESTAMPTOSTRING, true); #endif - set_test("ROLE.COMPARE", false); + set_test(ROLE_COMPARE, false); dpp::role role_1, role_2; role_1.position = 1; role_2.position = 2; - set_test("ROLE.COMPARE", role_1 < role_2 && role_1 != role_2); + set_test(ROLE_COMPARE, role_1 < role_2 && role_1 != role_2); - set_test("WEBHOOK", false); + set_test(WEBHOOK, false); try { dpp::webhook test_wh("https://discord.com/api/webhooks/833047646548133537/ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE"); - set_test("WEBHOOK", (test_wh.token == "ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE") && (test_wh.id == dpp::snowflake(833047646548133537))); + set_test(WEBHOOK, (test_wh.token == "ntCHEYYIoHSLy_GOxPx6pmM0sUoLbP101ct-WI6F-S4beAV2vaIcl_Id5loAMyQwxqhE") && (test_wh.id == dpp::snowflake(833047646548133537))); } catch (const dpp::exception&) { - set_test("WEBHOOK", false); + set_test(WEBHOOK, false); } { // test interaction_create_t::get_parameter @@ -198,7 +198,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b dpp::interaction_create_t interaction(&client, ""); /* Check the method with subcommands */ - set_test("GET_PARAMETER_WITH_SUBCOMMANDS", false); + set_test(GET_PARAMETER_WITH_SUBCOMMANDS, false); dpp::command_interaction cmd_data; // command cmd_data.type = dpp::ctxm_chat_input; @@ -230,10 +230,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b interaction.command.data = cmd_data; dpp::snowflake value1 = std::get(interaction.get_parameter("user")); - set_test("GET_PARAMETER_WITH_SUBCOMMANDS", value1 == dpp::snowflake(189759562910400512)); + set_test(GET_PARAMETER_WITH_SUBCOMMANDS, value1 == dpp::snowflake(189759562910400512)); /* Check the method without subcommands */ - set_test("GET_PARAMETER_WITHOUT_SUBCOMMANDS", false); + set_test(GET_PARAMETER_WITHOUT_SUBCOMMANDS, false); dpp::command_interaction cmd_data2; // command cmd_data2.type = dpp::ctxm_chat_input; @@ -248,15 +248,15 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b interaction.command.data = cmd_data2; int64_t value2 = std::get(interaction.get_parameter("number")); - set_test("GET_PARAMETER_WITHOUT_SUBCOMMANDS", value2 == 123456); + set_test(GET_PARAMETER_WITHOUT_SUBCOMMANDS, value2 == 123456); } { // test dpp::command_option_choice::fill_from_json - set_test("OPTCHOICE_DOUBLE", false); - set_test("OPTCHOICE_INT", false); - set_test("OPTCHOICE_BOOL", false); - set_test("OPTCHOICE_SNOWFLAKE", false); - set_test("OPTCHOICE_STRING", false); + set_test(OPTCHOICE_DOUBLE, false); + set_test(OPTCHOICE_INT, false); + set_test(OPTCHOICE_BOOL, false); + set_test(OPTCHOICE_SNOWFLAKE, false); + set_test(OPTCHOICE_STRING, false); json j; dpp::command_option_choice choice; j["value"] = 54.321; @@ -278,15 +278,15 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b j["value"] = "foobar"; choice.fill_from_json(&j); bool success_string = std::holds_alternative(choice.value); - set_test("OPTCHOICE_DOUBLE", success_double); - set_test("OPTCHOICE_INT", success_int && success_int2); - set_test("OPTCHOICE_BOOL", success_bool); - set_test("OPTCHOICE_SNOWFLAKE", success_snowflake); - set_test("OPTCHOICE_STRING", success_string); + set_test(OPTCHOICE_DOUBLE, success_double); + set_test(OPTCHOICE_INT, success_int && success_int2); + set_test(OPTCHOICE_BOOL, success_bool); + set_test(OPTCHOICE_SNOWFLAKE, success_snowflake); + set_test(OPTCHOICE_STRING, success_string); } { - set_test("PERMISSION_CLASS", false); + set_test(PERMISSION_CLASS, false); bool success = false; auto p = dpp::permission(); p = 16; @@ -321,7 +321,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }; constexpr auto constexpr_success = permission_test({~uint64_t{0}}); // test in constant evaluated success = permission_test({~uint64_t{0}}) && constexpr_success && success; // test at runtime - set_test("PERMISSION_CLASS", success); + set_test(PERMISSION_CLASS, success); } { // some dpp::user methods @@ -330,18 +330,18 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b user1.discriminator = 0001; user1.username = "brain"; - set_test("USER.GET_MENTION", false); - set_test("USER.GET_MENTION", user1.get_mention() == "<@189759562910400512>"); + set_test(USER_GET_MENTION, false); + set_test(USER_GET_MENTION, user1.get_mention() == "<@189759562910400512>"); - set_test("USER.FORMAT_USERNAME", false); - set_test("USER.FORMAT_USERNAME", user1.format_username() == "brain#0001"); + set_test(USER_FORMAT_USERNAME, false); + set_test(USER_FORMAT_USERNAME, user1.format_username() == "brain#0001"); - set_test("USER.GET_CREATION_TIME", false); - set_test("USER.GET_CREATION_TIME", (uint64_t)user1.get_creation_time() == 1465312605); + set_test(USER_GET_CREATION_TIME, false); + set_test(USER_GET_CREATION_TIME, (uint64_t)user1.get_creation_time() == 1465312605); } { // avatar size function - set_test("UTILITY.AVATAR_SIZE", false); + set_test(UTILITY_AVATAR_SIZE, false); bool success = false; success = dpp::utility::avatar_size(0).empty(); success = dpp::utility::avatar_size(16) == "?size=16" && success; @@ -349,11 +349,11 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b success = dpp::utility::avatar_size(4096) == "?size=4096" && success; success = dpp::utility::avatar_size(8192).empty() && success; success = dpp::utility::avatar_size(3000).empty() && success; - set_test("UTILITY.AVATAR_SIZE", success); + set_test(UTILITY_AVATAR_SIZE, success); } { // cdn endpoint url getter - set_test("UTILITY.CDN_ENDPOINT_URL_HASH", false); + set_test(UTILITY_CDN_ENDPOINT_URL_HASH, false); bool success = false; success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png }, "foobar/test", "", dpp::i_jpg, 0).empty(); success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png }, "foobar/test", "", dpp::i_png, 0) == "https://cdn.discordapp.com/foobar/test.png" && success; @@ -364,11 +364,11 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "12345", dpp::i_png, 0, true, true) == "https://cdn.discordapp.com/foobar/test/a_12345.gif" && success; success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "", dpp::i_png, 0, true, true) == "https://cdn.discordapp.com/foobar/test.gif" && success; success = dpp::utility::cdn_endpoint_url_hash({ dpp::i_png, dpp::i_gif }, "foobar/test", "", dpp::i_gif, 0, false, false).empty() && success; - set_test("UTILITY.CDN_ENDPOINT_URL_HASH", success); + set_test(UTILITY_CDN_ENDPOINT_URL_HASH, success); } { // sticker url getter - set_test("STICKER.GET_URL", false); + set_test(STICKER_GET_URL, false); dpp::sticker s; s.format_type = dpp::sf_png; bool success = s.get_url().empty(); @@ -378,7 +378,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b success = s.get_url() == "https://cdn.discordapp.com/stickers/12345.gif" && success; s.format_type = dpp::sf_lottie; success = s.get_url() == "https://cdn.discordapp.com/stickers/12345.json" && success; - set_test("STICKER.GET_URL", success); + set_test(STICKER_GET_URL, success); } { // user url getter @@ -393,8 +393,8 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b auto user3 = user2; user3.flags |= dpp::u_animated_icon; - set_test("USER.GET_AVATAR_URL", false); - set_test("USER.GET_AVATAR_URL", + set_test(USER_GET_AVATAR_URL, false); + set_test(USER_GET_AVATAR_URL, dpp::user().get_avatar_url().empty() && user1.get_avatar_url() == dpp::utility::cdn_host + "/embed/avatars/1.png" && user2.get_avatar_url() == dpp::utility::cdn_host + "/avatars/189759562910400512/5532c6414c70765a28cf9448c117205f.png" && @@ -413,34 +413,34 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b dpp::emoji emoji; emoji.id = 825407338755653641; - set_test("EMOJI.GET_URL", false); - set_test("EMOJI.GET_URL", emoji.get_url() == dpp::utility::cdn_host + "/emojis/825407338755653641.png"); + set_test(EMOJI_GET_URL, false); + set_test(EMOJI_GET_URL, emoji.get_url() == dpp::utility::cdn_host + "/emojis/825407338755653641.png"); } { // channel methods - set_test("CHANNEL.SET_TYPE", false); + set_test(CHANNEL_SET_TYPE, false); dpp::channel c; c.set_flags(dpp::c_nsfw | dpp::c_video_quality_720p); c.set_type(dpp::CHANNEL_CATEGORY); bool before = c.is_category() && !c.is_forum(); c.set_type(dpp::CHANNEL_FORUM); bool after = !c.is_category() && c.is_forum(); - set_test("CHANNEL.SET_TYPE", before && after); + set_test(CHANNEL_SET_TYPE, before && after); - set_test("CHANNEL.GET_MENTION", false); + set_test(CHANNEL_GET_MENTION, false); c.id = 825411707521728511; - set_test("CHANNEL.GET_MENTION", c.get_mention() == "<#825411707521728511>"); + set_test(CHANNEL_GET_MENTION, c.get_mention() == "<#825411707521728511>"); } { // utility methods - set_test("UTILITY.ICONHASH", false); + set_test(UTILITY_ICONHASH, false); auto iconhash1 = dpp::utility::iconhash("a_5532c6414c70765a28cf9448c117205f"); - set_test("UTILITY.ICONHASH", iconhash1.first == 6139187225817019994 && + set_test(UTILITY_ICONHASH, iconhash1.first == 6139187225817019994 && iconhash1.second == 2940732121894297695 && iconhash1.to_string() == "5532c6414c70765a28cf9448c117205f" ); - set_test("UTILITY.MAKE_URL_PARAMETERS", false); + set_test(UTILITY_MAKE_URL_PARAMETERS, false); auto url_params1 = dpp::utility::make_url_parameters({ {"foo", 15}, {"bar", 7} @@ -449,48 +449,48 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b {"foo", "hello"}, {"bar", "two words"} }); - set_test("UTILITY.MAKE_URL_PARAMETERS", url_params1 == "?bar=7&foo=15" && url_params2 == "?bar=two%20words&foo=hello"); + set_test(UTILITY_MAKE_URL_PARAMETERS, url_params1 == "?bar=7&foo=15" && url_params2 == "?bar=two%20words&foo=hello"); - set_test("UTILITY.MARKDOWN_ESCAPE", false); + set_test(UTILITY_MARKDOWN_ESCAPE, false); auto escaped = dpp::utility::markdown_escape( "> this is a quote\n" "**some bold text**"); - set_test("UTILITY.MARKDOWN_ESCAPE", "\\>this is a quote\\n\\*\\*some bold text\\*\\*"); + set_test(UTILITY_MARKDOWN_ESCAPE, "\\>this is a quote\\n\\*\\*some bold text\\*\\*"); - set_test("UTILITY.TOKENIZE", false); + set_test(UTILITY_TOKENIZE, false); auto tokens = dpp::utility::tokenize("some Whitespace seperated Text to Tokenize", " "); std::vector expected_tokens = {"some", "Whitespace", "seperated", "Text", "to", "Tokenize"}; - set_test("UTILITY.TOKENIZE", tokens == expected_tokens); + set_test(UTILITY_TOKENIZE, tokens == expected_tokens); - set_test("UTILITY.URL_ENCODE", false); + set_test(UTILITY_URL_ENCODE, false); auto url_encoded = dpp::utility::url_encode("S2-^$1Nd+U!g'8+_??o?p-bla bla"); - set_test("UTILITY.URL_ENCODE", url_encoded == "S2-%5E%241Nd%2BU%21g%278%2B_%3F%3Fo%3Fp-bla%20bla"); + set_test(UTILITY_URL_ENCODE, url_encoded == "S2-%5E%241Nd%2BU%21g%278%2B_%3F%3Fo%3Fp-bla%20bla"); - set_test("UTILITY.SLASHCOMMAND_MENTION", false); + set_test(UTILITY_SLASHCOMMAND_MENTION, false); auto mention1 = dpp::utility::slashcommand_mention(123, "name"); auto mention2 = dpp::utility::slashcommand_mention(123, "name", "sub"); auto mention3 = dpp::utility::slashcommand_mention(123, "name", "group", "sub"); bool success = mention1 == "" && mention2 == "" && mention3 == ""; - set_test("UTILITY.SLASHCOMMAND_MENTION", success); + set_test(UTILITY_SLASHCOMMAND_MENTION, success); - set_test("UTILITY.CHANNEL_MENTION", false); + set_test(UTILITY_CHANNEL_MENTION, false); auto channel_mention = dpp::utility::channel_mention(123); - set_test("UTILITY.CHANNEL_MENTION", channel_mention == "<#123>"); + set_test(UTILITY_CHANNEL_MENTION, channel_mention == "<#123>"); - set_test("UTILITY.USER_MENTION", false); + set_test(UTILITY_USER_MENTION, false); auto user_mention = dpp::utility::user_mention(123); - set_test("UTILITY.USER_MENTION", user_mention == "<@123>"); + set_test(UTILITY_USER_MENTION, user_mention == "<@123>"); - set_test("UTILITY.ROLE_MENTION", false); + set_test(UTILITY_ROLE_MENTION, false); auto role_mention = dpp::utility::role_mention(123); - set_test("UTILITY.ROLE_MENTION", role_mention == "<@&123>"); + set_test(UTILITY_ROLE_MENTION, role_mention == "<@&123>"); - set_test("UTILITY.EMOJI_MENTION", false); + set_test(UTILITY_EMOJI_MENTION, false); auto emoji_mention1 = dpp::utility::emoji_mention("role1", 123, false); auto emoji_mention2 = dpp::utility::emoji_mention("role2", 234, true); auto emoji_mention3 = dpp::utility::emoji_mention("white_check_mark", 0, false); auto emoji_mention4 = dpp::utility::emoji_mention("white_check_mark", 0, true); - set_test("UTILITY.EMOJI_MENTION", + set_test(UTILITY_EMOJI_MENTION, emoji_mention1 == "<:role1:123>" && emoji_mention2 == "" && emoji_mention3 == ":white_check_mark:" && @@ -499,54 +499,58 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } #ifndef _WIN32 - set_test("TIMESTRINGTOTIMESTAMP", false); + set_test(TIMESTRINGTOTIMESTAMP, false); json tj; tj["t1"] = "2022-01-19T17:18:14.506000+00:00"; tj["t2"] = "2022-01-19T17:18:14+00:00"; uint32_t inTimestamp = 1642612694; - set_test("TIMESTRINGTOTIMESTAMP", (uint64_t)dpp::ts_not_null(&tj, "t1") == inTimestamp && (uint64_t)dpp::ts_not_null(&tj, "t2") == inTimestamp); + set_test(TIMESTRINGTOTIMESTAMP, (uint64_t)dpp::ts_not_null(&tj, "t1") == inTimestamp && (uint64_t)dpp::ts_not_null(&tj, "t2") == inTimestamp); #else - set_test("TIMESTRINGTOTIMESTAMP", true); + set_test(TIMESTRINGTOTIMESTAMP, true); #endif { - set_test("TS", false); + set_test(TS, false); dpp::managed m(189759562910400512); - set_test("TS", ((uint64_t) m.get_creation_time()) == 1465312605); + set_test(TS, ((uint64_t) m.get_creation_time()) == 1465312605); + } + + { + coro_offline_tests(); } std::vector test_image = load_test_image(); - set_test("PRESENCE", false); - set_test("CLUSTER", false); + set_test(PRESENCE, false); + set_test(CLUSTER, false); try { dpp::cluster bot(token, dpp::i_all_intents); bot.set_websocket_protocol(dpp::ws_etf); - set_test("CLUSTER", true); - set_test("CONNECTION", false); - set_test("GUILDCREATE", false); - set_test("ICONHASH", false); + set_test(CLUSTER, true); + set_test(CONNECTION, false); + set_test(GUILDCREATE, false); + set_test(ICONHASH, false); - set_test("MSGCOLLECT", false); + set_test(MSGCOLLECT, false); if (!offline) { /* Intentional leak: freed on unit test end */ [[maybe_unused]] message_collector* collect_messages = new message_collector(&bot, 25); } - set_test("JSON_PARSE_ERROR", false); + set_test(JSON_PARSE_ERROR, false); dpp::rest_request(&bot, "/nonexistent", "address", "", dpp::m_get, "", [](const dpp::confirmation_callback_t& e) { if (e.is_error() && e.get_error().code == 404) { - set_test("JSON_PARSE_ERROR", true); + set_test(JSON_PARSE_ERROR, true); } else { - set_test("JSON_PARSE_ERROR", false); + set_test(JSON_PARSE_ERROR, false); } }); dpp::utility::iconhash i; std::string dummyval("fcffffffffffff55acaaaaaaaaaaaa66"); i = dummyval; - set_test("ICONHASH", (i.to_string() == dummyval)); + set_test(ICONHASH, (i.to_string() == dummyval)); /* This ensures we test both protocols, as voice is json and shard is etf */ bot.set_websocket_protocol(dpp::ws_etf); @@ -565,11 +569,11 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b std::promise ready_promise; std::future ready_future = ready_promise.get_future(); bot.on_ready([&](const dpp::ready_t & event) { - set_test("CONNECTION", true); + set_test(CONNECTION, true); ready_promise.set_value(); - set_test("APPCOMMAND", false); - set_test("LOGGER", false); + set_test(APPCOMMAND, false); + set_test(LOGGER, false); bot.log(dpp::ll_info, "Test log message"); bot.guild_command_create(dpp::slashcommand().set_name("testcommand") @@ -579,43 +583,43 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b .add_localization("fr", "zut", "Ou est la salor dans Discord?"), TEST_GUILD_ID, [&](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { - set_test("APPCOMMAND", true); - set_test("DELCOMMAND", false); + set_test(APPCOMMAND, true); + set_test(DELCOMMAND, false); dpp::slashcommand s = std::get(callback.value); bot.guild_command_delete(s.id, TEST_GUILD_ID, [&](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { dpp::message test_message(TEST_TEXT_CHANNEL_ID, "test message"); - set_test("DELCOMMAND", true); - set_test("MESSAGECREATE", false); - set_test("MESSAGEEDIT", false); - set_test("MESSAGERECEIVE", false); + set_test(DELCOMMAND, true); + set_test(MESSAGECREATE, false); + set_test(MESSAGEEDIT, false); + set_test(MESSAGERECEIVE, false); test_message.add_file("no-mime", "test"); test_message.add_file("test.txt", "test", "text/plain"); test_message.add_file("test.png", std::string{test_image.begin(), test_image.end()}, "image/png"); bot.message_create(test_message, [&bot](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { - set_test("MESSAGECREATE", true); - set_test("REACT", false); + set_test(MESSAGECREATE, true); + set_test(REACT, false); dpp::message m = std::get(callback.value); - set_test("REACTEVENT", false); + set_test(REACTEVENT, false); bot.message_add_reaction(m.id, TEST_TEXT_CHANNEL_ID, "😄", [](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { - set_test("REACT", true); + set_test(REACT, true); } else { - set_test("REACT", false); + set_test(REACT, false); } }); - set_test("EDITEVENT", false); + set_test(EDITEVENT, false); bot.message_edit(dpp::message(m).set_content("test edit"), [](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { - set_test("MESSAGEEDIT", true); + set_test(MESSAGEEDIT, true); } }); } }); } else { - set_test("DELCOMMAND", false); + set_test(DELCOMMAND, false); } }); } @@ -629,72 +633,72 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b std::cout << "[" << std::fixed << std::setprecision(3) << (dpp::utility::time_f() - get_start_time()) << "]: [\u001b[36m" << dpp::utility::loglevel(event.severity) << "\u001b[0m] " << event.message << "\n"; } if (event.message == "Test log message") { - set_test("LOGGER", true); + set_test(LOGGER, true); } }); - set_test("RUNONCE", false); + set_test(RUNONCE, false); uint8_t runs = 0; for (int x = 0; x < 10; ++x) { if (dpp::run_once()) { runs++; } } - set_test("RUNONCE", (runs == 1)); + set_test(RUNONCE, (runs == 1)); bot.on_voice_ready([&](const dpp::voice_ready_t & event) { - set_test("VOICECONN", true); + set_test(VOICECONN, true); dpp::discord_voice_client* v = event.voice_client; - set_test("VOICESEND", false); + set_test(VOICESEND, false); if (v && v->is_ready()) { v->send_audio_raw((uint16_t*)testaudio.data(), testaudio.size()); } else { - set_test("VOICESEND", false); + set_test(VOICESEND, false); } }); bot.on_invite_create([](const dpp::invite_create_t &event) { auto &inv = event.created_invite; if (!inv.code.empty() && inv.channel_id == TEST_TEXT_CHANNEL_ID && inv.guild_id == TEST_GUILD_ID && inv.created_at != 0 && inv.max_uses == 100) { - set_test("INVITE_CREATE_EVENT", true); + set_test(INVITE_CREATE_EVENT, true); } }); bot.on_invite_delete([](const dpp::invite_delete_t &event) { auto &inv = event.deleted_invite; if (!inv.code.empty() && inv.channel_id == TEST_TEXT_CHANNEL_ID && inv.guild_id == TEST_GUILD_ID) { - set_test("INVITE_DELETE_EVENT", true); + set_test(INVITE_DELETE_EVENT, true); } }); bot.on_voice_buffer_send([&](const dpp::voice_buffer_send_t & event) { if (event.buffer_size == 0) { - set_test("VOICESEND", true); + set_test(VOICESEND, true); } }); - set_test("SYNC", false); + set_test(SYNC, false); if (!offline) { dpp::message m = dpp::sync(&bot, &dpp::cluster::message_create, dpp::message(TEST_TEXT_CHANNEL_ID, "TEST")); - set_test("SYNC", m.content == "TEST"); + set_test(SYNC, m.content == "TEST"); } bot.on_guild_create([&](const dpp::guild_create_t & event) { if (event.created->id == TEST_GUILD_ID) { - set_test("GUILDCREATE", true); + set_test(GUILDCREATE, true); if (event.presences.size() && event.presences.begin()->second.user_id > 0) { - set_test("PRESENCE", true); + set_test(PRESENCE, true); } dpp::guild* g = dpp::find_guild(TEST_GUILD_ID); - set_test("CACHE", false); + set_test(CACHE, false); if (g) { - set_test("CACHE", true); - set_test("VOICECONN", false); + set_test(CACHE, true); + set_test(VOICECONN, false); dpp::discord_client* s = bot.get_shard(0); s->connect_voice(g->id, TEST_VC_ID, false, false); } else { - set_test("CACHE", false); + set_test(CACHE, false); } } }); @@ -714,10 +718,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b void delete_message_if_done() { if (files_tested == std::array{true, true, true} && pin_tested && thread_tested) { - set_test("MESSAGEDELETE", false); + set_test(MESSAGEDELETE, false); bot.message_delete(message_id, channel_id, [](const dpp::confirmation_callback_t &callback) { if (!callback.is_error()) { - set_test("MESSAGEDELETE", true); + set_test(MESSAGEDELETE, true); } }); } @@ -739,16 +743,16 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b assert(!files_tested[index]); files_tested[index] = true; if (files_tested == std::array{true, true, true}) { - set_test("MESSAGEFILE", files_success == std::array{true, true, true}); + set_test(MESSAGEFILE, files_success == std::array{true, true, true}); } delete_message_if_done(); } void test_threads(const dpp::message &message) { - set_test("THREAD_CREATE_MESSAGE", false); - set_test("THREAD_DELETE", false); - set_test("THREAD_DELETE_EVENT", false); + set_test(THREAD_CREATE_MESSAGE, false); + set_test(THREAD_DELETE, false); + set_test(THREAD_DELETE_EVENT, false); bot.thread_create_with_message("test", message.channel_id, message.id, 60, 60, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock(mutex); if (callback.is_error()) { @@ -757,9 +761,9 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b else { auto thread = callback.get(); thread_id = thread.id; - set_test("THREAD_CREATE_MESSAGE", true); + set_test(THREAD_CREATE_MESSAGE, true); bot.channel_delete(thread.id, [this](const dpp::confirmation_callback_t &callback) { - set_test("THREAD_DELETE", !callback.is_error()); + set_test(THREAD_DELETE, !callback.is_error()); set_thread_tested(); }); } @@ -767,7 +771,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } void test_files(const dpp::message &message) { - set_test("MESSAGEFILE", false); + set_test(MESSAGEFILE, false); if (message.attachments.size() == 3) { static constexpr auto check_mimetype = [](const auto &headers, std::string mimetype) { if (auto it = headers.find("content-type"); it != headers.end()) { @@ -813,16 +817,16 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_pin_tested(); return; } - set_test("MESSAGEPIN", false); - set_test("MESSAGEUNPIN", false); + set_test(MESSAGEPIN, false); + set_test(MESSAGEUNPIN, false); bot.message_pin(channel_id, message_id, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock(mutex); if (!callback.is_error()) { - set_test("MESSAGEPIN", true); + set_test(MESSAGEPIN, true); bot.message_unpin(TEST_TEXT_CHANNEL_ID, message_id, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock(mutex); if (!callback.is_error()) { - set_test("MESSAGEUNPIN", true); + set_test(MESSAGEUNPIN, true); } set_pin_tested(); }); @@ -930,7 +934,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b void set_event_tested(event_flag flag) { - if (events_tested_mask & flag) { + if (events_tested_mask & flag) { return; } events_tested_mask |= flag; @@ -970,14 +974,14 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (!edit_tested) { dpp::thread edit = thread; - set_test("THREAD_EDIT", false); - set_test("THREAD_UPDATE_EVENT", false); + set_test(THREAD_EDIT, false); + set_test(THREAD_UPDATE_EVENT, false); edit.name = "edited"; edit.metadata.locked = true; bot.thread_edit(edit, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock(mutex); if (!callback.is_error()) { - set_test("THREAD_EDIT", true); + set_test(THREAD_EDIT, true); } set_edit_tested(); }); @@ -988,7 +992,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b { std::lock_guard lock{mutex}; - set_test("THREAD_GET_ACTIVE", false); + set_test(THREAD_GET_ACTIVE, false); bot.threads_get_active(TEST_GUILD_ID, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (!callback.is_error()) { @@ -997,7 +1001,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b const auto &thread = thread_it->second.active_thread; const auto &member = thread_it->second.bot_member; if (thread.id == thread_id && member.has_value() && member->user_id == bot.me.id) { - set_test("THREAD_GET_ACTIVE", true); + set_test(THREAD_GET_ACTIVE, true); } } } @@ -1014,26 +1018,26 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_members_tested(); return; } - set_test("THREAD_MEMBER_ADD", false); - set_test("THREAD_MEMBER_GET", false); - set_test("THREAD_MEMBERS_GET", false); - set_test("THREAD_MEMBER_REMOVE", false); - set_test("THREAD_MEMBERS_ADD_EVENT", false); - set_test("THREAD_MEMBERS_REMOVE_EVENT", false); + set_test(THREAD_MEMBER_ADD, false); + set_test(THREAD_MEMBER_GET, false); + set_test(THREAD_MEMBERS_GET, false); + set_test(THREAD_MEMBER_REMOVE, false); + set_test(THREAD_MEMBERS_ADD_EVENT, false); + set_test(THREAD_MEMBERS_REMOVE_EVENT, false); bot.thread_member_add(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (callback.is_error()) { set_members_tested(); return; } - set_test("THREAD_MEMBER_ADD", true); + set_test(THREAD_MEMBER_ADD, true); bot.thread_member_get(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (callback.is_error()) { set_members_tested(); return; } - set_test("THREAD_MEMBER_GET", true); + set_test(THREAD_MEMBER_GET, true); bot.thread_members_get(thread_id, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (callback.is_error()) { @@ -1045,11 +1049,11 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b set_members_tested(); return; } - set_test("THREAD_MEMBERS_GET", true); + set_test(THREAD_MEMBERS_GET, true); bot.thread_member_remove(thread_id, TEST_USER_ID, [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; if (!callback.is_error()) { - set_test("THREAD_MEMBER_REMOVE", true); + set_test(THREAD_MEMBER_REMOVE, true); } set_members_tested(); }); @@ -1068,12 +1072,12 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } std::lock_guard lock{mutex}; - set_test("THREAD_MESSAGE", false); - set_test("THREAD_MESSAGE_CREATE_EVENT", false); - set_test("THREAD_MESSAGE_EDIT_EVENT", false); - set_test("THREAD_MESSAGE_REACT_ADD_EVENT", false); - set_test("THREAD_MESSAGE_REACT_REMOVE_EVENT", false); - set_test("THREAD_MESSAGE_DELETE_EVENT", false); + set_test(THREAD_MESSAGE, false); + set_test(THREAD_MESSAGE_CREATE_EVENT, false); + set_test(THREAD_MESSAGE_EDIT_EVENT, false); + set_test(THREAD_MESSAGE_REACT_ADD_EVENT, false); + set_test(THREAD_MESSAGE_REACT_REMOVE_EVENT, false); + set_test(THREAD_MESSAGE_DELETE_EVENT, false); events_to_test_mask |= MESSAGE_CREATE; bot.message_create(dpp::message{"hello thread"}.set_channel_id(thread.id), [this](const dpp::confirmation_callback_t &callback) { std::lock_guard lock{mutex}; @@ -1116,7 +1120,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b events_abort(); return; } - set_test("THREAD_MESSAGE", true); + set_test(THREAD_MESSAGE, true); }); }); }); @@ -1142,7 +1146,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_thread_create([&](const dpp::thread_create_t &event) { if (event.created.name == "thread test") { - set_test("THREAD_CREATE_EVENT", true); + set_test(THREAD_CREATE_EVENT, true); thread_helper.run(event.created); } }); @@ -1152,47 +1156,47 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (event.msg.author.id == bot.me.id) { if (event.msg.content == "test message" && !message_tested) { message_tested = true; - set_test("MESSAGERECEIVE", true); + set_test(MESSAGERECEIVE, true); message_helper.run(event.msg); - set_test("MESSAGESGET", false); + set_test(MESSAGESGET, false); bot.messages_get(event.msg.channel_id, 0, event.msg.id, 0, 5, [](const dpp::confirmation_callback_t &cc){ if (!cc.is_error()) { dpp::message_map mm = std::get(cc.value); if (mm.size()) { - set_test("MESSAGESGET", true); - set_test("TIMESTAMP", false); + set_test(MESSAGESGET, true); + set_test(TIMESTAMP, false); dpp::message m = mm.begin()->second; if (m.sent > 0) { - set_test("TIMESTAMP", true); + set_test(TIMESTAMP, true); } else { - set_test("TIMESTAMP", false); + set_test(TIMESTAMP, false); } } else { - set_test("MESSAGESGET", false); + set_test(MESSAGESGET, false); } } else { - set_test("MESSAGESGET", false); + set_test(MESSAGESGET, false); } }); - set_test("MSGCREATESEND", false); + set_test(MSGCREATESEND, false); event.send("MSGCREATESEND", [&bot, ch_id = event.msg.channel_id] (const auto& cc) { if (!cc.is_error()) { dpp::message m = std::get(cc.value); if (m.channel_id == ch_id) { - set_test("MSGCREATESEND", true); + set_test(MSGCREATESEND, true); } else { bot.log(dpp::ll_debug, cc.http_info.body); - set_test("MSGCREATESEND", false); + set_test(MSGCREATESEND, false); } bot.message_delete(m.id, m.channel_id); } else { bot.log(dpp::ll_debug, cc.http_info.body); - set_test("MSGCREATESEND", false); + set_test(MSGCREATESEND, false); } }); } if (event.msg.channel_id == thread_helper.thread_id && event.msg.content == "hello thread") { - set_test("THREAD_MESSAGE_CREATE_EVENT", true); + set_test(THREAD_MESSAGE_CREATE_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_CREATE); } } @@ -1201,10 +1205,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_message_reaction_add([&](const dpp::message_reaction_add_t & event) { if (event.reacting_user.id == bot.me.id) { if (event.reacting_emoji.name == "😄") { - set_test("REACTEVENT", true); + set_test(REACTEVENT, true); } if (event.channel_id == thread_helper.thread_id && event.reacting_emoji.name == THREAD_EMOJI) { - set_test("THREAD_MESSAGE_REACT_ADD_EVENT", true); + set_test(THREAD_MESSAGE_REACT_ADD_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_REACT); } } @@ -1213,7 +1217,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_message_reaction_remove([&](const dpp::message_reaction_remove_t & event) { if (event.reacting_user_id == bot.me.id) { if (event.channel_id == thread_helper.thread_id && event.reacting_emoji.name == THREAD_EMOJI) { - set_test("THREAD_MESSAGE_REACT_REMOVE_EVENT", true); + set_test(THREAD_MESSAGE_REACT_REMOVE_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_REMOVE_REACT); } } @@ -1221,7 +1225,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_message_delete([&](const dpp::message_delete_t & event) { if (event.deleted->channel_id == thread_helper.thread_id) { - set_test("THREAD_MESSAGE_DELETE_EVENT", true); + set_test(THREAD_MESSAGE_DELETE_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_DELETE); } }); @@ -1231,10 +1235,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (event.msg.author == bot.me.id) { if (event.msg.content == "test edit" && !message_edit_tested) { message_edit_tested = true; - set_test("EDITEVENT", true); + set_test(EDITEVENT, true); } if (event.msg.channel_id == thread_helper.thread_id && event.msg.content == "hello thread?") { - set_test("THREAD_MESSAGE_EDIT_EVENT", true); + set_test(THREAD_MESSAGE_EDIT_EVENT, true); thread_helper.notify_event_tested(thread_test_helper::MESSAGE_EDIT); } } @@ -1242,33 +1246,34 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bot.on_thread_update([&](const dpp::thread_update_t &event) { if (event.updating_guild->id == TEST_GUILD_ID && event.updated.id == thread_helper.thread_id && event.updated.name == "edited") { - set_test("THREAD_UPDATE_EVENT", true); + set_test(THREAD_UPDATE_EVENT, true); } }); bot.on_thread_members_update([&](const dpp::thread_members_update_t &event) { if (event.updating_guild->id == TEST_GUILD_ID && event.thread_id == thread_helper.thread_id) { if (std::find_if(std::begin(event.added), std::end(event.added), is_owner) != std::end(event.added)) { - set_test("THREAD_MEMBERS_ADD_EVENT", true); + set_test(THREAD_MEMBERS_ADD_EVENT, true); } if (std::find_if(std::begin(event.removed_ids), std::end(event.removed_ids), is_owner) != std::end(event.removed_ids)) { - set_test("THREAD_MEMBERS_REMOVE_EVENT", true); + set_test(THREAD_MEMBERS_REMOVE_EVENT, true); } } }); bot.on_thread_delete([&](const dpp::thread_delete_t &event) { if (event.deleting_guild->id == TEST_GUILD_ID && event.deleted.id == message_helper.thread_id) { - set_test("THREAD_DELETE_EVENT", true); + set_test(THREAD_DELETE_EVENT, true); } }); // set to execute from this thread (main thread) after on_ready is fired auto do_online_tests = [&] { - set_test("GUILD_BAN_CREATE", false); - set_test("GUILD_BAN_GET", false); - set_test("GUILD_BANS_GET", false); - set_test("GUILD_BAN_DELETE", false); + coro_online_tests(&bot); + set_test(GUILD_BAN_CREATE, false); + set_test(GUILD_BAN_GET, false); + set_test(GUILD_BANS_GET, false); + set_test(GUILD_BAN_DELETE, false); if (!offline) { // some deleted discord accounts to test the ban stuff with... dpp::snowflake deadUser1(802670069523415057); @@ -1281,7 +1286,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (event.is_error()) { return; } - set_test("GUILD_BAN_CREATE", true); + set_test(GUILD_BAN_CREATE, true); // when created, continue with getting and deleting // get ban @@ -1289,7 +1294,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (!event.is_error()) { dpp::ban ban = event.get(); if (ban.user_id == deadUser1 && ban.reason == "ban reason one") { - set_test("GUILD_BAN_GET", true); + set_test(GUILD_BAN_GET, true); } } }); @@ -1309,7 +1314,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } } if (successCount == 2) { - set_test("GUILD_BANS_GET", true); + set_test(GUILD_BANS_GET, true); } } }); @@ -1321,7 +1326,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b if (!event.is_error()) { bot.guild_ban_delete(TEST_GUILD_ID, deadUser3, [](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { - set_test("GUILD_BAN_DELETE", true); + set_test(GUILD_BAN_DELETE, true); } }); } @@ -1333,27 +1338,27 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); } - set_test("REQUEST_GET_IMAGE", false); + set_test(REQUEST_GET_IMAGE, false); if (!offline) { bot.request("https://dpp.dev/DPP-Logo.png", dpp::m_get, [&bot](const dpp::http_request_completion_t &callback) { if (callback.status != 200) { return; } - set_test("REQUEST_GET_IMAGE", true); + set_test(REQUEST_GET_IMAGE, true); dpp::emoji emoji; emoji.load_image(callback.body, dpp::i_png); emoji.name = "dpp"; // emoji unit test with the requested image - set_test("EMOJI_CREATE", false); - set_test("EMOJI_GET", false); - set_test("EMOJI_DELETE", false); + set_test(EMOJI_CREATE, false); + set_test(EMOJI_GET, false); + set_test(EMOJI_DELETE, false); bot.guild_emoji_create(TEST_GUILD_ID, emoji, [&bot](const dpp::confirmation_callback_t &event) { if (event.is_error()) { return; } - set_test("EMOJI_CREATE", true); + set_test(EMOJI_CREATE, true); auto created = event.get(); bot.guild_emoji_get(TEST_GUILD_ID, created.id, [&bot, created](const dpp::confirmation_callback_t &event) { @@ -1362,12 +1367,12 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } auto fetched = event.get(); if (created.id == fetched.id && created.name == fetched.name && created.flags == fetched.flags) { - set_test("EMOJI_GET", true); + set_test(EMOJI_GET, true); } bot.guild_emoji_delete(TEST_GUILD_ID, fetched.id, [](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { - set_test("EMOJI_DELETE", true); + set_test(EMOJI_DELETE, true); } }); }); @@ -1375,22 +1380,22 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); } - set_test("INVITE_CREATE", false); - set_test("INVITE_GET", false); - set_test("INVITE_DELETE", false); + set_test(INVITE_CREATE, false); + set_test(INVITE_GET, false); + set_test(INVITE_DELETE, false); if (!offline) { dpp::channel channel; channel.id = TEST_TEXT_CHANNEL_ID; dpp::invite invite; invite.max_age = 0; invite.max_uses = 100; - set_test("INVITE_CREATE_EVENT", false); + set_test(INVITE_CREATE_EVENT, false); bot.channel_invite_create(channel, invite, [&bot, invite](const dpp::confirmation_callback_t &event) { if (event.is_error()) return; auto created = event.get(); if (!created.code.empty() && created.channel_id == TEST_TEXT_CHANNEL_ID && created.guild_id == TEST_GUILD_ID && created.inviter.id == bot.me.id) { - set_test("INVITE_CREATE", true); + set_test(INVITE_CREATE, true); } bot.invite_get(created.code, [&bot, created](const dpp::confirmation_callback_t &event) { @@ -1398,30 +1403,30 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b auto retrieved = event.get(); if (retrieved.code == created.code && retrieved.guild_id == created.guild_id && retrieved.channel_id == created.channel_id && retrieved.inviter.id == created.inviter.id) { if (retrieved.destination_guild.flags & dpp::g_community) - set_test("INVITE_GET", retrieved.expires_at == 0); + set_test(INVITE_GET, retrieved.expires_at == 0); else - set_test("INVITE_GET", true); + set_test(INVITE_GET, true); } else { - set_test("INVITE_GET", false); + set_test(INVITE_GET, false); } } else { - set_test("INVITE_GET", false); + set_test(INVITE_GET, false); } - set_test("INVITE_DELETE_EVENT", false); + set_test(INVITE_DELETE_EVENT, false); bot.invite_delete(created.code, [](const dpp::confirmation_callback_t &event) { - set_test("INVITE_DELETE", !event.is_error()); + set_test(INVITE_DELETE, !event.is_error()); }); }); }); } - set_test("AUTOMOD_RULE_CREATE", false); - set_test("AUTOMOD_RULE_GET", false); - set_test("AUTOMOD_RULE_GET_ALL", false); - set_test("AUTOMOD_RULE_DELETE", false); + set_test(AUTOMOD_RULE_CREATE, false); + set_test(AUTOMOD_RULE_GET, false); + set_test(AUTOMOD_RULE_GET_ALL, false); + set_test(AUTOMOD_RULE_DELETE, false); if (!offline) { dpp::automod_rule automodRule; automodRule.name = "automod rule (keyword type)"; @@ -1443,7 +1448,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b return; } auto rules = event.get(); - set_test("AUTOMOD_RULE_GET_ALL", true); + set_test(AUTOMOD_RULE_GET_ALL, true); for (const auto &rule: rules) { if (rule.second.trigger_type == dpp::amod_type_keyword) { // delete one automod rule of type KEYWORD before creating one to make space... @@ -1458,7 +1463,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } auto created = event.get(); if (created.name == automodRule.name) { - set_test("AUTOMOD_RULE_CREATE", true); + set_test(AUTOMOD_RULE_CREATE, true); } // get automod rule @@ -1472,13 +1477,13 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b retrieved.trigger_metadata.keywords == automodRule.trigger_metadata.keywords && retrieved.trigger_metadata.regex_patterns == automodRule.trigger_metadata.regex_patterns && retrieved.trigger_metadata.allow_list == automodRule.trigger_metadata.allow_list && retrieved.actions.size() == automodRule.actions.size()) { - set_test("AUTOMOD_RULE_GET", true); + set_test(AUTOMOD_RULE_GET, true); } // delete the automod rule bot.automod_rule_delete(TEST_GUILD_ID, retrieved.id, [](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { - set_test("AUTOMOD_RULE_DELETE", true); + set_test(AUTOMOD_RULE_DELETE, true); } }); }); @@ -1486,16 +1491,16 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b }); } - set_test("USER_GET", false); - set_test("USER_GET_FLAGS", false); + set_test(USER_GET, false); + set_test(USER_GET_FLAGS, false); if (!offline) { bot.user_get(TEST_USER_ID, [](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { auto u = std::get(event.value); if (u.id == TEST_USER_ID) { - set_test("USER_GET", true); + set_test(USER_GET, true); } else { - set_test("USER_GET", false); + set_test(USER_GET, false); } json j = json::parse(event.http_info.body); uint64_t raw_flags = j["public_flags"]; @@ -1522,20 +1527,20 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b u.is_bot_http_interactions() == ((raw_flags & (1 << 19)) != 0) && u.is_active_developer() == ((raw_flags & (1 << 22)) != 0) ) { - set_test("USER_GET_FLAGS", true); + set_test(USER_GET_FLAGS, true); } else { - set_test("USER_GET_FLAGS", false); + set_test(USER_GET_FLAGS, false); } } else { - set_test("USER_GET", false); - set_test("USER_GET_FLAGS", false); + set_test(USER_GET, false); + set_test(USER_GET_FLAGS, false); } }); } - set_test("VOICE_CHANNEL_CREATE", false); - set_test("VOICE_CHANNEL_EDIT", false); - set_test("VOICE_CHANNEL_DELETE", false); + set_test(VOICE_CHANNEL_CREATE, false); + set_test(VOICE_CHANNEL_EDIT, false); + set_test(VOICE_CHANNEL_DELETE, false); if (!offline) { dpp::channel channel1; channel1.set_type(dpp::CHANNEL_VOICE) @@ -1547,14 +1552,14 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b try { createdChannel = bot.channel_create_sync(channel1); } catch (dpp::rest_exception &exception) { - set_test("VOICE_CHANNEL_CREATE", false); + set_test(VOICE_CHANNEL_CREATE, false); } if (createdChannel.name == channel1.name && createdChannel.user_limit == 99 && createdChannel.name == "voice1") { for (auto overwrite: createdChannel.permission_overwrites) { if (overwrite.id == TEST_GUILD_ID && overwrite.type == dpp::ot_role && overwrite.deny == dpp::p_view_channel) { - set_test("VOICE_CHANNEL_CREATE", true); + set_test(VOICE_CHANNEL_CREATE, true); } } @@ -1570,25 +1575,25 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b try { dpp::channel edited = bot.channel_edit_sync(createdChannel); if (edited.name == "foobar2" && edited.user_limit == 2) { - set_test("VOICE_CHANNEL_EDIT", true); + set_test(VOICE_CHANNEL_EDIT, true); } } catch (dpp::rest_exception &exception) { - set_test("VOICE_CHANNEL_EDIT", false); + set_test(VOICE_CHANNEL_EDIT, false); } // delete the voice channel try { bot.channel_delete_sync(createdChannel.id); - set_test("VOICE_CHANNEL_DELETE", true); + set_test(VOICE_CHANNEL_DELETE, true); } catch (dpp::rest_exception &exception) { - set_test("VOICE_CHANNEL_DELETE", false); + set_test(VOICE_CHANNEL_DELETE, false); } } } - set_test("FORUM_CREATION", false); - set_test("FORUM_CHANNEL_GET", false); - set_test("FORUM_CHANNEL_DELETE", false); + set_test(FORUM_CREATION, false); + set_test(FORUM_CHANNEL_GET, false); + set_test(FORUM_CHANNEL_DELETE, false); if (!offline) { dpp::channel c; c.name = "test-forum-channel"; @@ -1605,7 +1610,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b c.default_thread_rate_limit_per_user = 10; bot.channel_create(c, [&bot](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { - set_test("FORUM_CREATION", true); + set_test(FORUM_CREATION, true); auto channel = std::get(event.value); // retrieve the forum channel and check the values bot.channel_get(channel.id, [forum_id = channel.id, &bot](const dpp::confirmation_callback_t &event) { @@ -1621,56 +1626,56 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b bool name = channel.name == "test-forum-channel"; bool sort = channel.default_sort_order == dpp::so_creation_date; bool rateLimit = channel.default_thread_rate_limit_per_user == 10; - set_test("FORUM_CHANNEL_GET", tag && name && sort && rateLimit); + set_test(FORUM_CHANNEL_GET, tag && name && sort && rateLimit); } else { - set_test("FORUM_CHANNEL_GET", false); + set_test(FORUM_CHANNEL_GET, false); } // delete the forum channel bot.channel_delete(forum_id, [](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { - set_test("FORUM_CHANNEL_DELETE", true); + set_test(FORUM_CHANNEL_DELETE, true); } else { - set_test("FORUM_CHANNEL_DELETE", false); + set_test(FORUM_CHANNEL_DELETE, false); } }); }); } else { - set_test("FORUM_CREATION", false); - set_test("FORUM_CHANNEL_GET", false); + set_test(FORUM_CREATION, false); + set_test(FORUM_CHANNEL_GET, false); } }); } - set_test("THREAD_CREATE", false); + set_test(THREAD_CREATE, false); if (!offline) { bot.thread_create("thread test", TEST_TEXT_CHANNEL_ID, 60, dpp::channel_type::CHANNEL_PUBLIC_THREAD, true, 60, [&](const dpp::confirmation_callback_t &event) { if (!event.is_error()) { const auto &thread = event.get(); - set_test("THREAD_CREATE", true); + set_test(THREAD_CREATE, true); } // the thread tests are in the on_thread_create event handler }); } - set_test("MEMBER_GET", false); + set_test(MEMBER_GET, false); if (!offline) { bot.guild_get_member(TEST_GUILD_ID, TEST_USER_ID, [](const dpp::confirmation_callback_t &event){ if (!event.is_error()) { dpp::guild_member m = std::get(event.value); if (m.guild_id == TEST_GUILD_ID && m.user_id == TEST_USER_ID) { - set_test("MEMBER_GET", true); + set_test(MEMBER_GET, true); } else { - set_test("MEMBER_GET", false); + set_test(MEMBER_GET, false); } } else { - set_test("MEMBER_GET", false); + set_test(MEMBER_GET, false); } }); } - set_test("ROLE_CREATE", false); - set_test("ROLE_EDIT", false); - set_test("ROLE_DELETE", false); + set_test(ROLE_CREATE, false); + set_test(ROLE_EDIT, false); + set_test(ROLE_DELETE, false); if (!offline) { dpp::role r; r.guild_id = TEST_GUILD_ID; @@ -1685,10 +1690,10 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b createdRole.has_move_members() && createdRole.flags & dpp::r_mentionable && createdRole.colour == r.colour) { - set_test("ROLE_CREATE", true); + set_test(ROLE_CREATE, true); } } catch (dpp::rest_exception &exception) { - set_test("ROLE_CREATE", false); + set_test(ROLE_CREATE, false); } createdRole.guild_id = TEST_GUILD_ID; createdRole.name = "Test-Role-Edited"; @@ -1696,53 +1701,53 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b try { dpp::role edited = bot.role_edit_sync(createdRole); if (createdRole.id == edited.id && edited.name == "Test-Role-Edited") { - set_test("ROLE_EDIT", true); + set_test(ROLE_EDIT, true); } } catch (dpp::rest_exception &exception) { - set_test("ROLE_EDIT", false); + set_test(ROLE_EDIT, false); } try { bot.role_delete_sync(TEST_GUILD_ID, createdRole.id); - set_test("ROLE_DELETE", true); + set_test(ROLE_DELETE, true); } catch (dpp::rest_exception &exception) { - set_test("ROLE_DELETE", false); + set_test(ROLE_DELETE, false); } } }; - set_test("BOTSTART", false); + set_test(BOTSTART, false); try { if (!offline) { bot.start(true); - set_test("BOTSTART", true); + set_test(BOTSTART, true); } } catch (const std::exception &) { - set_test("BOTSTART", false); + set_test(BOTSTART, false); } - set_test("TIMERSTART", false); + set_test(TIMERSTART, false); uint32_t ticks = 0; dpp::timer th = bot.start_timer([&](dpp::timer timer_handle) { if (ticks == 5) { /* The simple test timer ticks every second. * If we get to 5 seconds, we know the timer is working. */ - set_test("TIMERSTART", true); + set_test(TIMERSTART, true); } ticks++; }, 1); - set_test("USER_GET_CACHED_PRESENT", false); + set_test(USER_GET_CACHED_PRESENT, false); try { dpp::user_identified u = bot.user_get_cached_sync(TEST_USER_ID); - set_test("USER_GET_CACHED_PRESENT", (u.id == TEST_USER_ID)); + set_test(USER_GET_CACHED_PRESENT, (u.id == TEST_USER_ID)); } catch (const std::exception&) { - set_test("USER_GET_CACHED_PRESENT", false); + set_test(USER_GET_CACHED_PRESENT, false); } - set_test("USER_GET_CACHED_ABSENT", false); + set_test(USER_GET_CACHED_ABSENT, false); try { /* This is the snowflake ID of a discord staff member. * We assume here that staffer's discord IDs will remain constant @@ -1751,38 +1756,38 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b * user ID. */ dpp::user_identified u = bot.user_get_cached_sync(90339695967350784); - set_test("USER_GET_CACHED_ABSENT", (u.id == dpp::snowflake(90339695967350784))); + set_test(USER_GET_CACHED_ABSENT, (u.id == dpp::snowflake(90339695967350784))); } catch (const std::exception&) { - set_test("USER_GET_CACHED_ABSENT", false); + set_test(USER_GET_CACHED_ABSENT, false); } - set_test("TIMEDLISTENER", false); + set_test(TIMEDLISTENER, false); dpp::timed_listener tl(&bot, 10, bot.on_log, [&](const dpp::log_t & event) { - set_test("TIMEDLISTENER", true); + set_test(TIMEDLISTENER, true); }); - set_test("ONESHOT", false); + set_test(ONESHOT, false); bool once = false; dpp::oneshot_timer ost(&bot, 5, [&](dpp::timer timer_handle) { if (!once) { - set_test("ONESHOT", true); + set_test(ONESHOT, true); } else { - set_test("ONESHOT", false); + set_test(ONESHOT, false); } once = true; }); - set_test("CUSTOMCACHE", false); + set_test(CUSTOMCACHE, false); dpp::cache testcache; test_cached_object_t* tco = new test_cached_object_t(666); tco->foo = "bar"; testcache.store(tco); test_cached_object_t* found_tco = testcache.find(666); if (found_tco && found_tco->id == dpp::snowflake(666) && found_tco->foo == "bar") { - set_test("CUSTOMCACHE", true); + set_test(CUSTOMCACHE, true); } else { - set_test("CUSTOMCACHE", false); + set_test(CUSTOMCACHE, false); } testcache.remove(found_tco); @@ -1792,36 +1797,36 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } } - noparam_api_test(current_user_get, dpp::user_identified, "CURRENTUSER"); - singleparam_api_test(channel_get, TEST_TEXT_CHANNEL_ID, dpp::channel, "GETCHAN"); - singleparam_api_test(guild_get, TEST_GUILD_ID, dpp::guild, "GETGUILD"); - singleparam_api_test_list(roles_get, TEST_GUILD_ID, dpp::role_map, "GETROLES"); - singleparam_api_test_list(channels_get, TEST_GUILD_ID, dpp::channel_map, "GETCHANS"); - singleparam_api_test_list(guild_get_invites, TEST_GUILD_ID, dpp::invite_map, "GETINVS"); - multiparam_api_test_list(guild_get_bans, TEST_GUILD_ID, dpp::ban_map, "GETBANS"); - singleparam_api_test_list(channel_pins_get, TEST_TEXT_CHANNEL_ID, dpp::message_map, "GETPINS"); - singleparam_api_test_list(guild_events_get, TEST_GUILD_ID, dpp::scheduled_event_map, "GETEVENTS"); - twoparam_api_test(guild_event_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::scheduled_event, "GETEVENT"); - twoparam_api_test_list(guild_event_users_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::event_member_map, "GETEVENTUSERS"); + noparam_api_test(current_user_get, dpp::user_identified, CURRENTUSER); + singleparam_api_test(channel_get, TEST_TEXT_CHANNEL_ID, dpp::channel, GETCHAN); + singleparam_api_test(guild_get, TEST_GUILD_ID, dpp::guild, GETGUILD); + singleparam_api_test_list(roles_get, TEST_GUILD_ID, dpp::role_map, GETROLES); + singleparam_api_test_list(channels_get, TEST_GUILD_ID, dpp::channel_map, GETCHANS); + singleparam_api_test_list(guild_get_invites, TEST_GUILD_ID, dpp::invite_map, GETINVS); + multiparam_api_test_list(guild_get_bans, TEST_GUILD_ID, dpp::ban_map, GETBANS); + singleparam_api_test_list(channel_pins_get, TEST_TEXT_CHANNEL_ID, dpp::message_map, GETPINS); + singleparam_api_test_list(guild_events_get, TEST_GUILD_ID, dpp::scheduled_event_map, GETEVENTS); + twoparam_api_test(guild_event_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::scheduled_event, GETEVENT); + twoparam_api_test_list(guild_event_users_get, TEST_GUILD_ID, TEST_EVENT_ID, dpp::event_member_map, GETEVENTUSERS); std::this_thread::sleep_for(std::chrono::seconds(20)); /* Test stopping timer */ - set_test("TIMERSTOP", false); - set_test("TIMERSTOP", bot.stop_timer(th)); + set_test(TIMERSTOP, false); + set_test(TIMERSTOP, bot.stop_timer(th)); - set_test("USERCACHE", false); + set_test(USERCACHE, false); if (!offline) { dpp::user* u = dpp::find_user(TEST_USER_ID); - set_test("USERCACHE", u); + set_test(USERCACHE, u); } - set_test("CHANNELCACHE", false); - set_test("CHANNELTYPES", false); + set_test(CHANNELCACHE, false); + set_test(CHANNELTYPES, false); if (!offline) { dpp::channel* c = dpp::find_channel(TEST_TEXT_CHANNEL_ID); dpp::channel* c2 = dpp::find_channel(TEST_VC_ID); - set_test("CHANNELCACHE", c && c2); - set_test("CHANNELTYPES", c && c->is_text_channel() && !c->is_voice_channel() && c2 && c2->is_voice_channel() && !c2->is_text_channel()); + set_test(CHANNELCACHE, c && c2); + set_test(CHANNELTYPES, c && c->is_text_channel() && !c->is_voice_channel() && c2 && c2->is_voice_channel() && !c2->is_text_channel()); } wait_for_tests(); @@ -1829,7 +1834,7 @@ Markdown lol \\|\\|spoiler\\|\\| \\~\\~strikethrough\\~\\~ \\`small \\*code\\* b } catch (const std::exception &e) { std::cout << e.what() << "\n"; - set_test("CLUSTER", false); + set_test(CLUSTER, false); } /* Return value = number of failed tests, exit code 0 = success */ diff --git a/src/unittest/test.h b/src/unittest/test.h index 6aba2a2110..be6a8e5c75 100644 --- a/src/unittest/test.h +++ b/src/unittest/test.h @@ -36,27 +36,212 @@ _Pragma("warning( disable : 5105 )"); // 4251 warns when we export classes or st using json = nlohmann::json; -enum test_type_t { - /* A test that does not require discord connectivity */ - tt_offline, +enum test_flags_t { + tf_offline = 0, /* A test that requires discord connectivity */ - tt_online, - /* A test that requires both online and full tests to be enabled */ - tt_extended + tf_online = 1, + /* A test that requires full tests to be enabled */ + tf_extended = 1 << 1, + /* A test that requires coro to be enabled */ + tf_coro = 1 << 2 }; +enum test_status_t { + /* Test was not executed */ + ts_not_executed = 0, + /* Test was started */ + ts_started, + /* Test was skipped */ + ts_skipped, + /* Test succeeded */ + ts_success, + /* Test failed */ + ts_failed +}; + +struct test_t; + +inline std::vector tests = {}; + /* Represents a test case */ struct test_t { - /* Test type */ - test_type_t type; + std::string_view name; /* Description of test */ - std::string description; - /* Has been executed */ - bool executed = false; - /* Was successfully tested */ - bool success = false; + std::string_view description; + /* Test type */ + test_flags_t flags; + /* Test status */ + test_status_t status = ts_not_executed; + + test_t(std::string_view testname, std::string_view testdesc, int testflags); }; +#define DPP_TEST(name, desc, flags) inline test_t name = {#name, desc, flags} + +/* Current list of unit tests */ +DPP_TEST(CLUSTER, "Instantiate DPP cluster", tf_offline); +DPP_TEST(BOTSTART, "cluster::start method", tf_online); +DPP_TEST(CONNECTION, "Connection to client websocket", tf_online); +DPP_TEST(APPCOMMAND, "Creation of application command", tf_online); +DPP_TEST(DELCOMMAND, "Deletion of application command", tf_online); +DPP_TEST(LOGGER, "Log events", tf_online); +DPP_TEST(MESSAGECREATE, "Creation of a channel message", tf_online); +DPP_TEST(MESSAGEEDIT, "Editing a channel message", tf_online); +DPP_TEST(EDITEVENT, "Message edit event", tf_online); +DPP_TEST(MESSAGEDELETE, "Deletion of a channel message", tf_online); +DPP_TEST(MESSAGERECEIVE, "Receipt of a created message", tf_online); +DPP_TEST(MESSAGEFILE, "Message attachment send and check", tf_online); +DPP_TEST(CACHE, "Test guild cache", tf_online); +DPP_TEST(USERCACHE, "Test user cache", tf_online); +DPP_TEST(VOICECONN, "Connect to voice channel", tf_online); +DPP_TEST(VOICESEND, "Send audio to voice channel", tf_online); +DPP_TEST(REACT, "React to a message", tf_online); +DPP_TEST(REACTEVENT, "Reaction event", tf_online); +DPP_TEST(GUILDCREATE, "Receive guild create event", tf_online); +DPP_TEST(MESSAGESGET, "Get messages", tf_online); +DPP_TEST(TIMESTAMP, "crossplatform_strptime()", tf_online); +DPP_TEST(ICONHASH, "utility::iconhash", tf_offline); +DPP_TEST(CURRENTUSER, "cluster::current_user_get()", tf_online); +DPP_TEST(GETGUILD, "cluster::guild_get()", tf_online); +DPP_TEST(GETCHAN, "cluster::channel_get()", tf_online); +DPP_TEST(GETCHANS, "cluster::channels_get()", tf_online); +DPP_TEST(GETROLES, "cluster::roles_get()", tf_online); +DPP_TEST(GETINVS, "cluster::guild_get_invites()", tf_online); +DPP_TEST(GETBANS, "cluster::guild_get_bans()", tf_online); +DPP_TEST(GETPINS, "cluster::channel_pins_get()", tf_online); +DPP_TEST(GETEVENTS, "cluster::guild_events_get()", tf_online); +DPP_TEST(GETEVENT, "cluster::guild_event_get()", tf_online); +DPP_TEST(MSGCREATESEND, "message_create_t::send()", tf_online); +DPP_TEST(GETEVENTUSERS, "cluster::guild_event_users_get()", tf_online); +DPP_TEST(TIMERSTART, "start timer", tf_online); +DPP_TEST(TIMERSTOP, "stop timer", tf_online); +DPP_TEST(ONESHOT, "one-shot timer", tf_online); +DPP_TEST(TIMEDLISTENER, "timed listener", tf_online); +DPP_TEST(PRESENCE, "Presence intent", tf_online); +DPP_TEST(CUSTOMCACHE, "Instantiate a cache", tf_offline); +DPP_TEST(MSGCOLLECT, "message_collector", tf_online); +DPP_TEST(TS, "managed::get_creation_date()", tf_online); +DPP_TEST(READFILE, "utility::read_file()", tf_offline); +DPP_TEST(TIMESTAMPTOSTRING, "ts_to_string()", tf_offline); +DPP_TEST(TIMESTRINGTOTIMESTAMP, "ts_not_null()", tf_offline); +DPP_TEST(OPTCHOICE_DOUBLE, "command_option_choice::fill_from_json: double", tf_offline); +DPP_TEST(OPTCHOICE_INT, "command_option_choice::fill_from_json: int64_t", tf_offline); +DPP_TEST(OPTCHOICE_BOOL, "command_option_choice::fill_from_json: bool", tf_offline); +DPP_TEST(OPTCHOICE_SNOWFLAKE, "command_option_choice::fill_from_json: snowflake", tf_offline); +DPP_TEST(OPTCHOICE_STRING, "command_option_choice::fill_from_json: string", tf_offline); +DPP_TEST(HOSTINFO, "https_client::get_host_info()", tf_offline); +DPP_TEST(HTTPS, "https_client HTTPS request", tf_online); +DPP_TEST(HTTP, "https_client HTTP request", tf_offline); +DPP_TEST(MULTIHEADER, "multiheader cookie test", tf_offline); +DPP_TEST(RUNONCE, "run_once", tf_offline); +DPP_TEST(WEBHOOK, "webhook construct from URL", tf_offline); +DPP_TEST(MD_ESC_1, "Markdown escaping (ignore code block contents)", tf_offline); +DPP_TEST(MD_ESC_2, "Markdown escaping (escape code block contents)", tf_offline); +DPP_TEST(URLENC, "URL encoding", tf_offline); +DPP_TEST(SYNC, "sync()", tf_online); +DPP_TEST(COMPARISON, "manged object comparison", tf_offline); +DPP_TEST(CHANNELCACHE, "find_channel()", tf_online); +DPP_TEST(CHANNELTYPES, "channel type flags", tf_online); +DPP_TEST(FORUM_CREATION, "create a forum channel", tf_online); +DPP_TEST(FORUM_CHANNEL_GET, "retrieve the created forum channel", tf_online); +DPP_TEST(FORUM_CHANNEL_DELETE, "delete the created forum channel", tf_online); + +DPP_TEST(GUILD_BAN_CREATE, "cluster::guild_ban_add ban three deleted discord accounts", tf_online); +DPP_TEST(GUILD_BAN_GET, "cluster::guild_get_ban getting one of the banned accounts", tf_online); +DPP_TEST(GUILD_BANS_GET, "cluster::guild_get_bans get bans using the after-parameter", tf_online); +DPP_TEST(GUILD_BAN_DELETE, "cluster::guild_ban_delete unban the banned discord accounts", tf_online); + +DPP_TEST(THREAD_CREATE, "cluster::thread_create", tf_online); +DPP_TEST(THREAD_CREATE_EVENT, "cluster::on_thread_create event", tf_online); +DPP_TEST(THREAD_DELETE, "cluster::channel_delete with thread", tf_online); +DPP_TEST(THREAD_DELETE_EVENT, "cluster::on_thread_delete event", tf_online); +DPP_TEST(THREAD_EDIT, "cluster::thread_edit", tf_online); +DPP_TEST(THREAD_UPDATE_EVENT, "cluster::on_thread_update event", tf_online); +DPP_TEST(THREAD_GET_ACTIVE, "cluster::threads_get_active", tf_online); + +DPP_TEST(VOICE_CHANNEL_CREATE, "creating a voice channel", tf_online); +DPP_TEST(VOICE_CHANNEL_EDIT, "editing the created voice channel", tf_online); +DPP_TEST(VOICE_CHANNEL_DELETE, "deleting the created voice channel", tf_online); + +DPP_TEST(PERMISSION_CLASS, "permission", tf_offline); +DPP_TEST(USER_GET, "cluster::user_get", tf_online); +DPP_TEST(USER_GET_FLAGS, "cluster::user_get flag parsing", tf_online); +DPP_TEST(MEMBER_GET, "cluster::guild_get_member", tf_online); +DPP_TEST(USER_GET_MENTION, "user::get_mention", tf_offline); +DPP_TEST(USER_FORMAT_USERNAME, "user::format_username", tf_offline); +DPP_TEST(USER_GET_CREATION_TIME, "user::get_creation_time", tf_offline); +DPP_TEST(USER_GET_AVATAR_URL, "user::get_avatar_url", tf_offline); +DPP_TEST(CHANNEL_SET_TYPE, "channel::set_type", tf_offline); +DPP_TEST(CHANNEL_GET_MENTION, "channel::get_mention", tf_offline); +DPP_TEST(UTILITY_ICONHASH, "utility::iconhash", tf_offline); +DPP_TEST(UTILITY_MAKE_URL_PARAMETERS, "utility::make_url_parameters", tf_offline); +DPP_TEST(UTILITY_MARKDOWN_ESCAPE, "utility::markdown_escape", tf_offline); +DPP_TEST(UTILITY_TOKENIZE, "utility::tokenize", tf_offline); +DPP_TEST(UTILITY_URL_ENCODE, "utility::url_encode", tf_offline); +DPP_TEST(UTILITY_SLASHCOMMAND_MENTION, "utility::slashcommand_mention", tf_offline); +DPP_TEST(UTILITY_CHANNEL_MENTION, "utility::channel_mention", tf_offline); +DPP_TEST(UTILITY_USER_MENTION, "utility::user_mention", tf_offline); +DPP_TEST(UTILITY_ROLE_MENTION, "utility::role_mention", tf_offline); +DPP_TEST(UTILITY_EMOJI_MENTION, "utility::emoji_mention", tf_offline); +DPP_TEST(UTILITY_AVATAR_SIZE, "utility::avatar_size", tf_offline); +DPP_TEST(UTILITY_CDN_ENDPOINT_URL_HASH, "utility::cdn_endpoint_url_hash", tf_offline); +DPP_TEST(STICKER_GET_URL, "sticker::get_url aka utility::cdn_endpoint_url_sticker", tf_offline); +DPP_TEST(EMOJI_GET_URL, "emoji::get_url", tf_offline); +DPP_TEST(ROLE_COMPARE, "role::operator<", tf_offline); +DPP_TEST(ROLE_CREATE, "cluster::role_create", tf_online); +DPP_TEST(ROLE_EDIT, "cluster::role_edit", tf_online); +DPP_TEST(ROLE_DELETE, "cluster::role_delete", tf_online); +DPP_TEST(JSON_PARSE_ERROR, "JSON parse error for post_rest", tf_online); +DPP_TEST(USER_GET_CACHED_PRESENT, "cluster::user_get_cached_sync() with present member", tf_online); +DPP_TEST(USER_GET_CACHED_ABSENT, "cluster::user_get_cached_sync() with not present member", tf_online); +DPP_TEST(GET_PARAMETER_WITH_SUBCOMMANDS, "interaction_create_t::get_parameter() with subcommands", tf_offline); +DPP_TEST(GET_PARAMETER_WITHOUT_SUBCOMMANDS, "interaction_create_t::get_parameter() without subcommands", tf_offline); +DPP_TEST(AUTOMOD_RULE_CREATE, "cluster::automod_rule_create", tf_online); +DPP_TEST(AUTOMOD_RULE_GET, "cluster::automod_rule_get", tf_online); +DPP_TEST(AUTOMOD_RULE_GET_ALL, "cluster::automod_rules_get", tf_online); +DPP_TEST(AUTOMOD_RULE_DELETE, "cluster::automod_rule_delete", tf_online); +DPP_TEST(REQUEST_GET_IMAGE, "using the cluster::request method to fetch an image", tf_online); +DPP_TEST(EMOJI_CREATE, "cluster::guild_emoji_create", tf_online); +DPP_TEST(EMOJI_GET, "cluster::guild_emoji_get", tf_online); +DPP_TEST(EMOJI_DELETE, "cluster::guild_emoji_delete", tf_online); +DPP_TEST(INVITE_CREATE_EVENT, "cluster::on_invite_create", tf_online); +DPP_TEST(INVITE_DELETE_EVENT, "cluster::on_invite_delete", tf_online); +DPP_TEST(INVITE_CREATE, "cluster::channel_invite_create", tf_online); +DPP_TEST(INVITE_GET, "cluster::invite_get", tf_online); +DPP_TEST(INVITE_DELETE, "cluster::invite_delete", tf_online); + +/* Extended set -- Less important, skipped on the master branch due to rate limits and GitHub actions limitations*/ +/* To execute, run unittests with "full" command line argument */ +DPP_TEST(MESSAGEPIN, "Pinning a channel message", tf_online | tf_extended); +DPP_TEST(MESSAGEUNPIN, "Unpinning a channel message", tf_online | tf_extended); + +DPP_TEST(THREAD_MEMBER_ADD, "cluster::thread_member_add", tf_online | tf_extended); +DPP_TEST(THREAD_MEMBER_GET, "cluster::thread_member_get", tf_online | tf_extended); +DPP_TEST(THREAD_MEMBERS_GET, "cluster::thread_members_get", tf_online | tf_extended); +DPP_TEST(THREAD_MEMBER_REMOVE, "cluster::thread_member_remove", tf_online | tf_extended); +DPP_TEST(THREAD_MEMBERS_ADD_EVENT, "cluster::on_thread_members_update event with member addition", tf_online | tf_extended); +DPP_TEST(THREAD_MEMBERS_REMOVE_EVENT, "cluster::on_thread_members_update event with member removal", tf_online | tf_extended); +DPP_TEST(THREAD_CREATE_MESSAGE, "cluster::thread_create_with_message", tf_online | tf_extended); + +DPP_TEST(THREAD_MESSAGE, "message manipulation in thread", tf_online | tf_extended); +DPP_TEST(THREAD_MESSAGE_CREATE_EVENT, "cluster::on_message_create in thread", tf_online | tf_extended); +DPP_TEST(THREAD_MESSAGE_EDIT_EVENT, "cluster::on_message_edit in thread", tf_online | tf_extended); +DPP_TEST(THREAD_MESSAGE_DELETE_EVENT, "cluster::on_message_delete in thread", tf_online | tf_extended); +DPP_TEST(THREAD_MESSAGE_REACT_ADD_EVENT, "cluster::on_reaction_add in thread", tf_online | tf_extended); +DPP_TEST(THREAD_MESSAGE_REACT_REMOVE_EVENT, "cluster::on_reaction_remove in thread", tf_online | tf_extended); + +DPP_TEST(CORO_JOB_OFFLINE, "coro: offline job", tf_offline | tf_coro); +DPP_TEST(CORO_COROUTINE_OFFLINE, "coro: offline coroutine", tf_offline | tf_coro); +DPP_TEST(CORO_TASK_OFFLINE, "coro: offline task", tf_offline | tf_coro); +DPP_TEST(CORO_AWAITABLE_OFFLINE, "coro: offline awaitable", tf_offline | tf_coro); +DPP_TEST(CORO_ASYNC_OFFLINE, "coro: offline async", tf_offline | tf_coro); +DPP_TEST(CORO_EVENT_HANDLER, "coro: online event handler", tf_online | tf_coro); +DPP_TEST(CORO_API_CALLS, "coro: online api calls", tf_online | tf_coro); +DPP_TEST(CORO_MUMBO_JUMBO, "coro: online mumbo jumbo in event handler", tf_online | tf_coro | tf_extended); + +void coro_offline_tests(); +void coro_online_tests(dpp::cluster *bot); + class test_cached_object_t : public dpp::managed { public: test_cached_object_t(dpp::snowflake _id) : dpp::managed(_id) { }; @@ -78,7 +263,13 @@ extern dpp::snowflake TEST_EVENT_ID; /* True if we skip tt_online tests */ extern bool offline; +/* True if we skip tt_extended tests*/ extern bool extended; +#ifdef DPP_CORO +inline constexpr bool coro = true; +#else +inline constexpr bool coro = false; +#endif /** * @brief Perform a test of a REST base API call with one parameter @@ -209,16 +400,45 @@ extern bool extended; } /** - * @brief Sets a test's status - * - * @param testname test name (key) to set the status of + * @brief Sets a test's status (legacy) + * + * @param test The test to set the status of * @param success If set to true, sets success to true, if set to false and called * once, sets executed to true, if called twice, also sets success to false. * This means that before you run the test you should call this function once * with success set to false, then if/wen the test completes call it again with true. * If the test fails, call it a second time with false, or not at all. */ -void set_test(const std::string &testname, bool success = false); +void set_test(test_t &test, bool success = false); + +/** + * @brief Sets a test's status + * + * @param test The test to set the status of + * @param status Status to set the test to + */ +void set_status(test_t &test, test_status_t status, std::string_view message = {}); + +/** + * @brief Sets a test's status to ts_skipped + * + * @param test The test to set the status of + */ +void skip_test(test_t &test); + +/** + * @brief Sets a test's status to ts_started + * + * @param test The test to set the status of + */ +void start_test(test_t &test); + +/** + * @brief Check if a test is/should be skipped + * + * @return bool Whether the test is/should be skipped + */ +bool is_skipped(const test_t &test); /** * @brief Prints a summary of all tests executed @@ -278,7 +498,7 @@ class message_collector : public dpp::message_collector { message_collector(dpp::cluster* cl, uint64_t duration) : dpp::message_collector(cl, duration) { } virtual void completed(const std::vector& list) { - set_test("MSGCOLLECT", list.size() > 0); + set_test(MSGCOLLECT, list.size() > 0); } }; diff --git a/src/unittest/unittest.cpp b/src/unittest/unittest.cpp index 971f6a5d2d..37408448a9 100644 --- a/src/unittest/unittest.cpp +++ b/src/unittest/unittest.cpp @@ -23,159 +23,6 @@ #include #include -/* Current list of unit tests */ -std::map tests = { - {"CLUSTER", {tt_offline, "Instantiate DPP cluster", false, false}}, - {"BOTSTART", {tt_online, "cluster::start method", false, false}}, - {"CONNECTION", {tt_online, "Connection to client websocket", false, false}}, - {"APPCOMMAND", {tt_online, "Creation of application command", false, false}}, - {"DELCOMMAND", {tt_online, "Deletion of application command", false, false}}, - {"LOGGER", {tt_online, "Log events", false, false}}, - {"MESSAGECREATE", {tt_online, "Creation of a channel message", false, false}}, - {"MESSAGEEDIT", {tt_online, "Editing a channel message", false, false}}, - {"EDITEVENT", {tt_online, "Message edit event", false, false}}, - {"MESSAGEDELETE", {tt_online, "Deletion of a channel message", false, false}}, - {"MESSAGERECEIVE", {tt_online, "Receipt of a created message", false, false}}, - {"MESSAGEFILE", {tt_online, "Message attachment send and check", false, false}}, - {"CACHE", {tt_online, "Test guild cache", false, false}}, - {"USERCACHE", {tt_online, "Test user cache", false, false}}, - {"VOICECONN", {tt_online, "Connect to voice channel", false, false}}, - {"VOICESEND", {tt_online, "Send audio to voice channel", false, false}}, - {"REACT", {tt_online, "React to a message", false, false}}, - {"REACTEVENT", {tt_online, "Reaction event", false, false}}, - {"GUILDCREATE", {tt_online, "Receive guild create event", false, false}}, - {"MESSAGESGET", {tt_online, "Get messages", false, false}}, - {"TIMESTAMP", {tt_online, "crossplatform_strptime()", false, false}}, - {"ICONHASH", {tt_offline, "utility::iconhash", false, false}}, - {"CURRENTUSER", {tt_online, "cluster::current_user_get()", false, false}}, - {"GETGUILD", {tt_online, "cluster::guild_get()", false, false}}, - {"GETCHAN", {tt_online, "cluster::channel_get()", false, false}}, - {"GETCHANS", {tt_online, "cluster::channels_get()", false, false}}, - {"GETROLES", {tt_online, "cluster::roles_get()", false, false}}, - {"GETINVS", {tt_online, "cluster::guild_get_invites()", false, false}}, - {"GETBANS", {tt_online, "cluster::guild_get_bans()", false, false}}, - {"GETPINS", {tt_online, "cluster::channel_pins_get()", false, false}}, - {"GETEVENTS", {tt_online, "cluster::guild_events_get()", false, false}}, - {"GETEVENT", {tt_online, "cluster::guild_event_get()", false, false}}, - {"MSGCREATESEND", {tt_online, "message_create_t::send()", false, false}}, - {"GETEVENTUSERS", {tt_online, "cluster::guild_event_users_get()", false, false}}, - {"TIMERSTART", {tt_online, "start timer", false, false}}, - {"TIMERSTOP", {tt_online, "stop timer", false, false}}, - {"ONESHOT", {tt_online, "one-shot timer", false, false}}, - {"PRESENCE", {tt_online, "Presence intent", false, false}}, - {"CUSTOMCACHE", {tt_offline, "Instantiate a cache", false, false}}, - {"MSGCOLLECT", {tt_online, "message_collector", false, false}}, - {"TS", {tt_online, "managed::get_creation_date()", false, false}}, - {"READFILE", {tt_offline, "utility::read_file()", false, false}}, - {"TIMESTAMPTOSTRING", {tt_offline, "ts_to_string()", false, false}}, - {"TIMESTRINGTOTIMESTAMP", {tt_offline, "ts_not_null()", false, false}}, - {"OPTCHOICE_DOUBLE", {tt_offline, "command_option_choice::fill_from_json: double", false, false}}, - {"OPTCHOICE_INT", {tt_offline, "command_option_choice::fill_from_json: int64_t", false, false}}, - {"OPTCHOICE_BOOL", {tt_offline, "command_option_choice::fill_from_json: bool", false, false}}, - {"OPTCHOICE_SNOWFLAKE", {tt_offline, "command_option_choice::fill_from_json: snowflake", false, false}}, - {"OPTCHOICE_STRING", {tt_offline, "command_option_choice::fill_from_json: string", false, false}}, - {"HOSTINFO", {tt_offline, "https_client::get_host_info()", false, false}}, - {"HTTPS", {tt_online, "https_client HTTPS request", false, false}}, - {"HTTP", {tt_offline, "https_client HTTP request", false, false}}, - {"MULTIHEADER", {tt_offline, "multiheader cookie test", false, false}}, - {"RUNONCE", {tt_offline, "run_once", false, false}}, - {"WEBHOOK", {tt_offline, "webhook construct from URL", false, false}}, - {"MD_ESC_1", {tt_offline, "Markdown escaping (ignore code block contents)", false, false}}, - {"MD_ESC_2", {tt_offline, "Markdown escaping (escape code block contents)", false, false}}, - {"URLENC", {tt_offline, "URL encoding", false, false}}, - {"SYNC", {tt_online, "sync()", false, false}}, - {"COMPARISON", {tt_offline, "manged object comparison", false, false}}, - {"CHANNELCACHE", {tt_online, "find_channel()", false, false}}, - {"CHANNELTYPES", {tt_online, "channel type flags", false, false}}, - {"FORUM_CREATION", {tt_online, "create a forum channel", false, false}}, - {"FORUM_CHANNEL_GET", {tt_online, "retrieve the created forum channel", false, false}}, - {"FORUM_CHANNEL_DELETE", {tt_online, "delete the created forum channel", false, false}}, - - {"GUILD_BAN_CREATE", {tt_online, "cluster::guild_ban_add ban three deleted discord accounts", false, false}}, - {"GUILD_BAN_GET", {tt_online, "cluster::guild_get_ban getting one of the banned accounts", false, false}}, - {"GUILD_BANS_GET", {tt_online, "cluster::guild_get_bans get bans using the after-parameter", false, false}}, - {"GUILD_BAN_DELETE", {tt_online, "cluster::guild_ban_delete unban the banned discord accounts", false, false}}, - - {"THREAD_CREATE", {tt_online, "cluster::thread_create", false, false}}, - {"THREAD_CREATE_EVENT", {tt_online, "cluster::on_thread_create event", false, false}}, - {"THREAD_DELETE", {tt_online, "cluster::channel_delete with thread", false, false}}, - {"THREAD_DELETE_EVENT", {tt_online, "cluster::on_thread_delete event", false, false}}, - {"THREAD_EDIT", {tt_online, "cluster::thread_edit", false, false}}, - {"THREAD_UPDATE_EVENT", {tt_online, "cluster::on_thread_update event", false, false}}, - {"THREAD_GET_ACTIVE", {tt_online, "cluster::threads_get_active", false, false}}, - - {"VOICE_CHANNEL_CREATE", {tt_online, "creating a voice channel", false, false}}, - {"VOICE_CHANNEL_EDIT", {tt_online, "editing the created voice channel", false, false}}, - {"VOICE_CHANNEL_DELETE", {tt_online, "deleting the created voice channel", false, false}}, - - {"PERMISSION_CLASS", {tt_offline, "permission", false, false}}, - {"USER_GET", {tt_online, "cluster::user_get", false, false}}, - {"USER_GET_FLAGS", {tt_online, "cluster::user_get flag parsing", false, false}}, - {"MEMBER_GET", {tt_online, "cluster::guild_get_member", false, false}}, - {"USER.GET_MENTION", {tt_offline, "user::get_mention", false, false}}, - {"USER.FORMAT_USERNAME", {tt_offline, "user::format_username", false, false}}, - {"USER.GET_CREATION_TIME", {tt_offline, "user::get_creation_time", false, false}}, - {"USER.GET_AVATAR_URL", {tt_offline, "user::get_avatar_url", false, false}}, - {"CHANNEL.SET_TYPE", {tt_offline, "channel::set_type", false, false}}, - {"CHANNEL.GET_MENTION", {tt_offline, "channel::get_mention", false, false}}, - {"UTILITY.ICONHASH", {tt_offline, "utility::iconhash", false, false}}, - {"UTILITY.MAKE_URL_PARAMETERS", {tt_offline, "utility::make_url_parameters", false, false}}, - {"UTILITY.MARKDOWN_ESCAPE", {tt_offline, "utility::markdown_escape", false, false}}, - {"UTILITY.TOKENIZE", {tt_offline, "utility::tokenize", false, false}}, - {"UTILITY.URL_ENCODE", {tt_offline, "utility::url_encode", false, false}}, - {"UTILITY.SLASHCOMMAND_MENTION", {tt_offline, "utility::slashcommand_mention", false, false}}, - {"UTILITY.CHANNEL_MENTION", {tt_offline, "utility::channel_mention", false, false}}, - {"UTILITY.USER_MENTION", {tt_offline, "utility::user_mention", false, false}}, - {"UTILITY.ROLE_MENTION", {tt_offline, "utility::role_mention", false, false}}, - {"UTILITY.EMOJI_MENTION", {tt_offline, "utility::emoji_mention", false, false}}, - {"UTILITY.AVATAR_SIZE", {tt_offline, "utility::avatar_size", false, false}}, - {"UTILITY.CDN_ENDPOINT_URL_HASH", {tt_offline, "utility::cdn_endpoint_url_hash", false, false}}, - {"STICKER.GET_URL", {tt_offline, "sticker::get_url aka utility::cdn_endpoint_url_sticker", false, false}}, - {"EMOJI.GET_URL", {tt_offline, "emoji::get_url", false, false}}, - {"ROLE.COMPARE", {tt_offline, "role::operator<", false, false}}, - {"ROLE_CREATE", {tt_online, "cluster::role_create", false, false}}, - {"ROLE_EDIT", {tt_online, "cluster::role_edit", false, false}}, - {"ROLE_DELETE", {tt_online, "cluster::role_delete", false, false}}, - {"JSON_PARSE_ERROR", {tt_online, "JSON parse error for post_rest", false, false}}, - {"USER_GET_CACHED_PRESENT", {tt_online, "cluster::user_get_cached_sync() with present member", false, false}}, - {"USER_GET_CACHED_ABSENT", {tt_online, "cluster::user_get_cached_sync() with not present member", false, false}}, - {"GET_PARAMETER_WITH_SUBCOMMANDS", {tt_offline, "interaction_create_t::get_parameter() with subcommands", false, false}}, - {"GET_PARAMETER_WITHOUT_SUBCOMMANDS", {tt_offline, "interaction_create_t::get_parameter() without subcommands", false, false}}, - {"AUTOMOD_RULE_CREATE", {tt_online, "cluster::automod_rule_create", false, false}}, - {"AUTOMOD_RULE_GET", {tt_online, "cluster::automod_rule_get", false, false}}, - {"AUTOMOD_RULE_GET_ALL", {tt_online, "cluster::automod_rules_get", false, false}}, - {"AUTOMOD_RULE_DELETE", {tt_online, "cluster::automod_rule_delete", false, false}}, - {"REQUEST_GET_IMAGE", {tt_online, "using the cluster::request method to fetch an image", false, false}}, - {"EMOJI_CREATE", {tt_online, "cluster::guild_emoji_create", false, false}}, - {"EMOJI_GET", {tt_online, "cluster::guild_emoji_get", false, false}}, - {"EMOJI_DELETE", {tt_online, "cluster::guild_emoji_delete", false, false}}, - {"INVITE_CREATE_EVENT", {tt_online, "cluster::on_invite_create", false, false}}, - {"INVITE_DELETE_EVENT", {tt_online, "cluster::on_invite_delete", false, false}}, - {"INVITE_CREATE", {tt_online, "cluster::channel_invite_create", false, false}}, - {"INVITE_GET", {tt_online, "cluster::invite_get", false, false}}, - {"INVITE_DELETE", {tt_online, "cluster::invite_delete", false, false}}, - - /* Extended set -- Less important, skipped on the master branch due to rate limits and GitHub actions limitations*/ - /* To execute, run unittests with "full" command line argument */ - {"MESSAGEPIN", {tt_extended, "Pinning a channel message", false, false}}, - {"MESSAGEUNPIN", {tt_extended, "Unpinning a channel message", false, false}}, - - {"THREAD_MEMBER_ADD", {tt_extended, "cluster::thread_member_add", false, false}}, - {"THREAD_MEMBER_GET", {tt_extended, "cluster::thread_member_get", false, false}}, - {"THREAD_MEMBERS_GET", {tt_extended, "cluster::thread_members_get", false, false}}, - {"THREAD_MEMBER_REMOVE", {tt_extended, "cluster::thread_member_remove", false, false}}, - {"THREAD_MEMBERS_ADD_EVENT", {tt_extended, "cluster::on_thread_members_update event with member addition", false, false}}, - {"THREAD_MEMBERS_REMOVE_EVENT", {tt_extended, "cluster::on_thread_members_update event with member removal", false, false}}, - {"THREAD_CREATE_MESSAGE", {tt_extended, "cluster::thread_create_with_message", false, false}}, - - {"THREAD_MESSAGE", {tt_extended, "message manipulation in thread", false, false}}, - {"THREAD_MESSAGE_CREATE_EVENT", {tt_extended, "cluster::on_message_create in thread", false, false}}, - {"THREAD_MESSAGE_EDIT_EVENT", {tt_extended, "cluster::on_message_edit in thread", false, false}}, - {"THREAD_MESSAGE_DELETE_EVENT", {tt_extended, "cluster::on_message_delete in thread", false, false}}, - {"THREAD_MESSAGE_REACT_ADD_EVENT", {tt_extended, "cluster::on_reaction_add in thread", false, false}}, - {"THREAD_MESSAGE_REACT_REMOVE_EVENT", {tt_extended, "cluster::on_reaction_remove in thread", false, false}}, -}; - double start = dpp::utility::time_f(); bool offline = false; bool extended = false; @@ -186,28 +33,60 @@ dpp::snowflake TEST_VC_ID = std::stoull(SAFE_GETENV("TEST_VC_ID")); dpp::snowflake TEST_USER_ID = std::stoull(SAFE_GETENV("TEST_USER_ID")); dpp::snowflake TEST_EVENT_ID = std::stoull(SAFE_GETENV("TEST_EVENT_ID")); -void set_test(const std::string &testname, bool success) { - auto i = tests.find(testname); - if (i != tests.end()) { - if (offline && i->second.type == tt_online) { - i->second.success = true; - i->second.executed = true; - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mSKIPPED\u001b[0m] " << i->second.description << "\n"; - } else { - if (!i->second.executed) { - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mTESTING\u001b[0m] " << i->second.description << "\n"; - } else if (!success) { - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[31mFAILED\u001b[0m] " << i->second.description << "\n"; - } - i->second.executed = true; - if (success) { - i->second.success = true; - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[32mSUCCESS\u001b[0m] " << i->second.description << "\n"; - } - } +test_t::test_t(std::string_view testname, std::string_view testdesc, int testflags) : name{testname}, description{testdesc}, flags{static_cast(testflags)} { + tests.push_back(this); +} + +void set_status(test_t &test, test_status_t newstatus, std::string_view message) { + static std::mutex m; + std::scoped_lock lock{m}; + + if (is_skipped(test) || newstatus == test.status) + return; + if (test.status != ts_failed) // disallow changing the state of a failed test, but we still log + test.status = newstatus; + switch (newstatus) { + case ts_started: + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mTESTING\u001b[0m] " << test.description << (message.empty() ? "" : " - " + std::string{message} ) << "\n"; + break; + + case ts_failed: + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[31mFAILED\u001b[0m] " << test.description << (message.empty() ? "" : " - " + std::string{message} ) << "\n"; + break; + + case ts_success: + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[32mSUCCESS\u001b[0m] " << test.description << (message.empty() ? "" : " - " + std::string{message} ) << "\n"; + break; + + case ts_skipped: + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mSKIPPED\u001b[0m] " << test.description << (message.empty() ? "" : " - " + std::string{message} ) << "\n"; + break; + + default: + break; } } +void start_test(test_t &test) { + set_status(test, ts_started); +} + +void skip_test(test_t &test) { + set_status(test, ts_skipped); +} + +void set_test(test_t &test, bool success) { + if (test.status == ts_not_executed) { + set_status(test, success ? ts_success : ts_started); + } else { + set_status(test, success ? ts_success : ts_failed); + } +} + +bool is_skipped(const test_t &test) { + return (test.flags & tf_online && offline) || (test.flags & tf_extended && !extended) || (test.flags & tf_coro && !coro); +} + double get_start_time() { return start; } @@ -221,18 +100,20 @@ int test_summary() { int failed = 0, passed = 0, skipped = 0; std::cout << "\u001b[37;1m\n\nUNIT TEST SUMMARY\n==================\n\u001b[0m"; for (auto & t : tests) { - bool test_skipped = false; - if ((t.second.type == tt_online && offline) || (t.second.type == tt_extended && !extended)) { + std::cout << std::left << std::setw(60) << t->description << " " << std::fixed << std::setw(6); + if (t->status == ts_skipped || (t->flags & tf_online && offline) || (t->flags & tf_extended && !extended) || (t->flags & tf_coro && !coro)) { skipped++; - test_skipped = true; + std::cout << "\u001b[33mSKIPPED"; } else { - if (t.second.success == false || t.second.executed == false) { - failed++; - } else { + if (t->status == ts_success) { passed++; + std::cout << "\u001b[32mPASS"; + } else { + failed++; + std::cout << (t->status == ts_not_executed ? "\u001b[31mNOT EXECUTED" : "\u001b[31mFAIL"); } } - std::cout << std::left << std::setw(60) << t.second.description << " " << std::fixed << std::setw(6) << (test_skipped ? "\u001b[33mSKIPPED" : (t.second.executed && t.second.success ? "\u001b[32mPASS" : (!t.second.executed ? "\u001b[31mNOT EXECUTED" : "\u001b[31mFAIL"))) << std::setw(0) << "\u001b[0m\n"; + std::cout << std::setw(0) << "\u001b[0m\n"; } std::cout << "\u001b[37;1m\nExecution finished in " << std::fixed << std::setprecision(3) << get_time() << std::setprecision(0) << " seconds.\nFailed: " << failed << " Passed: " << passed << (skipped ? " Skipped: " : "") << (skipped ? std::to_string(skipped) : "") << " Percentage: " << std::setprecision(2) << ((float)(passed) / (float)(passed + failed) * 100.0f) << "%\u001b[0m\n"; return failed; @@ -299,25 +180,25 @@ std::string get_token() { void wait_for_tests() { uint16_t ticks = 0; while (ticks < TEST_TIMEOUT) { - size_t executed = 0; - for (auto & t : tests) { - if (t.second.executed == true) { - executed++; - } else if (!t.second.executed && ((offline && t.second.type == tt_online) || (!extended && t.second.type == tt_extended))) { - executed++; - t.second.executed = true; - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mSKIPPED\u001b[0m] " << t.second.description << "\n"; + size_t finished = 0; + for (auto t : tests) { + if (t->status != ts_started) { + finished++; + } else if (is_skipped(*t)) { + finished++; + t->status = ts_skipped; + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[33mSKIPPED\u001b[0m] " << t->description << "\n"; } } - if (executed == tests.size()) { + if (finished == tests.size()) { std::this_thread::sleep_for(std::chrono::seconds(10)); return; } std::this_thread::sleep_for(std::chrono::seconds(1)); ticks++; } - for (auto &t : tests) { - if (!t.second.executed) - std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[31mTIMEOUT\u001b[0m] " << t.second.description << "\n"; + for (auto t : tests) { + if (t->status == ts_started) + std::cout << "[" << std::fixed << std::setprecision(3) << get_time() << "]: " << "[\u001b[31mTIMEOUT\u001b[0m] " << t->description << "\n"; } }