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 spawnStop() 17 { 18 ctx->spawn(std::execution::just() | 19 std::execution::then([this]() { ctx->request_stop(); })); 20 } 21 22 void runToStop() 23 { 24 spawnStop(); 25 ctx->run(); 26 } 27 28 std::optional<sdbusplus::async::context> ctx{std::in_place}; 29 }; 30 31 TEST_F(Context, RunSimple) 32 { 33 runToStop(); 34 } 35 36 TEST_F(Context, SpawnedTask) 37 { 38 ctx->spawn(std::execution::just()); 39 runToStop(); 40 } 41 42 TEST_F(Context, ReentrantRun) 43 { 44 runToStop(); 45 for (int i = 0; i < 100; ++i) 46 { 47 ctx->run(); 48 } 49 } 50 51 TEST_F(Context, SpawnThrowingTask) 52 { 53 ctx->spawn(std::execution::just() | 54 std::execution::then([]() { throw std::logic_error("Oops"); })); 55 56 EXPECT_THROW(runToStop(), std::logic_error); 57 ctx->run(); 58 } 59 60 TEST_F(Context, SpawnManyThrowingTasks) 61 { 62 static constexpr size_t count = 100; 63 for (size_t i = 0; i < count; ++i) 64 { 65 ctx->spawn(std::execution::just() | std::execution::then([]() { 66 throw std::logic_error("Oops"); 67 })); 68 } 69 spawnStop(); 70 71 for (size_t i = 0; i < count; ++i) 72 { 73 EXPECT_THROW(ctx->run(), std::logic_error); 74 } 75 ctx->run(); 76 } 77 78 TEST_F(Context, SpawnDelayedTask) 79 { 80 using namespace std::literals; 81 static constexpr auto timeout = 500ms; 82 83 auto start = std::chrono::steady_clock::now(); 84 85 bool ran = false; 86 ctx->spawn(sdbusplus::async::sleep_for(*ctx, timeout) | 87 std::execution::then([&ran]() { ran = true; })); 88 89 runToStop(); 90 91 auto stop = std::chrono::steady_clock::now(); 92 93 EXPECT_TRUE(ran); 94 EXPECT_GT(stop - start, timeout); 95 EXPECT_LT(stop - start, timeout * 3); 96 } 97 98 TEST_F(Context, SpawnRecursiveTask) 99 { 100 struct _ 101 { 102 static auto one(size_t count, size_t& executed) 103 -> sdbusplus::async::task<size_t> 104 { 105 if (count) 106 { 107 ++executed; 108 co_return (co_await one(count - 1, executed)) + 1; 109 } 110 co_return co_await std::execution::just(0); 111 } 112 }; 113 114 static constexpr size_t count = 100; 115 size_t executed = 0; 116 117 ctx->spawn(_::one(count, executed) | std::execution::then([=](auto result) { 118 EXPECT_EQ(result, count); 119 })); 120 121 runToStop(); 122 123 EXPECT_EQ(executed, count); 124 } 125 126 TEST_F(Context, DestructMatcherWithPendingAwait) 127 { 128 using namespace std::literals; 129 130 bool ran = false; 131 auto m = std::make_optional<sdbusplus::async::match>( 132 *ctx, sdbusplus::bus::match::rules::interfacesAdded( 133 "/this/is/a/bogus/path/for/SpawnMatcher")); 134 135 // Await the match completion (which will never happen). 136 ctx->spawn(m->next() | std::execution::then([&ran](...) { ran = true; })); 137 138 // Destruct the match. 139 ctx->spawn(sdbusplus::async::sleep_for(*ctx, 1ms) | 140 std::execution::then([&m](...) { m.reset(); })); 141 142 EXPECT_THROW(runToStop(), sdbusplus::exception::UnhandledStop); 143 EXPECT_NO_THROW(ctx->run()); 144 EXPECT_FALSE(ran); 145 } 146 147 TEST_F(Context, DestructMatcherWithPendingAwaitAsTask) 148 { 149 using namespace std::literals; 150 151 auto m = std::make_optional<sdbusplus::async::match>( 152 *ctx, sdbusplus::bus::match::rules::interfacesAdded( 153 "/this/is/a/bogus/path/for/SpawnMatcher")); 154 155 struct _ 156 { 157 static auto fn(decltype(m->next()) snd, bool& ran) 158 -> sdbusplus::async::task<> 159 { 160 co_await std::move(snd); 161 ran = true; 162 co_return; 163 } 164 }; 165 166 bool ran = false; 167 ctx->spawn(_::fn(m->next(), ran)); 168 ctx->spawn(sdbusplus::async::sleep_for(*ctx, 1ms) | 169 std::execution::then([&]() { m.reset(); })); 170 171 EXPECT_THROW(runToStop(), sdbusplus::exception::UnhandledStop); 172 EXPECT_NO_THROW(ctx->run()); 173 EXPECT_FALSE(ran); 174 } 175