From 4688e8153445b1ecf960dcd44edfdc4e847b0f3f Mon Sep 17 00:00:00 2001 From: sangelovic Date: Sun, 12 Jan 2020 23:10:19 +0100 Subject: [PATCH] Emulate sync D-Bus method call with async implementation underneath to avoid poll race condition --- docs/using-sdbus-c++.md | 8 +- include/sdbus-c++/Message.h | 10 +- src/Connection.cpp | 77 +- src/Connection.h | 7 +- src/IConnection.h | 2 + src/Message.cpp | 8 + src/Proxy.cpp | 95 +- src/Proxy.h | 4 +- .../integrationtests/AdaptorAndProxy_test.cpp | 1064 +++++++++-------- tests/integrationtests/TestingAdaptor.h | 21 +- tests/integrationtests/TestingProxy.h | 2 +- tests/integrationtests/adaptor-glue.h | 6 +- tests/integrationtests/proxy-glue.h | 20 +- tests/perftests/client.cpp | 28 +- 14 files changed, 760 insertions(+), 592 deletions(-) diff --git a/docs/using-sdbus-c++.md b/docs/using-sdbus-c++.md index 0368e2f..804430c 100644 --- a/docs/using-sdbus-c++.md +++ b/docs/using-sdbus-c++.md @@ -167,13 +167,17 @@ The following diagram illustrates the major entities in sdbus-c++. ### Thread safety in sdbus-c++ -sdbus-c++ is thread-aware by design. But, in general, it's not thread-safe. At least not in all places. There are situations where sdbus-c++ provides and guarantees API-level thread safety by design. It is safe to do these operations from multiple threads at the same time: +sdbus-c++ is completely thread-aware by design. Though sdbus-c++ is not thread-safe in general, there are situations where sdbus-c++ provides and guarantees API-level thread safety by design. It is safe to do these operations (operations within the bullet points, not across them) from multiple threads at the same time: - * Making and destroying `Object`s and `Proxy`s, even on a shared connection that is already running an event loop. Under *making* here is meant a complete atomic sequence of construction, registration of method/signal/property callbacks and export of the `Object`/`Proxy` so it is ready to issue/receive messages. This sequence must be done in one thread. + * Making or destroying distinct `Object`/`Proxy` instances simultaneously (even on a shared connection that is running an event loop already, see below). Under *making* here is meant a complete sequence of construction, registration of method/signal/property callbacks and export of the `Object`/`Proxy` so it is ready to issue/receive messages. This sequence must be completely done within the context of one thread. * Creating and sending asynchronous method replies on an `Object` instance. * Creating and emitting signals on an `Object` instance. * Creating and sending method calls (both synchronously and asynchronously) on an `Proxy` instance. (But it's generally better that our threads use their own exclusive instances of proxy, to minimize shared state and contention.) +sdbus-c++ is designed such that all the above operations are thread-safe also on a connection that is running an event loop (usually in a separate thread) at that time. It's an internal thread safety. For example, a signal arrives and is processed by sdbus-c++ even loop at an appropriate `Proxy` instance, while the user is going to destroy that instance in their application thread. The user cannot explicitly control these situations (or they could, but that would be very limiting and cubersome on the API level). + +However, other combinations, that the user invokes explicitly from within more threads are NOT thread-safe in sdbus-c++ by design, and the user should make sure by their design that these cases never occur. For example, destroying an `Object` instance in one thread while emitting a signal on it in another thread is not thread-safe. In this specific case, the user should make sure in their application that all threads stop working with a specific instance before a thread proceeds with deleting an instance. + Multiple layers of sdbus-c++ API ------------------------------- diff --git a/include/sdbus-c++/Message.h b/include/sdbus-c++/Message.h index 9889425..086c924 100644 --- a/include/sdbus-c++/Message.h +++ b/include/sdbus-c++/Message.h @@ -172,15 +172,17 @@ namespace sdbus { public: MethodCall() = default; + MethodReply send(uint64_t timeout = 0) const; + MethodReply sendWithReply(uint64_t timeout = 0) const; + void sendWithAsyncReply(void* callback, void* userData, uint64_t timeout = 0) const; + MethodReply sendWithNoReply() const; + MethodReply createReply() const; MethodReply createErrorReply(const sdbus::Error& error) const; + void dontExpectReply(); bool doesntExpectReply() const; - - private: - MethodReply sendWithReply(uint64_t timeout) const; - MethodReply sendWithNoReply() const; }; class AsyncMethodCall : public Message diff --git a/src/Connection.cpp b/src/Connection.cpp index 3341275..09ea5cd 100644 --- a/src/Connection.cpp +++ b/src/Connection.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -51,7 +52,6 @@ Connection::Connection(std::unique_ptr&& interface, const BusFactory& bu Connection::Connection(std::unique_ptr&& interface, system_bus_t) : Connection(std::move(interface), [this](sd_bus** bus){ return iface_->sd_bus_open_system(bus); }) { -printf("Here: %p\n", this); } Connection::Connection(std::unique_ptr&& interface, session_bus_t) @@ -91,6 +91,10 @@ std::string Connection::getUniqueName() const void Connection::enterProcessingLoop() { + loopThreadId_ = std::this_thread::get_id(); + + std::lock_guard guard(loopMutex_); + while (true) { auto processed = processPendingRequest(); @@ -101,46 +105,14 @@ void Connection::enterProcessingLoop() if (!success) break; // Exit processing loop } + + loopThreadId_ = std::thread::id{}; } void Connection::enterProcessingLoopAsync() { if (!asyncLoopThread_.joinable()) asyncLoopThread_ = std::thread([this](){ enterProcessingLoop(); }); -// if (!asyncLoopThread2_.joinable()) -// asyncLoopThread2_ = std::thread([this](){ -// enterProcessingLoop(); -//// while (true) -//// { -//// auto bus = bus_.get(); - -//// assert(bus != nullptr); -//// assert(loopExitFd_.fd != 0); - -//// auto sdbusPollData = getProcessLoopPollData(); -//// struct pollfd fds[] = {{sdbusPollData.fd, sdbusPollData.events, 0}, {loopExitFd_.fd, POLLIN, 0}}; -//// auto fdsCount = sizeof(fds)/sizeof(fds[0]); - -//// printf("Thread 2: Going to poll %p\n", this); - -//// auto timeout = sdbusPollData.timeout_usec == (uint64_t) -1 ? (uint64_t)-1 : (sdbusPollData.timeout_usec+999)/1000; -//// auto r = poll(fds, fdsCount, timeout); - -//// printf("Thread 2: Poll woken up %p\n", this); - -//// if (r < 0 && errno == EINTR) -//// continue; - -//// SDBUS_THROW_ERROR_IF(r < 0, "Failed to wait on the bus", -errno); - -//// if (fds[1].revents & POLLIN) -//// { -//// clearExitNotification(); -//// printf("Thread 2: Exiting %p\n", this); -//// break; -//// } -//// } -// }); } void Connection::leaveProcessingLoop() @@ -327,6 +299,35 @@ SlotPtr Connection::registerSignalHandler( const std::string& objectPath return {slot, [this](void *slot){ iface_->sd_bus_slot_unref((sd_bus_slot*)slot); }}; } +MethodReply Connection::tryCallMethodSynchronously(const MethodCall& message, uint64_t timeout) +{ + auto loopThreadId = loopThreadId_.load(std::memory_order_relaxed); + + // Is the loop not yet on? => Go make synchronous call + while (loopThreadId == std::thread::id{}) + { + // Did the loop begin in the meantime? Or try_lock() failed spuriously? + if (!loopMutex_.try_lock()) + { + loopThreadId = loopThreadId_.load(std::memory_order_relaxed); + continue; + } + + // Synchronous D-Bus call + std::lock_guard guard(loopMutex_, std::adopt_lock); + return message.send(timeout); + } + + // Is the loop on and we are in the same thread? => Go for synchronous call + if (loopThreadId == std::this_thread::get_id()) + { + assert(!loopMutex_.try_lock()); + return message.send(timeout); + } + + return MethodReply{}; +} + Connection::BusPtr Connection::openBus(const BusFactory& busFactory) { sd_bus* bus{}; @@ -397,13 +398,13 @@ bool Connection::waitForNextRequest() struct pollfd fds[] = {{sdbusPollData.fd, sdbusPollData.events, 0}, {loopExitFd_.fd, POLLIN, 0}}; auto fdsCount = sizeof(fds)/sizeof(fds[0]); - printf("Thread %d: Going to poll %p\n", gettid(), this); + //printf("Thread %d: Going to poll %p\n", gettid(), this); auto timeout = sdbusPollData.timeout_usec == (uint64_t) -1 ? (uint64_t)-1 : (sdbusPollData.timeout_usec+999)/1000; auto r = poll(fds, fdsCount, timeout); //auto r = ppoll(fds, fdsCount, nullptr, nullptr); - printf("Thread %d: Poll woken up %p\n", gettid(), this); + //printf("Thread %d: Poll woken up %p\n", gettid(), this); if (r < 0 && errno == EINTR) return true; // Try again @@ -413,7 +414,7 @@ bool Connection::waitForNextRequest() if (fds[1].revents & POLLIN) { clearExitNotification(); - printf("Thread %d: Exiting %p\n", gettid(), this); + //printf("Thread %d: Exiting %p\n", gettid(), this); return false; } diff --git a/src/Connection.h b/src/Connection.h index 4f100ea..2fa5f90 100644 --- a/src/Connection.h +++ b/src/Connection.h @@ -37,6 +37,8 @@ #include #include #include +#include +#include namespace sdbus { namespace internal { @@ -103,6 +105,8 @@ namespace sdbus { namespace internal { , sd_bus_message_handler_t callback , void* userData ) override; + MethodReply tryCallMethodSynchronously(const MethodCall& message, uint64_t timeout) override; + private: using BusFactory = std::function; using BusPtr = std::unique_ptr>; @@ -130,7 +134,8 @@ namespace sdbus { namespace internal { std::unique_ptr iface_; BusPtr bus_; std::thread asyncLoopThread_; - std::thread asyncLoopThread2_; + std::atomic loopThreadId_; + std::mutex loopMutex_; LoopExitEventFd loopExitFd_; }; diff --git a/src/IConnection.h b/src/IConnection.h index 07b74b3..5245262 100644 --- a/src/IConnection.h +++ b/src/IConnection.h @@ -91,6 +91,8 @@ namespace internal { virtual void enterProcessingLoopAsync() = 0; virtual void leaveProcessingLoop() = 0; + + virtual MethodReply tryCallMethodSynchronously(const MethodCall& message, uint64_t timeout) = 0; }; } diff --git a/src/Message.cpp b/src/Message.cpp index 8ebefa2..aebe570 100644 --- a/src/Message.cpp +++ b/src/Message.cpp @@ -652,6 +652,14 @@ MethodReply MethodCall::sendWithReply(uint64_t timeout) const return Factory::create(sdbusReply, sdbus_, adopt_message); } +// TODO: Consider merging MethodCall with AsyncMethodCall (provide owning boolean parameter for no Slot), +// return sendWithReply and sendWithNoReply back as private methods. +void MethodCall::sendWithAsyncReply(void* callback, void* userData, uint64_t timeout) const +{ + auto r = sdbus_->sd_bus_call_async(nullptr, nullptr, (sd_bus_message*)msg_, (sd_bus_message_handler_t)callback, userData, timeout); + SDBUS_THROW_ERROR_IF(r < 0, "Failed to call method with asynchronous reply", -r); +} + MethodReply MethodCall::sendWithNoReply() const { auto r = sdbus_->sd_bus_send(nullptr, (sd_bus_message*)msg_, nullptr); diff --git a/src/Proxy.cpp b/src/Proxy.cpp index e396f2f..17a84c8 100644 --- a/src/Proxy.cpp +++ b/src/Proxy.cpp @@ -26,6 +26,7 @@ #include "Proxy.h" #include "IConnection.h" +#include "ISdBus.h" #include "MessageUtils.h" #include "sdbus-c++/Message.h" #include "sdbus-c++/IConnection.h" @@ -35,6 +36,12 @@ #include #include #include +#include +#include + +#include +#include +#define gettid() syscall(SYS_gettid) namespace sdbus { namespace internal { @@ -71,9 +78,35 @@ AsyncMethodCall Proxy::createAsyncMethodCall(const std::string& interfaceName, c MethodReply Proxy::callMethod(const MethodCall& message, uint64_t timeout) { + // Sending method call synchronously is the only operation that blocks, waiting for the method + // reply message among the incoming message on the sd-bus connection socket. But typically there + // already is somebody that generally handles incoming D-Bus messages -- the connection event loop + // running typically in its own thread. We have to avoid polling on socket from several threads. + // So we have to branch here: either we are within the context of the event loop thread, then we + // can send the message simply, and blockingly, via sd_bus_call. Or we are in another thread, then + // we can perform the send operation of the method call message from here (because that is thread- + // safe like all other sd-bus API accesses), but the incoming reply we have to get through the event + // loop thread, because this should be the only rightful listener on the sd-bus connection socket. + // So, technically, we use async means to wait here for reply received by the event loop thread. + SDBUS_THROW_ERROR_IF(!message.isValid(), "Invalid method call message provided", EINVAL); return message.send(timeout); + + /* + // If we don't need to wait for any reply, we can send the message now irrespective of the context + if (message.doesntExpectReply()) + return message.sendWithNoReply(); + + // If we are in the context of event loop thread, we can send the D-Bus call synchronously + // and wait blockingly for the reply, because we are the exclusive listeners on the socket + auto reply = connection_->tryCallMethodSynchronously(message, timeout); + if (reply.isValid()) + return reply; + + // Otherwise we send the call asynchronously and do blocking wait for the reply from the event loop thread + return callMethodWithAsyncReplyBlocking(message, timeout); + */ } void Proxy::callMethod(const AsyncMethodCall& message, async_reply_handler asyncReplyCallback, uint64_t timeout) @@ -88,6 +121,35 @@ void Proxy::callMethod(const AsyncMethodCall& message, async_reply_handler async pendingAsyncCalls_.addCall(callData->slot.get(), std::move(callData)); } +MethodReply Proxy::callMethodWithAsyncReplyBlocking(const MethodCall& message, uint64_t timeout) +{ + // TODO: use thread_local data exchange facility (OPTIMIZE) + std::promise result; + auto future = result.get_future(); + + auto callback = (void*)&Proxy::sdbus_quasi_sync_reply_handler; + auto data = std::make_pair(std::ref(result), std::ref(connection_->getSdBusInterface())); + message.sendWithAsyncReply(callback, &data, timeout); + + //printf("Thread %d: Proxy going to wait on future\n", gettid()); + MethodReply r = future.get(); + //printf("Thread %d: Proxy woken up on future\n", gettid()); + return r; + + // // TODO: Switch to thread_local once we have re-usable thread_local data exchange facility + // /*thread_local*/ async_reply_handler asyncReplyCallback = [&result](MethodReply& reply, const Error* error) + // { + // if (error == nullptr) + // result.set_value(std::move(reply)); + // else + // result.set_exception(std::make_exception_ptr(error)); + // }; + + // auto callback = (void*)&Proxy::sdbus_async_reply_handler; + // AsyncCalls::CallData callData{*this, std::move(asyncReplyCallback), {}}; + // message.sendWithAsyncReply((void*)&Proxy::sdbus_async_reply_handler, &data, timeout); +} + void Proxy::registerSignalHandler( const std::string& interfaceName , const std::string& signalName , signal_handler signalHandler ) @@ -122,7 +184,7 @@ void Proxy::registerSignalHandlers(sdbus::internal::IConnection& connection) slot = connection.registerSignalHandler( objectPath_ , interfaceName , signalName - , &Proxy::sdbus_signal_callback + , &Proxy::sdbus_signal_handler , this ); } } @@ -134,6 +196,7 @@ void Proxy::unregister() interfaces_.clear(); } +// Handler for D-Bus method replies of fully asynchronous D-Bus method calls int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) { auto* asyncCallData = static_cast(userData); @@ -159,7 +222,35 @@ int Proxy::sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userDat return 1; } -int Proxy::sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) +// Handler for D-Bus method replies of synchronous D-Bus method calls done out of event loop thread context +int Proxy::sdbus_quasi_sync_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) +{ + //printf("Thread %d: Proxy::sdbus_quasi_sync_reply_handler 1\n", gettid()); + + assert(userData != nullptr); + auto* data = static_cast&, ISdBus&>*>(userData); + auto& promise = data->first; + auto& sdBus = data->second; + + auto message = Message::Factory::create(sdbusMessage, &sdBus); + + const auto* error = sd_bus_message_get_error(sdbusMessage); + if (error == nullptr) + { + //printf("Thread %d: Proxy::sdbus_quasi_sync_reply_handler 2\n", gettid()); + promise.set_value(std::move(message)); + } + else + { + sdbus::Error exception(error->name, error->message); + promise.set_exception(std::make_exception_ptr(exception)); + } + + return 1; +} + +// Handler for signals coming from the D-Bus object +int Proxy::sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error */*retError*/) { auto* proxy = static_cast(userData); assert(proxy != nullptr); diff --git a/src/Proxy.h b/src/Proxy.h index d211db6..659d264 100644 --- a/src/Proxy.h +++ b/src/Proxy.h @@ -62,9 +62,11 @@ namespace internal { void unregister() override; private: + MethodReply callMethodWithAsyncReplyBlocking(const MethodCall& message, uint64_t timeout); void registerSignalHandlers(sdbus::internal::IConnection& connection); static int sdbus_async_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); - static int sdbus_signal_callback(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); + static int sdbus_quasi_sync_reply_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); + static int sdbus_signal_handler(sd_bus_message *sdbusMessage, void *userData, sd_bus_error *retError); private: std::unique_ptr< sdbus::internal::IConnection diff --git a/tests/integrationtests/AdaptorAndProxy_test.cpp b/tests/integrationtests/AdaptorAndProxy_test.cpp index 0c52e74..476f9c5 100644 --- a/tests/integrationtests/AdaptorAndProxy_test.cpp +++ b/tests/integrationtests/AdaptorAndProxy_test.cpp @@ -48,6 +48,7 @@ using ::testing::Eq; using ::testing::DoubleEq; using ::testing::Gt; +using ::testing::AnyOf; using ::testing::ElementsAre; using ::testing::SizeIs; using namespace std::chrono_literals; @@ -89,9 +90,9 @@ private: void SetUp() override { m_adaptor = std::make_unique(*s_connection); - printf("PROXY BEGIN!\n"); + //printf("PROXY BEGIN!\n"); m_proxy = std::make_unique(INTERFACE_NAME, OBJECT_PATH); - printf("PROXY END!\n"); + //printf("PROXY END!\n"); std::this_thread::sleep_for(50ms); // Give time for the proxy to start listening to signals } @@ -139,522 +140,549 @@ TEST_F(SdbusTestObject, CallsEmptyMethodSuccesfully) ASSERT_NO_THROW(m_proxy->noArgNoReturn()); } -TEST_F(SdbusTestObject, RequestsSignalEmission) +TEST_F(SdbusTestObject, CallsMethodsWithBaseTypesSuccesfully) { - printf("BEGIN !!!\n"); - ASSERT_THAT(m_proxy->doSignalEmission(), Gt(0)); - printf("END !!!\n"); - std::this_thread::sleep_for(1s); -// printf("\n"); -// m_adaptor->emitSimpleSignal(); -// std::this_thread::sleep_for(1s); + auto resInt = m_proxy->getInt(); + ASSERT_THAT(resInt, Eq(INT32_VALUE)); + + auto multiplyRes = m_proxy->multiply(INT64_VALUE, DOUBLE_VALUE); + ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE)); } -//TEST_F(SdbusTestObject, CallsMethodsWithBaseTypesSuccesfully) -//{ -// auto resInt = m_proxy->getInt(); -// ASSERT_THAT(resInt, Eq(INT32_VALUE)); - -// auto multiplyRes = m_proxy->multiply(INT64_VALUE, DOUBLE_VALUE); -// ASSERT_THAT(multiplyRes, Eq(INT64_VALUE * DOUBLE_VALUE)); -//} - -//TEST_F(SdbusTestObject, CallsMethodsWithTuplesSuccesfully) -//{ -// auto resTuple = m_proxy->getTuple(); -// ASSERT_THAT(std::get<0>(resTuple), Eq(UINT32_VALUE)); -// ASSERT_THAT(std::get<1>(resTuple), Eq(STRING_VALUE)); -//} - -//TEST_F(SdbusTestObject, CallsMethodsWithStructSuccesfully) -//{ -// sdbus::Struct> a{}; -// auto vectorRes = m_proxy->getInts16FromStruct(a); -// ASSERT_THAT(vectorRes, Eq(std::vector{0})); // because second item is by default initialized to 0 - - -// sdbus::Struct> b{ -// UINT8_VALUE, INT16_VALUE, DOUBLE_VALUE, STRING_VALUE, {INT16_VALUE, -INT16_VALUE} -// }; -// vectorRes = m_proxy->getInts16FromStruct(b); -// ASSERT_THAT(vectorRes, Eq(std::vector{INT16_VALUE, INT16_VALUE, -INT16_VALUE})); -//} - -//TEST_F(SdbusTestObject, CallsMethodWithVariantSuccesfully) -//{ -// sdbus::Variant v{DOUBLE_VALUE}; -// auto variantRes = m_proxy->processVariant(v); -// ASSERT_THAT(variantRes.get(), Eq(static_cast(DOUBLE_VALUE))); -//} - -//TEST_F(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully) -//{ -// std::vector x{-2, 0, 2}; -// sdbus::Struct y{false, true}; -// auto mapOfVariants = m_proxy->getMapOfVariants(x, y); -// decltype(mapOfVariants) res{{-2, false}, {0, false}, {2, true}}; - -// ASSERT_THAT(mapOfVariants[-2].get(), Eq(res[-2].get())); -// ASSERT_THAT(mapOfVariants[0].get(), Eq(res[0].get())); -// ASSERT_THAT(mapOfVariants[2].get(), Eq(res[2].get())); -//} - -//TEST_F(SdbusTestObject, CallsMethodWithStructInStructSuccesfully) -//{ -// auto val = m_proxy->getStructInStruct(); -// ASSERT_THAT(val.get<0>(), Eq(STRING_VALUE)); -// ASSERT_THAT(std::get<0>(std::get<1>(val))[INT32_VALUE], Eq(INT32_VALUE)); -//} - -//TEST_F(SdbusTestObject, CallsMethodWithTwoStructsSuccesfully) -//{ -// auto val = m_proxy->sumStructItems({1, 2}, {3, 4}); -// ASSERT_THAT(val, Eq(1 + 2 + 3 + 4)); -//} - -//TEST_F(SdbusTestObject, CallsMethodWithTwoVectorsSuccesfully) -//{ -// auto val = m_proxy->sumVectorItems({1, 7}, {2, 3}); -// ASSERT_THAT(val, Eq(1 + 7 + 2 + 3)); -//} - -//TEST_F(SdbusTestObject, CallsMethodWithSignatureSuccesfully) -//{ -// auto resSignature = m_proxy->getSignature(); -// ASSERT_THAT(resSignature, Eq(SIGNATURE_VALUE)); -//} - -//TEST_F(SdbusTestObject, CallsMethodWithObjectPathSuccesfully) -//{ -// auto resObjectPath = m_proxy->getObjectPath(); -// ASSERT_THAT(resObjectPath, Eq(OBJECT_PATH_VALUE)); -//} - -//TEST_F(SdbusTestObject, CallsMethodWithUnixFdSuccesfully) -//{ -// auto resUnixFd = m_proxy->getUnixFd(); -// ASSERT_THAT(resUnixFd.get(), Gt(UNIX_FD_VALUE)); -//} - -//TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully) -//{ -// auto resComplex = m_proxy->getComplex(); -// ASSERT_THAT(resComplex.count(0), Eq(1)); -//} - -//TEST_F(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag) -//{ -// m_proxy->multiplyWithNoReply(INT64_VALUE, DOUBLE_VALUE); - -// ASSERT_TRUE(waitUntil(m_adaptor->m_wasMultiplyCalled)); -// ASSERT_THAT(m_adaptor->m_multiplyResult, Eq(INT64_VALUE * DOUBLE_VALUE)); -//} - -//TEST_F(SdbusTestObject, CallsMethodWithCustomTimeoutSuccessfully) -//{ -// auto res = m_proxy->doOperationWith500msTimeout(20); // The operation will take 20ms, but the timeout is 500ms, so we are fine -// ASSERT_THAT(res, Eq(20)); -//} - -//TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut) -//{ -// try -// { -// m_proxy->doOperationWith500msTimeout(1000); // The operation will take 1s, but the timeout is 500ms, so we should time out -// FAIL() << "Expected sdbus::Error exception"; -// } -// catch (const sdbus::Error& e) -// { -// ASSERT_THAT(e.getName(), Eq("org.freedesktop.DBus.Error.Timeout")); -// ASSERT_THAT(e.getMessage(), Eq("Connection timed out")); -// } -// catch(...) -// { -// FAIL() << "Expected sdbus::Error exception"; -// } -//} - -//TEST_F(SdbusTestObject, CallsMethodThatThrowsError) -//{ -// try -// { -// m_proxy->throwError(); -// FAIL() << "Expected sdbus::Error exception"; -// } -// catch (const sdbus::Error& e) -// { -// ASSERT_THAT(e.getName(), Eq("org.freedesktop.DBus.Error.AccessDenied")); -// ASSERT_THAT(e.getMessage(), Eq("A test error occurred (Operation not permitted)")); -// } -// catch(...) -// { -// FAIL() << "Expected sdbus::Error exception"; -// } -//} - -//TEST_F(SdbusTestObject, CallsErrorThrowingMethodWithDontExpectReplySet) -//{ -// ASSERT_NO_THROW(m_proxy->throwErrorWithNoReply()); - -// ASSERT_TRUE(waitUntil(m_adaptor->m_wasThrowErrorCalled)); -//} - -//TEST_F(SdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously) -//{ -// // Yeah, this is kinda timing-dependent test, but times should be safe... -// std::mutex mtx; -// std::vector results; -// std::atomic invoke{}; -// std::atomic startedCount{}; -// auto call = [&](uint32_t param) -// { -// TestingProxy proxy{INTERFACE_NAME, OBJECT_PATH}; -// ++startedCount; -// while (!invoke) ; -// auto result = proxy.doOperationAsync(param); -// std::lock_guard guard(mtx); -// results.push_back(result); -// }; - -// std::thread invocations[]{std::thread{call, 1500}, std::thread{call, 1000}, std::thread{call, 500}}; -// while (startedCount != 3) ; -// invoke = true; -// std::for_each(std::begin(invocations), std::end(invocations), [](auto& t){ t.join(); }); - -// ASSERT_THAT(results, ElementsAre(500, 1000, 1500)); -//} - -//TEST_F(SdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods) -//{ -// std::atomic resultCount{}; -// std::atomic invoke{}; -// std::atomic startedCount{}; -// auto call = [&]() -// { -// TestingProxy proxy{INTERFACE_NAME, OBJECT_PATH}; -// ++startedCount; -// while (!invoke) ; - -// size_t localResultCount{}; -// for (size_t i = 0; i < 500; ++i) -// { -// auto result = proxy.doOperationAsync(i % 2); -// if (result == (i % 2)) // Correct return value? -// localResultCount++; -// } - -// resultCount += localResultCount; -// }; - -// std::thread invocations[]{std::thread{call}, std::thread{call}, std::thread{call}}; -// while (startedCount != 3) ; -// invoke = true; -// std::for_each(std::begin(invocations), std::end(invocations), [](auto& t){ t.join(); }); - -// ASSERT_THAT(resultCount, Eq(1500)); -//} - -//TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSide) -//{ -// std::promise promise; -// auto future = promise.get_future(); -// m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) -// { -// if (err == nullptr) -// promise.set_value(res); -// else -// promise.set_exception(std::make_exception_ptr(*err)); -// }); - -// m_proxy->doOperationClientSideAsync(100); - -// ASSERT_THAT(future.get(), Eq(100)); -//} - -//TEST_F(SdbusTestObject, InvokesErroneousMethodAsynchronouslyOnClientSide) -//{ -// std::promise promise; -// auto future = promise.get_future(); -// m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) -// { -// if (err == nullptr) -// promise.set_value(res); -// else -// promise.set_exception(std::make_exception_ptr(*err)); -// }); - -// m_proxy->doErroneousOperationClientSideAsync(); - -// ASSERT_THROW(future.get(), sdbus::Error); -//} - -//TEST_F(SdbusTestObject, FailsCallingNonexistentMethod) -//{ -// ASSERT_THROW(m_proxy->callNonexistentMethod(), sdbus::Error); -//} - -//TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentInterface) -//{ -// ASSERT_THROW(m_proxy->callMethodOnNonexistentInterface(), sdbus::Error); -//} - -//TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentDestination) -//{ -// TestingProxy proxy("sdbuscpp.destination.that.does.not.exist", OBJECT_PATH); -// ASSERT_THROW(proxy.getInt(), sdbus::Error); -//} - -//TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentObject) -//{ -// TestingProxy proxy(INTERFACE_NAME, "/sdbuscpp/path/that/does/not/exist"); -// ASSERT_THROW(proxy.getInt(), sdbus::Error); -//} - -//#if LIBSYSTEMD_VERSION>=240 -//TEST_F(SdbusTestObject, CanSetGeneralMethodTimeoutWithLibsystemdVersionGreaterThan239) -//{ -// s_connection->setMethodCallTimeout(5000000); -// ASSERT_THAT(s_connection->getMethodCallTimeout(), Eq(5000000)); -//} -//#else -//TEST_F(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessThan240) -//{ -// ASSERT_THROW(s_connection->setMethodCallTimeout(5000000), sdbus::Error); -// ASSERT_THROW(s_connection->getMethodCallTimeout(), sdbus::Error); -//} -//#endif - -//// Signals - -//TEST_F(SdbusTestObject, EmitsSimpleSignalSuccesfully) -//{ -// m_adaptor->emitSimpleSignal(); - -// ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal)); -//} - -//TEST_F(SdbusTestObject, EmitsSignalWithMapSuccesfully) -//{ -// m_adaptor->emitSignalWithMap({{0, "zero"}, {1, "one"}}); - -// ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithMap)); -// ASSERT_THAT(m_proxy->m_mapFromSignal[0], Eq("zero")); -// ASSERT_THAT(m_proxy->m_mapFromSignal[1], Eq("one")); -//} - -//TEST_F(SdbusTestObject, EmitsSignalWithVariantSuccesfully) -//{ -// double d = 3.14; -// m_adaptor->emitSignalWithVariant(d); - -// ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithVariant)); -// ASSERT_THAT(m_proxy->m_variantFromSignal, DoubleEq(d)); -//} - -//TEST_F(SdbusTestObject, EmitsSignalWithoutRegistrationSuccesfully) -//{ -// m_adaptor->emitSignalWithoutRegistration({"platform", {"av"}}); - -// ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithSignature)); -// ASSERT_THAT(m_proxy->m_signatureFromSignal["platform"], Eq("av")); -//} - -//// Properties - -//TEST_F(SdbusTestObject, ReadsReadOnlyPropertySuccesfully) -//{ -// ASSERT_THAT(m_proxy->state(), Eq(DEFAULT_STATE_VALUE)); -//} - -//TEST_F(SdbusTestObject, FailsWritingToReadOnlyProperty) -//{ -// ASSERT_THROW(m_proxy->state("new_value"), sdbus::Error); -//} - -//TEST_F(SdbusTestObject, WritesAndReadsReadWritePropertySuccesfully) -//{ -// uint32_t newActionValue = 5678; - -// m_proxy->action(newActionValue); - -// ASSERT_THAT(m_proxy->action(), Eq(newActionValue)); -//} - -//// Standard D-Bus interfaces - -//TEST_F(SdbusTestObject, PingsViaPeerInterface) -//{ -// ASSERT_NO_THROW(m_proxy->Ping()); -//} - -//TEST_F(SdbusTestObject, AnswersMachineUuidViaPeerInterface) -//{ -// // If /etc/machine-id does not exist in your system (which is very likely because you have -// // a non-systemd Linux), org.freedesktop.DBus.Peer.GetMachineId() will not work. To solve -// // this, you can create /etc/machine-id yourself as symlink to /var/lib/dbus/machine-id, -// // and then org.freedesktop.DBus.Peer.GetMachineId() will start to work. -// if (::access("/etc/machine-id", F_OK) == -1) -// GTEST_SKIP() << "/etc/machine-id file does not exist, GetMachineId() will not work"; - -// ASSERT_NO_THROW(m_proxy->GetMachineId()); -//} - -//TEST_F(SdbusTestObject, AnswersXmlApiDescriptionViaIntrospectableInterface) -//{ -// ASSERT_THAT(m_proxy->Introspect(), Eq(m_adaptor->getExpectedXmlApiDescription())); -//} - -//TEST_F(SdbusTestObject, GetsPropertyViaPropertiesInterface) -//{ -// ASSERT_THAT(m_proxy->Get(INTERFACE_NAME, "state").get(), Eq(DEFAULT_STATE_VALUE)); -//} - -//TEST_F(SdbusTestObject, SetsPropertyViaPropertiesInterface) -//{ -// uint32_t newActionValue = 2345; - -// m_proxy->Set(INTERFACE_NAME, "action", newActionValue); - -// ASSERT_THAT(m_proxy->action(), Eq(newActionValue)); -//} - -//TEST_F(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface) -//{ -// const auto properties = m_proxy->GetAll(INTERFACE_NAME); - -// ASSERT_THAT(properties, SizeIs(3)); -// EXPECT_THAT(properties.at("state").get(), Eq(DEFAULT_STATE_VALUE)); -// EXPECT_THAT(properties.at("action").get(), Eq(DEFAULT_ACTION_VALUE)); -// EXPECT_THAT(properties.at("blocking").get(), Eq(DEFAULT_BLOCKING_VALUE)); -//} - -//TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties) -//{ -// std::atomic signalReceived{false}; -// m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName -// , const std::map& changedProperties -// , const std::vector& /*invalidatedProperties*/ ) -// { -// EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); -// EXPECT_THAT(changedProperties, SizeIs(1)); -// EXPECT_THAT(changedProperties.at("blocking").get(), Eq(!DEFAULT_BLOCKING_VALUE)); -// signalReceived = true; -// }; - -// m_proxy->blocking(!DEFAULT_BLOCKING_VALUE); -// m_proxy->action(DEFAULT_ACTION_VALUE*2); -// m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME, {"blocking"}); - -// ASSERT_TRUE(waitUntil(signalReceived)); -//} - -//TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForAllProperties) -//{ -// std::atomic signalReceived{false}; -// m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName -// , const std::map& changedProperties -// , const std::vector& invalidatedProperties ) -// { -// EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); -// EXPECT_THAT(changedProperties, SizeIs(1)); -// EXPECT_THAT(changedProperties.at("blocking").get(), Eq(DEFAULT_BLOCKING_VALUE)); -// ASSERT_THAT(invalidatedProperties, SizeIs(1)); -// EXPECT_THAT(invalidatedProperties[0], Eq("action")); -// signalReceived = true; -// }; - -// m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME); - -// ASSERT_TRUE(waitUntil(signalReceived)); -//} - -//TEST_F(SdbusTestObject, GetsZeroManagedObjectsIfHasNoSubPathObjects) -//{ -// const auto objectsInterfacesAndProperties = m_proxy->GetManagedObjects(); - -// ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(0)); -//} - -//TEST_F(SdbusTestObject, GetsManagedObjectsSuccessfully) -//{ -// auto subObject1 = sdbus::createObject(*s_connection, "/sub/path1"); -// subObject1->registerProperty("aProperty1").onInterface("org.sdbuscpp.integrationtests.iface1").withGetter([]{return uint8_t{123};}); -// subObject1->finishRegistration(); -// auto subObject2 = sdbus::createObject(*s_connection, "/sub/path2"); -// subObject2->registerProperty("aProperty2").onInterface("org.sdbuscpp.integrationtests.iface2").withGetter([]{return "hi";}); -// subObject2->finishRegistration(); - -// const auto objectsInterfacesAndProperties = m_proxy->GetManagedObjects(); - -// ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(2)); -// EXPECT_THAT(objectsInterfacesAndProperties.at("/sub/path1").at("org.sdbuscpp.integrationtests.iface1").at("aProperty1").get(), Eq(123)); -// EXPECT_THAT(objectsInterfacesAndProperties.at("/sub/path2").at("org.sdbuscpp.integrationtests.iface2").at("aProperty2").get(), Eq("hi")); -//} - -//TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForSelectedObjectInterfaces) -//{ -// std::atomic signalReceived{false}; -// m_proxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath -// , const std::map>& interfacesAndProperties ) -// { -// EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); -// EXPECT_THAT(interfacesAndProperties, SizeIs(1)); -// EXPECT_THAT(interfacesAndProperties.count(INTERFACE_NAME), Eq(1)); -// EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3)); -// signalReceived = true; -// }; - -// m_adaptor->emitInterfacesAddedSignal({INTERFACE_NAME}); - -// ASSERT_TRUE(waitUntil(signalReceived)); -//} - -//TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForAllObjectInterfaces) -//{ -// std::atomic signalReceived{false}; -// m_proxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath -// , const std::map>& interfacesAndProperties ) -// { -// EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); -// EXPECT_THAT(interfacesAndProperties, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces -// EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3)); // 3 properties under INTERFACE_NAME -// signalReceived = true; -// }; - -// m_adaptor->emitInterfacesAddedSignal(); - -// ASSERT_TRUE(waitUntil(signalReceived)); -//} - -//TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForSelectedObjectInterfaces) -//{ -// std::atomic signalReceived{false}; -// m_proxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath -// , const std::vector& interfaces ) -// { -// EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); -// ASSERT_THAT(interfaces, SizeIs(1)); -// EXPECT_THAT(interfaces[0], Eq(INTERFACE_NAME)); -// signalReceived = true; -// }; - -// m_adaptor->emitInterfacesRemovedSignal({INTERFACE_NAME}); - -// ASSERT_TRUE(waitUntil(signalReceived)); -//} - -//TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForAllObjectInterfaces) -//{ -// std::atomic signalReceived{false}; -// m_proxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath -// , const std::vector& interfaces ) -// { -// EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); -// ASSERT_THAT(interfaces, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces -// signalReceived = true; -// }; - -// m_adaptor->emitInterfacesRemovedSignal(); - -// ASSERT_TRUE(waitUntil(signalReceived)); -//} +TEST_F(SdbusTestObject, CallsMethodsWithTuplesSuccesfully) +{ + auto resTuple = m_proxy->getTuple(); + ASSERT_THAT(std::get<0>(resTuple), Eq(UINT32_VALUE)); + ASSERT_THAT(std::get<1>(resTuple), Eq(STRING_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodsWithStructSuccesfully) +{ + sdbus::Struct> a{}; + auto vectorRes = m_proxy->getInts16FromStruct(a); + ASSERT_THAT(vectorRes, Eq(std::vector{0})); // because second item is by default initialized to 0 + + + sdbus::Struct> b{ + UINT8_VALUE, INT16_VALUE, DOUBLE_VALUE, STRING_VALUE, {INT16_VALUE, -INT16_VALUE} + }; + vectorRes = m_proxy->getInts16FromStruct(b); + ASSERT_THAT(vectorRes, Eq(std::vector{INT16_VALUE, INT16_VALUE, -INT16_VALUE})); +} + +TEST_F(SdbusTestObject, CallsMethodWithVariantSuccesfully) +{ + sdbus::Variant v{DOUBLE_VALUE}; + auto variantRes = m_proxy->processVariant(v); + ASSERT_THAT(variantRes.get(), Eq(static_cast(DOUBLE_VALUE))); +} + +TEST_F(SdbusTestObject, CallsMethodWithStructVariantsAndGetMapSuccesfully) +{ + std::vector x{-2, 0, 2}; + sdbus::Struct y{false, true}; + auto mapOfVariants = m_proxy->getMapOfVariants(x, y); + decltype(mapOfVariants) res{{-2, false}, {0, false}, {2, true}}; + + ASSERT_THAT(mapOfVariants[-2].get(), Eq(res[-2].get())); + ASSERT_THAT(mapOfVariants[0].get(), Eq(res[0].get())); + ASSERT_THAT(mapOfVariants[2].get(), Eq(res[2].get())); +} + +TEST_F(SdbusTestObject, CallsMethodWithStructInStructSuccesfully) +{ + auto val = m_proxy->getStructInStruct(); + ASSERT_THAT(val.get<0>(), Eq(STRING_VALUE)); + ASSERT_THAT(std::get<0>(std::get<1>(val))[INT32_VALUE], Eq(INT32_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodWithTwoStructsSuccesfully) +{ + auto val = m_proxy->sumStructItems({1, 2}, {3, 4}); + ASSERT_THAT(val, Eq(1 + 2 + 3 + 4)); +} + +TEST_F(SdbusTestObject, CallsMethodWithTwoVectorsSuccesfully) +{ + auto val = m_proxy->sumVectorItems({1, 7}, {2, 3}); + ASSERT_THAT(val, Eq(1 + 7 + 2 + 3)); +} + +TEST_F(SdbusTestObject, CallsMethodWithSignatureSuccesfully) +{ + auto resSignature = m_proxy->getSignature(); + ASSERT_THAT(resSignature, Eq(SIGNATURE_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodWithObjectPathSuccesfully) +{ + auto resObjectPath = m_proxy->getObjectPath(); + ASSERT_THAT(resObjectPath, Eq(OBJECT_PATH_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodWithUnixFdSuccesfully) +{ + auto resUnixFd = m_proxy->getUnixFd(); + ASSERT_THAT(resUnixFd.get(), Gt(UNIX_FD_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodWithComplexTypeSuccesfully) +{ + auto resComplex = m_proxy->getComplex(); + ASSERT_THAT(resComplex.count(0), Eq(1)); +} + +TEST_F(SdbusTestObject, CallsMultiplyMethodWithNoReplyFlag) +{ + m_proxy->multiplyWithNoReply(INT64_VALUE, DOUBLE_VALUE); + + ASSERT_TRUE(waitUntil(m_adaptor->m_wasMultiplyCalled)); + ASSERT_THAT(m_adaptor->m_multiplyResult, Eq(INT64_VALUE * DOUBLE_VALUE)); +} + +TEST_F(SdbusTestObject, CallsMethodWithCustomTimeoutSuccessfully) +{ + auto res = m_proxy->doOperationWith500msTimeout(20); // The operation will take 20ms, but the timeout is 500ms, so we are fine + ASSERT_THAT(res, Eq(20)); +} + +TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenMethodTimesOut) +{ + try + { + m_proxy->doOperationWith500msTimeout(1000); // The operation will take 1s, but the timeout is 500ms, so we should time out + FAIL() << "Expected sdbus::Error exception"; + } + catch (const sdbus::Error& e) + { + ASSERT_THAT(e.getName(), AnyOf("org.freedesktop.DBus.Error.Timeout", "org.freedesktop.DBus.Error.NoReply")); + ASSERT_THAT(e.getMessage(), AnyOf("Connection timed out", "Method call timed out")); + } + catch(...) + { + FAIL() << "Expected sdbus::Error exception"; + } +} + +TEST_F(SdbusTestObject, ThrowsTimeoutErrorWhenClientSideAsyncMethodTimesOut) +{ + try + { + std::promise promise; + auto future = promise.get_future(); + m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) + { + if (err == nullptr) + promise.set_value(res); + else + promise.set_exception(std::make_exception_ptr(*err)); + }); + + m_proxy->doOperationClientSideAsyncWith500msTimeout(1000); // The operation will take 1s, but the timeout is 500ms, so we should time out + future.get(), Eq(100); + + FAIL() << "Expected sdbus::Error exception"; + } + catch (const sdbus::Error& e) + { + ASSERT_THAT(e.getName(), AnyOf("org.freedesktop.DBus.Error.Timeout", "org.freedesktop.DBus.Error.NoReply")); + ASSERT_THAT(e.getMessage(), AnyOf("Connection timed out", "Method call timed out")); + } + catch(...) + { + FAIL() << "Expected sdbus::Error exception"; + } +} + +TEST_F(SdbusTestObject, CallsMethodThatThrowsError) +{ + try + { + m_proxy->throwError(); + FAIL() << "Expected sdbus::Error exception"; + } + catch (const sdbus::Error& e) + { + ASSERT_THAT(e.getName(), Eq("org.freedesktop.DBus.Error.AccessDenied")); + ASSERT_THAT(e.getMessage(), Eq("A test error occurred (Operation not permitted)")); + } + catch(...) + { + FAIL() << "Expected sdbus::Error exception"; + } +} + +TEST_F(SdbusTestObject, CallsErrorThrowingMethodWithDontExpectReplySet) +{ + ASSERT_NO_THROW(m_proxy->throwErrorWithNoReply()); + + ASSERT_TRUE(waitUntil(m_adaptor->m_wasThrowErrorCalled)); +} + +TEST_F(SdbusTestObject, RunsServerSideAsynchoronousMethodAsynchronously) +{ + // Yeah, this is kinda timing-dependent test, but times should be safe... + std::mutex mtx; + std::vector results; + std::atomic invoke{}; + std::atomic startedCount{}; + auto call = [&](uint32_t param) + { + TestingProxy proxy{INTERFACE_NAME, OBJECT_PATH}; + ++startedCount; + while (!invoke) ; + auto result = proxy.doOperationAsync(param); + std::lock_guard guard(mtx); + results.push_back(result); + }; + + std::thread invocations[]{std::thread{call, 1500}, std::thread{call, 1000}, std::thread{call, 500}}; + while (startedCount != 3) ; + invoke = true; + std::for_each(std::begin(invocations), std::end(invocations), [](auto& t){ t.join(); }); + + ASSERT_THAT(results, ElementsAre(500, 1000, 1500)); +} + +TEST_F(SdbusTestObject, HandlesCorrectlyABulkOfParallelServerSideAsyncMethods) +{ + std::atomic resultCount{}; + std::atomic invoke{}; + std::atomic startedCount{}; + auto call = [&]() + { + TestingProxy proxy{INTERFACE_NAME, OBJECT_PATH}; + ++startedCount; + while (!invoke) ; + + size_t localResultCount{}; + for (size_t i = 0; i < 500; ++i) + { + auto result = proxy.doOperationAsync(i % 2); + if (result == (i % 2)) // Correct return value? + localResultCount++; + } + + resultCount += localResultCount; + }; + + std::thread invocations[]{std::thread{call}, std::thread{call}, std::thread{call}}; + while (startedCount != 3) ; + invoke = true; + std::for_each(std::begin(invocations), std::end(invocations), [](auto& t){ t.join(); }); + + ASSERT_THAT(resultCount, Eq(1500)); +} + +TEST_F(SdbusTestObject, InvokesMethodAsynchronouslyOnClientSide) +{ + std::promise promise; + auto future = promise.get_future(); + m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) + { + if (err == nullptr) + promise.set_value(res); + else + promise.set_exception(std::make_exception_ptr(*err)); + }); + + m_proxy->doOperationClientSideAsync(100); + + ASSERT_THAT(future.get(), Eq(100)); +} + +TEST_F(SdbusTestObject, InvokesErroneousMethodAsynchronouslyOnClientSide) +{ + std::promise promise; + auto future = promise.get_future(); + m_proxy->installDoOperationClientSideAsyncReplyHandler([&](uint32_t res, const sdbus::Error* err) + { + if (err == nullptr) + promise.set_value(res); + else + promise.set_exception(std::make_exception_ptr(*err)); + }); + + m_proxy->doErroneousOperationClientSideAsync(); + + ASSERT_THROW(future.get(), sdbus::Error); +} + +TEST_F(SdbusTestObject, FailsCallingNonexistentMethod) +{ + ASSERT_THROW(m_proxy->callNonexistentMethod(), sdbus::Error); +} + +TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentInterface) +{ + ASSERT_THROW(m_proxy->callMethodOnNonexistentInterface(), sdbus::Error); +} + +TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentDestination) +{ + TestingProxy proxy("sdbuscpp.destination.that.does.not.exist", OBJECT_PATH); + ASSERT_THROW(proxy.getInt(), sdbus::Error); +} + +TEST_F(SdbusTestObject, FailsCallingMethodOnNonexistentObject) +{ + TestingProxy proxy(INTERFACE_NAME, "/sdbuscpp/path/that/does/not/exist"); + ASSERT_THROW(proxy.getInt(), sdbus::Error); +} + +TEST_F(SdbusTestObject, ReceivesTwoSignalsWhileMakingMethodCall) +{ + m_proxy->emitTwoSimpleSignals(); + + ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal)); + ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithMap)); +} + +#if LIBSYSTEMD_VERSION>=240 +TEST_F(SdbusTestObject, CanSetGeneralMethodTimeoutWithLibsystemdVersionGreaterThan239) +{ + s_connection->setMethodCallTimeout(5000000); + ASSERT_THAT(s_connection->getMethodCallTimeout(), Eq(5000000)); +} +#else +TEST_F(SdbusTestObject, CannotSetGeneralMethodTimeoutWithLibsystemdVersionLessThan240) +{ + ASSERT_THROW(s_connection->setMethodCallTimeout(5000000), sdbus::Error); + ASSERT_THROW(s_connection->getMethodCallTimeout(), sdbus::Error); +} +#endif + +// Signals + +TEST_F(SdbusTestObject, EmitsSimpleSignalSuccesfully) +{ + m_adaptor->emitSimpleSignal(); + + ASSERT_TRUE(waitUntil(m_proxy->m_gotSimpleSignal)); +} + +TEST_F(SdbusTestObject, EmitsSignalWithMapSuccesfully) +{ + m_adaptor->emitSignalWithMap({{0, "zero"}, {1, "one"}}); + + ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithMap)); + ASSERT_THAT(m_proxy->m_mapFromSignal[0], Eq("zero")); + ASSERT_THAT(m_proxy->m_mapFromSignal[1], Eq("one")); +} + +TEST_F(SdbusTestObject, EmitsSignalWithVariantSuccesfully) +{ + double d = 3.14; + m_adaptor->emitSignalWithVariant(d); + + ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithVariant)); + ASSERT_THAT(m_proxy->m_variantFromSignal, DoubleEq(d)); +} + +TEST_F(SdbusTestObject, EmitsSignalWithoutRegistrationSuccesfully) +{ + m_adaptor->emitSignalWithoutRegistration({"platform", {"av"}}); + + ASSERT_TRUE(waitUntil(m_proxy->m_gotSignalWithSignature)); + ASSERT_THAT(m_proxy->m_signatureFromSignal["platform"], Eq("av")); +} + +// Properties + +TEST_F(SdbusTestObject, ReadsReadOnlyPropertySuccesfully) +{ + ASSERT_THAT(m_proxy->state(), Eq(DEFAULT_STATE_VALUE)); +} + +TEST_F(SdbusTestObject, FailsWritingToReadOnlyProperty) +{ + ASSERT_THROW(m_proxy->state("new_value"), sdbus::Error); +} + +TEST_F(SdbusTestObject, WritesAndReadsReadWritePropertySuccesfully) +{ + uint32_t newActionValue = 5678; + + m_proxy->action(newActionValue); + + ASSERT_THAT(m_proxy->action(), Eq(newActionValue)); +} + +// Standard D-Bus interfaces + +TEST_F(SdbusTestObject, PingsViaPeerInterface) +{ + ASSERT_NO_THROW(m_proxy->Ping()); +} + +TEST_F(SdbusTestObject, AnswersMachineUuidViaPeerInterface) +{ + // If /etc/machine-id does not exist in your system (which is very likely because you have + // a non-systemd Linux), org.freedesktop.DBus.Peer.GetMachineId() will not work. To solve + // this, you can create /etc/machine-id yourself as symlink to /var/lib/dbus/machine-id, + // and then org.freedesktop.DBus.Peer.GetMachineId() will start to work. + if (::access("/etc/machine-id", F_OK) == -1) + GTEST_SKIP() << "/etc/machine-id file does not exist, GetMachineId() will not work"; + + ASSERT_NO_THROW(m_proxy->GetMachineId()); +} + +TEST_F(SdbusTestObject, AnswersXmlApiDescriptionViaIntrospectableInterface) +{ + ASSERT_THAT(m_proxy->Introspect(), Eq(m_adaptor->getExpectedXmlApiDescription())); +} + +TEST_F(SdbusTestObject, GetsPropertyViaPropertiesInterface) +{ + ASSERT_THAT(m_proxy->Get(INTERFACE_NAME, "state").get(), Eq(DEFAULT_STATE_VALUE)); +} + +TEST_F(SdbusTestObject, SetsPropertyViaPropertiesInterface) +{ + uint32_t newActionValue = 2345; + + m_proxy->Set(INTERFACE_NAME, "action", newActionValue); + + ASSERT_THAT(m_proxy->action(), Eq(newActionValue)); +} + +TEST_F(SdbusTestObject, GetsAllPropertiesViaPropertiesInterface) +{ + const auto properties = m_proxy->GetAll(INTERFACE_NAME); + + ASSERT_THAT(properties, SizeIs(3)); + EXPECT_THAT(properties.at("state").get(), Eq(DEFAULT_STATE_VALUE)); + EXPECT_THAT(properties.at("action").get(), Eq(DEFAULT_ACTION_VALUE)); + EXPECT_THAT(properties.at("blocking").get(), Eq(DEFAULT_BLOCKING_VALUE)); +} + +TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForSelectedProperties) +{ + std::atomic signalReceived{false}; + m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName + , const std::map& changedProperties + , const std::vector& /*invalidatedProperties*/ ) + { + EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); + EXPECT_THAT(changedProperties, SizeIs(1)); + EXPECT_THAT(changedProperties.at("blocking").get(), Eq(!DEFAULT_BLOCKING_VALUE)); + signalReceived = true; + }; + + m_proxy->blocking(!DEFAULT_BLOCKING_VALUE); + m_proxy->action(DEFAULT_ACTION_VALUE*2); + m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME, {"blocking"}); + + ASSERT_TRUE(waitUntil(signalReceived)); +} + +TEST_F(SdbusTestObject, EmitsPropertyChangedSignalForAllProperties) +{ + std::atomic signalReceived{false}; + m_proxy->m_onPropertiesChangedHandler = [&signalReceived]( const std::string& interfaceName + , const std::map& changedProperties + , const std::vector& invalidatedProperties ) + { + EXPECT_THAT(interfaceName, Eq(INTERFACE_NAME)); + EXPECT_THAT(changedProperties, SizeIs(1)); + EXPECT_THAT(changedProperties.at("blocking").get(), Eq(DEFAULT_BLOCKING_VALUE)); + ASSERT_THAT(invalidatedProperties, SizeIs(1)); + EXPECT_THAT(invalidatedProperties[0], Eq("action")); + signalReceived = true; + }; + + m_adaptor->emitPropertiesChangedSignal(INTERFACE_NAME); + + ASSERT_TRUE(waitUntil(signalReceived)); +} + +TEST_F(SdbusTestObject, GetsZeroManagedObjectsIfHasNoSubPathObjects) +{ + const auto objectsInterfacesAndProperties = m_proxy->GetManagedObjects(); + + ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(0)); +} + +TEST_F(SdbusTestObject, GetsManagedObjectsSuccessfully) +{ + auto subObject1 = sdbus::createObject(*s_connection, "/sub/path1"); + subObject1->registerProperty("aProperty1").onInterface("org.sdbuscpp.integrationtests.iface1").withGetter([]{return uint8_t{123};}); + subObject1->finishRegistration(); + auto subObject2 = sdbus::createObject(*s_connection, "/sub/path2"); + subObject2->registerProperty("aProperty2").onInterface("org.sdbuscpp.integrationtests.iface2").withGetter([]{return "hi";}); + subObject2->finishRegistration(); + + const auto objectsInterfacesAndProperties = m_proxy->GetManagedObjects(); + + ASSERT_THAT(objectsInterfacesAndProperties, SizeIs(2)); + EXPECT_THAT(objectsInterfacesAndProperties.at("/sub/path1").at("org.sdbuscpp.integrationtests.iface1").at("aProperty1").get(), Eq(123)); + EXPECT_THAT(objectsInterfacesAndProperties.at("/sub/path2").at("org.sdbuscpp.integrationtests.iface2").at("aProperty2").get(), Eq("hi")); +} + +TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForSelectedObjectInterfaces) +{ + std::atomic signalReceived{false}; + m_proxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath + , const std::map>& interfacesAndProperties ) + { + EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); + EXPECT_THAT(interfacesAndProperties, SizeIs(1)); + EXPECT_THAT(interfacesAndProperties.count(INTERFACE_NAME), Eq(1)); + EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3)); + signalReceived = true; + }; + + m_adaptor->emitInterfacesAddedSignal({INTERFACE_NAME}); + + ASSERT_TRUE(waitUntil(signalReceived)); +} + +TEST_F(SdbusTestObject, EmitsInterfacesAddedSignalForAllObjectInterfaces) +{ + std::atomic signalReceived{false}; + m_proxy->m_onInterfacesAddedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath + , const std::map>& interfacesAndProperties ) + { + EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); + EXPECT_THAT(interfacesAndProperties, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces + EXPECT_THAT(interfacesAndProperties.at(INTERFACE_NAME), SizeIs(3)); // 3 properties under INTERFACE_NAME + signalReceived = true; + }; + + m_adaptor->emitInterfacesAddedSignal(); + + ASSERT_TRUE(waitUntil(signalReceived)); +} + +TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForSelectedObjectInterfaces) +{ + std::atomic signalReceived{false}; + m_proxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath + , const std::vector& interfaces ) + { + EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); + ASSERT_THAT(interfaces, SizeIs(1)); + EXPECT_THAT(interfaces[0], Eq(INTERFACE_NAME)); + signalReceived = true; + }; + + m_adaptor->emitInterfacesRemovedSignal({INTERFACE_NAME}); + + ASSERT_TRUE(waitUntil(signalReceived)); +} + +TEST_F(SdbusTestObject, EmitsInterfacesRemovedSignalForAllObjectInterfaces) +{ + std::atomic signalReceived{false}; + m_proxy->m_onInterfacesRemovedHandler = [&signalReceived]( const sdbus::ObjectPath& objectPath + , const std::vector& interfaces ) + { + EXPECT_THAT(objectPath, Eq(OBJECT_PATH)); + ASSERT_THAT(interfaces, SizeIs(5)); // INTERFACE_NAME + 4 standard interfaces + signalReceived = true; + }; + + m_adaptor->emitInterfacesRemovedSignal(); + + ASSERT_TRUE(waitUntil(signalReceived)); +} diff --git a/tests/integrationtests/TestingAdaptor.h b/tests/integrationtests/TestingAdaptor.h index 00d0adb..4ebdd65 100644 --- a/tests/integrationtests/TestingAdaptor.h +++ b/tests/integrationtests/TestingAdaptor.h @@ -50,20 +50,6 @@ public: protected: - int32_t doSignalEmission() override - { - static int32_t counter = 0; - emitSimpleSignal(); - printf("First simple signal thrown\n"); - counter++; - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - //emitSimpleSignal(); - //printf("Second simple signal thrown\n"); - counter++; - - return counter; - } - void noArgNoReturn() const { } @@ -211,6 +197,13 @@ protected: throw sdbus::createError(1, "A test error occurred"); } + + void emitTwoSimpleSignals() override + { + emitSimpleSignal(); + emitSignalWithMap({}); + } + std::string state() { return m_state; diff --git a/tests/integrationtests/TestingProxy.h b/tests/integrationtests/TestingProxy.h index a183119..5131dec 100644 --- a/tests/integrationtests/TestingProxy.h +++ b/tests/integrationtests/TestingProxy.h @@ -61,7 +61,6 @@ protected: void onSimpleSignal() override { m_gotSimpleSignal = true; - printf("Thread %d: Got simple signal\n", gettid()); } void onSignalWithMap(const std::map& m) override @@ -113,6 +112,7 @@ protected: //private: public: // for tests + int m_SimpleSignals = 0; std::atomic m_gotSimpleSignal{false}; std::atomic m_gotSignalWithMap{false}; std::map m_mapFromSignal; diff --git a/tests/integrationtests/adaptor-glue.h b/tests/integrationtests/adaptor-glue.h index 1131cd9..0ba64f5 100644 --- a/tests/integrationtests/adaptor-glue.h +++ b/tests/integrationtests/adaptor-glue.h @@ -59,8 +59,6 @@ protected: { object_.setInterfaceFlags(INTERFACE_NAME).markAsDeprecated().withPropertyUpdateBehavior(sdbus::Flags::EMITS_NO_SIGNAL); - object_.registerMethod("doSignalEmission").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->doSignalEmission(); }); - object_.registerMethod("noArgNoReturn").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->noArgNoReturn(); }); object_.registerMethod("getInt").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getInt(); }); object_.registerMethod("getTuple").onInterface(INTERFACE_NAME).implementedAs([this](){ return this->getTuple(); }); @@ -108,6 +106,8 @@ protected: object_.registerMethod("doPrivilegedStuff").onInterface(INTERFACE_NAME).implementedAs([](){}).markAsPrivileged(); + object_.registerMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME).implementedAs([this](){ this->emitTwoSimpleSignals(); }); + // registration of signals is optional, it is useful because of introspection object_.registerSignal("simpleSignal").onInterface(INTERFACE_NAME).markAsDeprecated(); object_.registerSignal("signalWithMap").onInterface(INTERFACE_NAME).withParameters>(); @@ -152,7 +152,6 @@ private: protected: - virtual int32_t doSignalEmission() = 0; virtual void noArgNoReturn() const = 0; virtual int32_t getInt() const = 0; virtual std::tuple getTuple() const = 0; @@ -171,6 +170,7 @@ protected: virtual sdbus::UnixFd getUnixFd() const = 0; virtual ComplexType getComplex() const = 0; virtual void throwError() const = 0; + virtual void emitTwoSimpleSignals() = 0; virtual std::string state() = 0; virtual uint32_t action() = 0; diff --git a/tests/integrationtests/proxy-glue.h b/tests/integrationtests/proxy-glue.h index 8323a9a..f2b8442 100644 --- a/tests/integrationtests/proxy-glue.h +++ b/tests/integrationtests/proxy-glue.h @@ -56,12 +56,9 @@ protected: virtual void onDoOperationReply(uint32_t returnValue, const sdbus::Error* error) = 0; public: - int32_t doSignalEmission() + void emitTwoSimpleSignals() { - int32_t result; - object_.callMethod("doSignalEmission").onInterface(INTERFACE_NAME).storeResultsTo(result); - //object_.callMethodAsync("doSignalEmission").onInterface(INTERFACE_NAME).uponReplyInvoke([this](const sdbus::Error* error, int32_t returnValue){ printf("Async reply handler\n"); }); - return result; + object_.callMethod("emitTwoSimpleSignals").onInterface(INTERFACE_NAME); } void noArgNoReturn() @@ -180,6 +177,19 @@ public: }); } + void doOperationClientSideAsyncWith500msTimeout(uint32_t param) + { + using namespace std::chrono_literals; + object_.callMethodAsync("doOperation") + .onInterface(INTERFACE_NAME) + .withTimeout(500000us) + .withArguments(param) + .uponReplyInvoke([this](const sdbus::Error* error, uint32_t returnValue) + { + this->onDoOperationReply(returnValue, error); + }); + } + sdbus::Signature getSignature() { sdbus::Signature result; diff --git a/tests/perftests/client.cpp b/tests/perftests/client.cpp index dacb216..1f46fd7 100644 --- a/tests/perftests/client.cpp +++ b/tests/perftests/client.cpp @@ -37,6 +37,8 @@ using namespace std::chrono_literals; +uint64_t totalDuration = 0; + class PerftestProxy : public sdbus::ProxyInterfaces { public: @@ -66,7 +68,9 @@ protected: else if (counter == m_msgCount) { auto stopTime = std::chrono::steady_clock::now(); - std::cout << "Received " << m_msgCount << " signals in: " << std::chrono::duration_cast(stopTime - startTime).count() << " ms" << std::endl; + auto duration = std::chrono::duration_cast(stopTime - startTime).count(); + totalDuration += duration; + std::cout << "Received " << m_msgCount << " signals in: " << duration << " ms" << std::endl; counter = 0; } } @@ -104,6 +108,7 @@ int main(int /*argc*/, char */*argv*/[]) unsigned int msgCount = 1000; unsigned int msgSize{}; + /* msgSize = 20; std::cout << "** Measuring signals of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl; client.m_msgCount = msgCount; client.m_msgSize = msgSize; @@ -114,6 +119,9 @@ int main(int /*argc*/, char */*argv*/[]) std::this_thread::sleep_for(1000ms); } + std::cout << "AVERAGE: " << (totalDuration/repetitions) << " ms" << std::endl; + totalDuration = 0; + msgSize = 1000; std::cout << std::endl << "** Measuring signals of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl; client.m_msgCount = msgCount; client.m_msgSize = msgSize; @@ -124,6 +132,10 @@ int main(int /*argc*/, char */*argv*/[]) std::this_thread::sleep_for(1000ms); } + std::cout << "AVERAGE: " << (totalDuration/repetitions) << " ms" << std::endl; + totalDuration = 0; + */ + msgSize = 20; std::cout << std::endl << "** Measuring method calls of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl; for (unsigned int r = 0; r < repetitions; ++r) @@ -140,11 +152,16 @@ int main(int /*argc*/, char */*argv*/[]) assert(result.size() == msgSize); } auto stopTime = std::chrono::steady_clock::now(); - std::cout << "Called " << msgCount << " methods in: " << std::chrono::duration_cast(stopTime - startTime).count() << " ms" << std::endl; + auto duration = std::chrono::duration_cast(stopTime - startTime).count(); + totalDuration += duration; + std::cout << "Called " << msgCount << " methods in: " << duration << " ms" << std::endl; std::this_thread::sleep_for(1000ms); } + std::cout << "AVERAGE: " << (totalDuration/repetitions) << " ms" << std::endl; + totalDuration = 0; + msgSize = 1000; std::cout << std::endl << "** Measuring method calls of size " << msgSize << " bytes (" << repetitions << " repetitions)..." << std::endl << std::endl; for (unsigned int r = 0; r < repetitions; ++r) @@ -161,10 +178,15 @@ int main(int /*argc*/, char */*argv*/[]) assert(result.size() == msgSize); } auto stopTime = std::chrono::steady_clock::now(); - std::cout << "Called " << msgCount << " methods in: " << std::chrono::duration_cast(stopTime - startTime).count() << " ms" << std::endl; + auto duration = std::chrono::duration_cast(stopTime - startTime).count(); + totalDuration += duration; + std::cout << "Called " << msgCount << " methods in: " << duration << " ms" << std::endl; std::this_thread::sleep_for(1000ms); } + std::cout << "AVERAGE: " << (totalDuration/repetitions) << " ms" << std::endl; + totalDuration = 0; + return 0; }