1 #include <sdbusplus/async.hpp> 2 3 #include <gtest/gtest.h> 4 5 struct Context : public testing::Test 6 { 7 ~Context() noexcept = default; 8 9 void TearDown() override 10 { 11 // Destructing the context can throw, so we have to do it in 12 // the TearDown in order to make our destructor noexcept. 13 ctx.reset(); 14 } 15 16 void runToStop() 17 { 18 ctx->spawn(std::execution::just() | 19 std::execution::then([this]() { ctx->request_stop(); })); 20 ctx->run(); 21 } 22 23 std::optional<sdbusplus::async::context> ctx{std::in_place}; 24 }; 25 26 TEST_F(Context, RunSimple) 27 { 28 runToStop(); 29 } 30 31 TEST_F(Context, SpawnedTask) 32 { 33 ctx->spawn(std::execution::just()); 34 runToStop(); 35 } 36 37 TEST_F(Context, ReentrantRun) 38 { 39 runToStop(); 40 for (int i = 0; i < 100; ++i) 41 { 42 ctx->run(); 43 } 44 } 45 46 TEST_F(Context, SpawnThrowingTask) 47 { 48 ctx->spawn(std::execution::just() | 49 std::execution::then([]() { throw std::logic_error("Oops"); })); 50 51 EXPECT_THROW(runToStop(), std::logic_error); 52 ctx->run(); 53 } 54 55 TEST_F(Context, SpawnDelayedTask) 56 { 57 using namespace std::literals; 58 static constexpr auto timeout = 500ms; 59 60 auto start = std::chrono::steady_clock::now(); 61 62 bool ran = false; 63 ctx->spawn(sdbusplus::async::sleep_for(*ctx, timeout) | 64 std::execution::then([&ran]() { ran = true; })); 65 66 runToStop(); 67 68 auto stop = std::chrono::steady_clock::now(); 69 70 EXPECT_TRUE(ran); 71 EXPECT_GT(stop - start, timeout); 72 EXPECT_LT(stop - start, timeout * 2); 73 } 74 75 TEST_F(Context, SpawnRecursiveTask) 76 { 77 struct _ 78 { 79 static auto one(size_t count, size_t& executed) 80 -> sdbusplus::async::task<size_t> 81 { 82 if (count) 83 { 84 ++executed; 85 co_return (co_await one(count - 1, executed)) + 1; 86 } 87 co_return co_await std::execution::just(0); 88 } 89 }; 90 91 static constexpr size_t count = 100; 92 size_t executed = 0; 93 94 ctx->spawn(_::one(count, executed) | std::execution::then([=](auto result) { 95 EXPECT_EQ(result, count); 96 })); 97 98 runToStop(); 99 100 EXPECT_EQ(executed, count); 101 } 102 103 TEST_F(Context, DestructMatcherWithPendingAwait) 104 { 105 using namespace std::literals; 106 107 bool ran = false; 108 auto m = std::make_optional<sdbusplus::async::match>( 109 *ctx, sdbusplus::bus::match::rules::interfacesAdded( 110 "/this/is/a/bogus/path/for/SpawnMatcher")); 111 112 // Await the match completion (which will never happen). 113 ctx->spawn(m->next() | std::execution::then([&ran](...) { ran = true; })); 114 115 // Destruct the match. 116 ctx->spawn(sdbusplus::async::sleep_for(*ctx, 1ms) | 117 std::execution::then([&m](...) { m.reset(); })); 118 119 EXPECT_THROW(runToStop(), sdbusplus::exception::UnhandledStop); 120 EXPECT_NO_THROW(ctx->run()); 121 EXPECT_FALSE(ran); 122 } 123 124 TEST_F(Context, DestructMatcherWithPendingAwaitAsTask) 125 { 126 using namespace std::literals; 127 128 auto m = std::make_optional<sdbusplus::async::match>( 129 *ctx, sdbusplus::bus::match::rules::interfacesAdded( 130 "/this/is/a/bogus/path/for/SpawnMatcher")); 131 132 struct _ 133 { 134 static auto fn(decltype(m->next()) snd, bool& ran) 135 -> sdbusplus::async::task<> 136 { 137 co_await std::move(snd); 138 ran = true; 139 co_return; 140 } 141 }; 142 143 bool ran = false; 144 ctx->spawn(_::fn(m->next(), ran)); 145 ctx->spawn(sdbusplus::async::sleep_for(*ctx, 1ms) | 146 std::execution::then([&]() { m.reset(); })); 147 148 EXPECT_THROW(runToStop(), sdbusplus::exception::UnhandledStop); 149 EXPECT_NO_THROW(ctx->run()); 150 EXPECT_FALSE(ran); 151 } 152