#include #include struct Context : public testing::Test { ~Context() noexcept = 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(std::execution::just() | std::execution::then([this]() { ctx->request_stop(); })); } void runToStop() { spawnStop(); ctx->run(); } std::optional ctx{std::in_place}; }; TEST_F(Context, RunSimple) { runToStop(); } TEST_F(Context, SpawnedTask) { ctx->spawn(std::execution::just()); runToStop(); } TEST_F(Context, ReentrantRun) { runToStop(); for (int i = 0; i < 100; ++i) { ctx->run(); } } TEST_F(Context, SpawnThrowingTask) { ctx->spawn(std::execution::just() | std::execution::then([]() { throw std::logic_error("Oops"); })); 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(std::execution::just() | std::execution::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) | std::execution::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 { if (count) { ++executed; co_return (co_await one(count - 1, executed)) + 1; } co_return co_await std::execution::just(0); } }; static constexpr size_t count = 100; size_t executed = 0; ctx->spawn(_::one(count, executed) | std::execution::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( *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() | std::execution::then([&ran](...) { ran = true; })); // Destruct the match. ctx->spawn(sdbusplus::async::sleep_for(*ctx, 1ms) | std::execution::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( *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) | std::execution::then([&]() { m.reset(); })); EXPECT_THROW(runToStop(), sdbusplus::exception::UnhandledStop); EXPECT_NO_THROW(ctx->run()); EXPECT_FALSE(ran); }