uvco/test/promise_test.cc
Kimapr 741f9184fd
Some checks failed
Actions CI + Docs / uvco (clang++) (push) Has been cancelled
Actions CI + Docs / uvco (g++-14) (push) Has been cancelled
Fix failing tests and SelectSet
additionally, add constexpr to IoVec constructor, just in case...
2026-03-20 18:42:01 +05:00

323 lines
7.7 KiB
C++

#include <coroutine>
#include <fmt/core.h>
#include <fmt/format.h>
#include <gtest/gtest.h>
#include "test_util.h"
#include "uvco/combinators.h"
#include "uvco/exception.h"
#include "uvco/loop/loop.h"
#include "uvco/promise/multipromise.h"
#include "uvco/promise/promise.h"
#include "uvco/run.h"
#include <iostream>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
using namespace uvco;
namespace {
TEST(PromiseTest, moveCtor) {
auto setup = [](const Loop &) -> Promise<void> {
Promise<int> promise1 = []() -> Promise<int> { co_return 1; }();
Promise<int> promise2 = std::move(promise1);
EXPECT_EQ(co_await promise2, 1);
};
run_loop(setup);
}
TEST(PromiseTest, moveCtor2) {
auto setup = [](const Loop &) -> Promise<void> {
Promise<int> promise1 = []() -> Promise<int> { co_return 1; }();
Promise<int> promise2 = std::move(promise1);
promise1 = std::move(promise2);
EXPECT_EQ(co_await promise1, 1);
};
run_loop(setup);
}
TEST(PromiseTest, moveCtorVoid) {
auto setup = [](const Loop &) -> Promise<void> {
Promise<void> promise1 = []() -> Promise<void> { co_return; }();
Promise<void> promise2 = std::move(promise1);
promise1 = std::move(promise2);
co_await promise1;
};
run_loop(setup);
}
TEST(PromiseTest, moveCtorVoid2) {
auto setup = [](const Loop &) -> Promise<void> {
Promise<void> promise1 = []() -> Promise<void> { co_return; }();
Promise<void> promise2 = std::move(promise1);
promise1 = []() -> Promise<void> {
co_await yield();
co_return;
}();
promise1 = std::move(promise2);
co_await promise1;
};
run_loop(setup);
}
TEST(PromiseTest, awaitTwice) {
auto setup = [](const Loop &) -> Promise<void> {
Promise<int> promise = []() -> Promise<int> {
co_await yield();
co_return 1;
}();
EXPECT_EQ(co_await promise, 1);
EXPECT_THROW({ co_await promise; }, UvcoException);
};
run_loop(setup);
}
TEST(PromiseTest, awaitConst) {
auto setup = [](const Loop &) -> Promise<void> {
const Promise<int> promise = []() -> Promise<int> {
co_await yield();
co_return 1;
}();
EXPECT_EQ(co_await promise, 1);
};
run_loop(setup);
}
TEST(PromiseTest, assignmentEquivalent) {
auto setup = [](const Loop &) -> Promise<void> {
auto coroutine = []() -> Promise<int> {
co_await yield();
co_return 1;
};
// Verify that awaiting a called coroutine and a stored promise both works.
EXPECT_EQ(co_await coroutine(), 1);
auto promise = coroutine();
EXPECT_EQ(co_await promise, 1);
};
run_loop(setup);
}
TEST(PromiseTest, awaitTwiceImmediateReturn) {
auto setup = [](const Loop &) -> Promise<void> {
Promise<int> promise = []() -> Promise<int> { co_return 1; }();
EXPECT_EQ(co_await promise, 1);
EXPECT_THROW({ co_await promise; }, UvcoException);
};
run_loop(setup);
}
struct MovableOnly {
MovableOnly() = default;
MovableOnly(MovableOnly &&other) noexcept = default;
MovableOnly &operator=(MovableOnly &&other) noexcept = default;
MovableOnly(const MovableOnly &) = delete;
MovableOnly &operator=(const MovableOnly &) = delete;
};
TEST(PromiseTest, awaitReturnsMovableOnly) {
auto setup = [](const Loop &) -> Promise<void> {
Promise<MovableOnly> promise = []() -> Promise<MovableOnly> {
co_await yield();
co_return MovableOnly{};
}();
MovableOnly value = co_await promise;
(void)value;
};
run_loop(setup);
}
TEST(PromiseTest, unwrapReturnsMovableOnly) {
auto setup = [](const Loop &) -> Promise<void> {
Promise<MovableOnly> promise = []() -> Promise<MovableOnly> {
co_await yield();
co_return MovableOnly{};
}();
co_await yield();
MovableOnly value = promise.unwrap();
(void)value;
};
run_loop(setup);
}
TEST(PromiseTest, unwrapRethrows) {
auto setup = [](const Loop &) -> Promise<void> {
Promise<int> promise = []() -> Promise<int> {
co_await yield();
throw UvcoException("test exception");
}();
try {
co_await yield();
EXPECT_TRUE(promise.ready());
promise.unwrap();
} catch (const UvcoException &e) {
EXPECT_STREQ(e.what(), "test exception");
}
co_return;
};
run_loop(setup);
}
template <typename PromiseType>
struct RvalueCoroutineFixture : public ::testing::Test {};
using RvalueCoroutineTypes = ::testing::Types<void, int>;
TYPED_TEST_SUITE(RvalueCoroutineFixture, RvalueCoroutineTypes);
// Tests that temporary values passed to a coroutine don't wreak havoc.
TYPED_TEST(RvalueCoroutineFixture, rvalueCoroutine) {
auto setup = [](const Loop &) -> Promise<void> {
// An rvalue `stringArg` is forbidden by the Coroutine constructors.
const auto coroutine = [](std::string stringArg) -> Promise<TypeParam> {
co_await yield();
[[maybe_unused]]
const std::string result = fmt::format("{}\n", stringArg);
if constexpr (std::is_same_v<TypeParam, void>) {
co_return;
} else {
co_return 1;
}
};
std::optional<Promise<TypeParam>> promise1;
std::optional<Promise<TypeParam>> promise2;
std::optional<Promise<TypeParam>> promise3;
{
std::string string{"abc"};
promise1 = coroutine(fmt::format("abc"));
promise2 = coroutine("abc");
promise3 = coroutine(std::move(string));
}
co_await promise1.value();
co_await promise2.value();
co_await promise3.value();
};
run_loop(setup);
}
struct YieldAwaiter_ {
[[nodiscard]] static bool await_ready() noexcept { return false; }
template<class T>
bool await_suspend(std::coroutine_handle<T> handle) noexcept {
Loop::enqueue(handle);
return true;
}
[[nodiscard]] int await_resume() const noexcept { return 1; }
};
Promise<int> yieldInt() { co_return (co_await YieldAwaiter_{}); }
TEST(PromiseTest, yield) {
auto setup = [](const Loop &) -> Promise<void> {
co_await yield();
co_return;
};
run_loop(setup);
}
TEST(PromiseTest, yieldInt) {
auto setup = [](const Loop &) -> Promise<void> {
BOOST_VERIFY(1 == co_await yieldInt());
co_return;
};
run_loop(setup);
}
Promise<void> testTemporaryFunction(std::string_view message) {
co_await yield();
// Ensure that all memory is accessible, checked by asan
for (char c : message) {
EXPECT_NE(c, '\0');
}
co_return;
}
TEST(PromiseTest, temporaryOk) {
auto setup = [](const Loop &) -> Promise<void> {
// Temporary promise.
co_await testTemporaryFunction(fmt::format("Hello {}", "World"));
};
run_loop(setup);
}
TEST(PromiseTest, movePromiseBetweenFunctions) {
auto coro1 = [](Promise<int> promise) -> Promise<void> {
EXPECT_EQ(co_await promise, 42);
};
auto coro2 = [&](Promise<int> promise) -> Promise<void> {
co_return co_await coro1(std::move(promise));
};
auto setup = [&](const Loop &) -> Promise<void> {
Promise<int> promise1 = []() -> Promise<int> {
co_await yield();
co_return 42;
}();
co_await coro2(std::move(promise1));
};
run_loop(setup);
}
TEST(PromiseTest, destroyWithoutResume) {
bool initialized = false;
bool ran = false;
auto setup = [&](const Loop &) -> Promise<void> {
Promise<int> promise = [&]() -> Promise<int> {
initialized = true;
co_await yield();
ran = true;
co_return 1;
}();
co_return;
};
run_loop(setup);
EXPECT_TRUE(initialized);
EXPECT_FALSE(ran);
}
TEST(PromiseTest, destroyVoidWithoutResume) {
bool initialized = false;
bool ran = false;
auto setup = [&](const Loop &) -> Promise<void> {
Promise<void> promise = [&]() -> Promise<void> {
initialized = true;
co_await yield();
ran = true;
}();
co_return;
};
run_loop(setup);
EXPECT_TRUE(initialized);
EXPECT_FALSE(ran);
}
} // namespace