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