1 #include <sdbusplus/async.hpp> 2 3 #include <gtest/gtest.h> 4 5 struct Context : public testing::Test 6 { 7 ~Context() noexcept override = 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(stdexec::just() | 19 stdexec::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(stdexec::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(stdexec::just() | 54 stdexec::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(stdexec::just() | 66 stdexec::then([]() { throw std::logic_error("Oops"); })); 67 } 68 spawnStop(); 69 70 for (size_t i = 0; i < count; ++i) 71 { 72 EXPECT_THROW(ctx->run(), std::logic_error); 73 } 74 ctx->run(); 75 } 76 77 TEST_F(Context, SpawnDelayedTask) 78 { 79 using namespace std::literals; 80 static constexpr auto timeout = 500ms; 81 82 auto start = std::chrono::steady_clock::now(); 83 84 bool ran = false; 85 ctx->spawn(sdbusplus::async::sleep_for(*ctx, timeout) | 86 stdexec::then([&ran]() { ran = true; })); 87 88 runToStop(); 89 90 auto stop = std::chrono::steady_clock::now(); 91 92 EXPECT_TRUE(ran); 93 EXPECT_GT(stop - start, timeout); 94 EXPECT_LT(stop - start, timeout * 3); 95 } 96 97 TEST_F(Context, SpawnRecursiveTask) 98 { 99 struct _ 100 { 101 static auto one(size_t count, size_t& executed) 102 -> sdbusplus::async::task<size_t> 103 { 104 if (count) 105 { 106 ++executed; 107 co_return (co_await one(count - 1, executed)) + 1; 108 } 109 co_return co_await stdexec::just(0); 110 } 111 }; 112 113 static constexpr size_t count = 100; 114 size_t executed = 0; 115 116 ctx->spawn(_::one(count, executed) | 117 stdexec::then([=](auto result) { EXPECT_EQ(result, count); })); 118 119 runToStop(); 120 121 EXPECT_EQ(executed, count); 122 } 123 124 TEST_F(Context, DestructMatcherWithPendingAwait) 125 { 126 using namespace std::literals; 127 128 bool ran = false; 129 auto m = std::make_optional<sdbusplus::async::match>( 130 *ctx, sdbusplus::bus::match::rules::interfacesAdded( 131 "/this/is/a/bogus/path/for/SpawnMatcher")); 132 133 // Await the match completion (which will never happen). 134 ctx->spawn(m->next() | stdexec::then([&ran](...) { ran = true; })); 135 136 // Destruct the match. 137 ctx->spawn(sdbusplus::async::sleep_for(*ctx, 1ms) | 138 stdexec::then([&m](...) { m.reset(); })); 139 140 EXPECT_THROW(runToStop(), sdbusplus::exception::UnhandledStop); 141 EXPECT_NO_THROW(ctx->run()); 142 EXPECT_FALSE(ran); 143 } 144 145 TEST_F(Context, DestructMatcherWithPendingAwaitAsTask) 146 { 147 using namespace std::literals; 148 149 auto m = std::make_optional<sdbusplus::async::match>( 150 *ctx, sdbusplus::bus::match::rules::interfacesAdded( 151 "/this/is/a/bogus/path/for/SpawnMatcher")); 152 153 struct _ 154 { 155 static auto fn(decltype(m->next()) snd, bool& ran) 156 -> sdbusplus::async::task<> 157 { 158 co_await std::move(snd); 159 ran = true; 160 co_return; 161 } 162 }; 163 164 bool ran = false; 165 ctx->spawn(_::fn(m->next(), ran)); 166 ctx->spawn(sdbusplus::async::sleep_for(*ctx, 1ms) | 167 stdexec::then([&]() { m.reset(); })); 168 169 EXPECT_THROW(runToStop(), sdbusplus::exception::UnhandledStop); 170 EXPECT_NO_THROW(ctx->run()); 171 EXPECT_FALSE(ran); 172 } 173