blob: 7d7f6fa952519aac81d545d5e4c6167aa37d0583 [file] [log] [blame]
#include <sdbusplus/async.hpp>
#include <gtest/gtest.h>
struct Context : public testing::Test
{
~Context() noexcept override = default;
void TearDown() override
{
// Destructing the context can throw, so we have to do it in
// the TearDown in order to make our destructor noexcept.
ctx.reset();
}
void spawnStop()
{
ctx->spawn(stdexec::just() |
stdexec::then([this]() { ctx->request_stop(); }));
}
void runToStop()
{
spawnStop();
ctx->run();
}
std::optional<sdbusplus::async::context> ctx{std::in_place};
};
TEST_F(Context, RunSimple)
{
runToStop();
}
TEST_F(Context, SpawnedTask)
{
ctx->spawn(stdexec::just());
runToStop();
}
TEST_F(Context, ReentrantRun)
{
runToStop();
for (int i = 0; i < 100; ++i)
{
ctx->run();
}
}
TEST_F(Context, SpawnThrowingTask)
{
ctx->spawn(stdexec::just() |
stdexec::then([]() { throw std::logic_error("Oops"); }));
EXPECT_THROW(runToStop(), std::logic_error);
ctx->run();
}
TEST_F(Context, SpawnThrowingCoroutine)
{
struct _
{
static auto one() -> sdbusplus::async::task<>
{
throw std::logic_error("Oops");
co_return;
}
};
ctx->spawn(_::one());
EXPECT_THROW(runToStop(), std::logic_error);
ctx->run();
};
TEST_F(Context, SpawnManyThrowingTasks)
{
static constexpr size_t count = 100;
for (size_t i = 0; i < count; ++i)
{
ctx->spawn(stdexec::just() |
stdexec::then([]() { throw std::logic_error("Oops"); }));
}
spawnStop();
for (size_t i = 0; i < count; ++i)
{
EXPECT_THROW(ctx->run(), std::logic_error);
}
ctx->run();
}
TEST_F(Context, SpawnDelayedTask)
{
using namespace std::literals;
static constexpr auto timeout = 500ms;
auto start = std::chrono::steady_clock::now();
bool ran = false;
ctx->spawn(sdbusplus::async::sleep_for(*ctx, timeout) |
stdexec::then([&ran]() { ran = true; }));
runToStop();
auto stop = std::chrono::steady_clock::now();
EXPECT_TRUE(ran);
EXPECT_GT(stop - start, timeout);
EXPECT_LT(stop - start, timeout * 3);
}
TEST_F(Context, SpawnRecursiveTask)
{
struct _
{
static auto one(size_t count, size_t& executed)
-> sdbusplus::async::task<size_t>
{
if (count)
{
++executed;
co_return (co_await one(count - 1, executed)) + 1;
}
co_return co_await stdexec::just(0);
}
};
static constexpr size_t count = 100;
size_t executed = 0;
ctx->spawn(_::one(count, executed) |
stdexec::then([=](auto result) { EXPECT_EQ(result, count); }));
runToStop();
EXPECT_EQ(executed, count);
}
TEST_F(Context, DestructMatcherWithPendingAwait)
{
using namespace std::literals;
bool ran = false;
auto m = std::make_optional<sdbusplus::async::match>(
*ctx, sdbusplus::bus::match::rules::interfacesAdded(
"/this/is/a/bogus/path/for/SpawnMatcher"));
// Await the match completion (which will never happen).
ctx->spawn(m->next() | stdexec::then([&ran](...) { ran = true; }));
// Destruct the match.
ctx->spawn(sdbusplus::async::sleep_for(*ctx, 1ms) |
stdexec::then([&m](...) { m.reset(); }));
EXPECT_THROW(runToStop(), sdbusplus::exception::UnhandledStop);
EXPECT_NO_THROW(ctx->run());
EXPECT_FALSE(ran);
}
TEST_F(Context, DestructMatcherWithPendingAwaitAsTask)
{
using namespace std::literals;
auto m = std::make_optional<sdbusplus::async::match>(
*ctx, sdbusplus::bus::match::rules::interfacesAdded(
"/this/is/a/bogus/path/for/SpawnMatcher"));
struct _
{
static auto fn(decltype(m->next()) snd, bool& ran)
-> sdbusplus::async::task<>
{
co_await std::move(snd);
ran = true;
co_return;
}
};
bool ran = false;
ctx->spawn(_::fn(m->next(), ran));
ctx->spawn(sdbusplus::async::sleep_for(*ctx, 1ms) |
stdexec::then([&]() { m.reset(); }));
EXPECT_THROW(runToStop(), sdbusplus::exception::UnhandledStop);
EXPECT_NO_THROW(ctx->run());
EXPECT_FALSE(ran);
}