141 lines
3.8 KiB
C++
141 lines
3.8 KiB
C++
#include <drogon/drogon_test.h>
|
|
#include <drogon/utils/coroutine.h>
|
|
|
|
using namespace drogon;
|
|
|
|
namespace drogon::internal
|
|
{
|
|
struct SomeStruct
|
|
{
|
|
~SomeStruct()
|
|
{
|
|
beenDestructed = true;
|
|
}
|
|
static bool beenDestructed;
|
|
};
|
|
|
|
bool SomeStruct::beenDestructed = false;
|
|
|
|
struct StructAwaiter : public CallbackAwaiter<std::shared_ptr<SomeStruct>>
|
|
{
|
|
void await_suspend(std::coroutine_handle<> handle)
|
|
{
|
|
setValue(std::make_shared<SomeStruct>());
|
|
handle.resume();
|
|
}
|
|
};
|
|
|
|
} // namespace drogon::internal
|
|
|
|
// Workarround limitation of macros
|
|
template <typename T>
|
|
using is_int = std::is_same<T, int>;
|
|
template <typename T>
|
|
using is_void = std::is_same<T, void>;
|
|
|
|
DROGON_TEST(CroutineBasics)
|
|
{
|
|
// Basic checks making sure coroutine works as expected
|
|
STATIC_REQUIRE(is_awaitable_v<Task<>>);
|
|
STATIC_REQUIRE(is_awaitable_v<Task<int>>);
|
|
STATIC_REQUIRE(is_awaitable_v<Task<>>);
|
|
STATIC_REQUIRE(is_awaitable_v<Task<int>>);
|
|
STATIC_REQUIRE(is_int<await_result_t<Task<int>>>::value);
|
|
STATIC_REQUIRE(is_void<await_result_t<Task<>>>::value);
|
|
|
|
// No, you cannot await AsyncTask. By design
|
|
STATIC_REQUIRE(is_awaitable_v<AsyncTask> == false);
|
|
|
|
// AsyncTask should execute eagerly
|
|
int m = 0;
|
|
[&m]() -> AsyncTask {
|
|
m = 1;
|
|
co_return;
|
|
}();
|
|
REQUIRE(m == 1);
|
|
|
|
// Make sure sync_wait works
|
|
CHECK(sync_wait([]() -> Task<int> { co_return 1; }()) == 1);
|
|
|
|
// make sure it does affect the outside world
|
|
int n = 0;
|
|
sync_wait([&]() -> Task<> {
|
|
n = 1;
|
|
co_return;
|
|
}());
|
|
CHECK(n == 1);
|
|
|
|
// Testing that exceptions can propergate through coroutines
|
|
auto throw_in_task = [TEST_CTX]() -> Task<> {
|
|
auto f = []() -> Task<> { throw std::runtime_error("test error"); };
|
|
|
|
CHECK_THROWS_AS(co_await f(), std::runtime_error);
|
|
};
|
|
sync_wait(throw_in_task());
|
|
|
|
// Test sync_wait propergrates exception
|
|
auto throws = []() -> Task<> {
|
|
throw std::runtime_error("bla");
|
|
co_return;
|
|
};
|
|
CHECK_THROWS_AS(sync_wait(throws()), std::runtime_error);
|
|
|
|
// Test co_return non-copyable object works
|
|
auto return_unique_ptr = [TEST_CTX]() -> Task<std::unique_ptr<int>> {
|
|
co_return std::make_unique<int>(42);
|
|
};
|
|
CHECK(*sync_wait(return_unique_ptr()) == 42);
|
|
|
|
// Test co_awaiting non-copyable object works
|
|
auto await_non_copyable = [TEST_CTX]() -> Task<> {
|
|
auto return_unique_ptr = []() -> Task<std::unique_ptr<int>> {
|
|
co_return std::make_unique<int>(123);
|
|
};
|
|
auto ptr = co_await return_unique_ptr();
|
|
CHECK(*ptr == 123);
|
|
};
|
|
sync_wait(await_non_copyable());
|
|
}
|
|
|
|
DROGON_TEST(CompilcatedCoroutineLifetime)
|
|
{
|
|
auto coro = []() -> Task<Task<std::string>> {
|
|
auto coro2 = []() -> Task<std::string> {
|
|
auto coro3 = []() -> Task<std::string> {
|
|
co_return std::string("Hello, World!");
|
|
};
|
|
auto coro4 = [coro3 = std::move(coro3)]() -> Task<std::string> {
|
|
auto coro5 = []() -> Task<> { co_return; };
|
|
co_await coro5();
|
|
co_return co_await coro3();
|
|
};
|
|
co_return co_await coro4();
|
|
};
|
|
|
|
co_return coro2();
|
|
};
|
|
|
|
auto task1 = coro();
|
|
auto task2 = sync_wait(task1);
|
|
std::string str = sync_wait(task2);
|
|
|
|
CHECK(str == "Hello, World!");
|
|
}
|
|
|
|
DROGON_TEST(CoroutineDestruction)
|
|
{
|
|
// Test coroutine destruction
|
|
auto destruct = []() -> Task<> {
|
|
auto awaitStruct = []() -> Task<std::shared_ptr<internal::SomeStruct>> {
|
|
co_return co_await internal::StructAwaiter();
|
|
};
|
|
|
|
auto awaitNothing = [awaitStruct]() -> Task<> {
|
|
co_await awaitStruct();
|
|
};
|
|
|
|
co_await awaitNothing();
|
|
};
|
|
sync_wait(destruct());
|
|
CHECK(internal::SomeStruct::beenDestructed == true);
|
|
} |