diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | sdbusplus/server/interface.hpp | 30 | ||||
-rw-r--r-- | sdbusplus/server/object.hpp | 67 | ||||
-rw-r--r-- | test/Makefile.am | 21 | ||||
-rw-r--r-- | test/server/Test.interface.yaml | 5 | ||||
-rw-r--r-- | test/server/object.cpp | 105 | ||||
-rw-r--r-- | tools/sdbusplus/templates/interface.mako.server.hpp | 12 |
7 files changed, 235 insertions, 7 deletions
@@ -66,3 +66,5 @@ Makefile.in /test/utility_tuple_to_array /test/utility_type_traits /test/vtable_vtable +/test/object +/test/server/Test diff --git a/sdbusplus/server/interface.hpp b/sdbusplus/server/interface.hpp index 4c9d133..2b05931 100644 --- a/sdbusplus/server/interface.hpp +++ b/sdbusplus/server/interface.hpp @@ -46,7 +46,6 @@ struct interface final interface& operator=(const interface&) = delete; interface(interface&&) = default; interface& operator=(interface&&) = default; - ~interface() = default; /** @brief Register the (path, interface, vtable) as a dbus object. * @@ -60,7 +59,8 @@ struct interface final interface(sdbusplus::bus::bus& bus, const char* path, const char* interf, const sdbusplus::vtable::vtable_t* vtable, void* context) : _bus(bus.get(), bus.getInterface()), - _path(path), _interf(interf), _slot(nullptr), _intf(bus.getInterface()) + _path(path), _interf(interf), _slot(nullptr), _intf(bus.getInterface()), + _interface_added(false) { sd_bus_slot* slot = nullptr; _intf->sd_bus_add_object_vtable(_bus.get(), &slot, _path.c_str(), @@ -69,6 +69,11 @@ struct interface final _slot = decltype(_slot){slot}; } + ~interface() + { + emit_removed(); + } + /** @brief Create a new signal message. * * @param[in] member - The signal name to create. @@ -93,6 +98,26 @@ struct interface final _bus.get(), _path.c_str(), _interf.c_str(), static_cast<char**>(p)); } + /** @brief Emit the interface is added on D-Bus */ + void emit_added() + { + if (!_interface_added) + { + _bus.emit_interfaces_added(_path.c_str(), {_interf}); + _interface_added = true; + } + } + + /** @brief Emit the interface is removed on D-Bus */ + void emit_removed() + { + if (_interface_added) + { + _bus.emit_interfaces_removed(_path.c_str(), {_interf}); + _interface_added = false; + } + } + bus::bus& bus() { return _bus; @@ -108,6 +133,7 @@ struct interface final std::string _interf; slot::slot _slot; SdBusInterface* _intf; + bool _interface_added; }; } // namespace interface diff --git a/sdbusplus/server/object.hpp b/sdbusplus/server/object.hpp index 7aaeaa5..afb031e 100644 --- a/sdbusplus/server/object.hpp +++ b/sdbusplus/server/object.hpp @@ -2,6 +2,7 @@ #include <sdbusplus/bus.hpp> #include <sdbusplus/sdbus.hpp> +#include <type_traits> namespace sdbusplus { @@ -15,6 +16,25 @@ namespace object namespace details { +/** Helper functions to detect if member exists in a class */ + +/** Test if emit_added() exists in T return std::true_type */ +template <class T> +constexpr auto has_emit_added_helper(int) + -> decltype(std::declval<T>().emit_added(), std::true_type{}); + +/** If the above test fails, fall back to this to return std::false_type */ +template <class> +constexpr std::false_type has_emit_added_helper(...); + +/** Invoke the test with an int so it first resolves to + * has_emit_added_helper(int), and when it fails, it resovles to + * has_emit_added_helper(...) thanks to SFINAE. + * So the return type is std::true_type if emit_added() exists in T and + * std::false_type otherwise */ +template <class T> +using has_emit_added = decltype(has_emit_added_helper<T>(0)); + /** Templates to allow multiple inheritance via template parameters. * * These allow an object to group multiple dbus interface bindings into a @@ -87,6 +107,13 @@ struct object : details::compose<Args...> object(object&&) = default; object& operator=(object&&) = default; + enum class action + { + emit_object_added, + emit_interface_added, + defer_emit, + }; + /** Construct an 'object' on a bus with a path. * * @param[in] bus - The bus to place the object on. @@ -96,17 +123,23 @@ struct object : details::compose<Args...> * object needs custom property init before the * signal can be sent. */ - object(bus::bus& bus, const char* path, bool deferSignal = false) : + object(bus::bus& bus, const char* path, + action act = action::emit_object_added) : details::compose<Args...>(bus, path), __sdbusplus_server_object_bus(bus.get(), bus.getInterface()), __sdbusplus_server_object_path(path), __sdbusplus_server_object_emitremoved(false), __sdbusplus_server_object_intf(bus.getInterface()) { - if (!deferSignal) - { - emit_object_added(); - } + // Default ctor + check_action(act); + } + + object(bus::bus& bus, const char* path, bool deferSignal) : + object(bus, path, + deferSignal ? action::defer_emit : action::emit_object_added) + { + // Delegate to default ctor } ~object() @@ -140,6 +173,30 @@ struct object : details::compose<Args...> std::string __sdbusplus_server_object_path; bool __sdbusplus_server_object_emitremoved; SdBusInterface* __sdbusplus_server_object_intf; + + /** Detect if the interface has emit_added() and invoke it */ + template <class T> + void maybe_emit() + { + if constexpr (details::has_emit_added<T>()) + { + T::emit_added(); + } + } + + /** Check and run the action */ + void check_action(action act) + { + if (act == action::emit_object_added) + { + emit_object_added(); + } + else if (act == action::emit_interface_added) + { + (maybe_emit<Args>(), ...); + } + // Otherwise, do nothing + } }; } // namespace object diff --git a/test/Makefile.am b/test/Makefile.am index ab52758..445569a 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -60,4 +60,25 @@ check_PROGRAMS += timer timer_SOURCES = timer.cpp timer_LDADD = $(gtest_ldadd) +server/Test/server.hpp: + @mkdir -p $(@D) + @top_srcdir@/tools/sdbus++ \ + -r $(srcdir) -t $(top_builddir)/tools/sdbusplus/templates \ + interface server-header server.Test > $@ + +server/Test/server.cpp: + @mkdir -p $(@D) + @top_srcdir@/tools/sdbus++ \ + -r $(srcdir) -t $(top_builddir)/tools/sdbusplus/templates \ + interface server-cpp server.Test > $@ + +server_test_generated_sources = server/Test/server.cpp server/Test/server.hpp + +BUILT_SOURCES = $(server_test_generated_sources) +CLEANFILES = $(server_test_generated_sources) + +check_PROGRAMS += object +object_SOURCES = server/object.cpp $(server_test_generated_sources) +object_LDADD = $(gtest_ldadd) + endif diff --git a/test/server/Test.interface.yaml b/test/server/Test.interface.yaml new file mode 100644 index 0000000..d015c99 --- /dev/null +++ b/test/server/Test.interface.yaml @@ -0,0 +1,5 @@ +description: > + A test interface +properties: + - name: SomeValue + type: int64 diff --git a/test/server/object.cpp b/test/server/object.cpp new file mode 100644 index 0000000..8faa668 --- /dev/null +++ b/test/server/object.cpp @@ -0,0 +1,105 @@ +#include "Test/server.hpp" + +#include <sdbusplus/bus.hpp> +#include <sdbusplus/server/manager.hpp> +#include <sdbusplus/test/sdbus_mock.hpp> + +#include <gtest/gtest.h> + +using ::testing::_; +using ::testing::StrEq; + +using TestInherit = + sdbusplus::server::object::object<sdbusplus::server::server::Test>; + +class Object : public ::testing::Test +{ + protected: + sdbusplus::SdBusMock sdbusMock; + sdbusplus::bus::bus bus = sdbusplus::get_mocked_new(&sdbusMock); + + static constexpr auto busName = "xyz.openbmc_project.sdbusplus.test.Object"; + static constexpr auto objPath = "/xyz/openbmc_project/sdbusplus/test"; +}; + +TEST_F(Object, InterfaceAddedOnly) +{ + // Simulate the typical usage of a service + sdbusplus::server::manager::manager objManager(bus, objPath); + bus.request_name(busName); + + EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath))) + .Times(0); + EXPECT_CALL(sdbusMock, + sd_bus_emit_interfaces_added_strv(_, StrEq(objPath), _)) + .Times(1); + + // This only add interface to the existing object + auto test = std::make_unique<TestInherit>( + bus, objPath, TestInherit::action::emit_interface_added); + + // After destruction, the interface shall be removed + EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath))) + .Times(0); + EXPECT_CALL(sdbusMock, + sd_bus_emit_interfaces_removed_strv(_, StrEq(objPath), _)) + .Times(1); +} + +TEST_F(Object, DeferAddInterface) +{ + // Simulate the typical usage of a service + sdbusplus::server::manager::manager objManager(bus, objPath); + bus.request_name(busName); + + EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath))) + .Times(0); + EXPECT_CALL(sdbusMock, + sd_bus_emit_interfaces_added_strv(_, StrEq(objPath), _)) + .Times(0); + + // It defers emit_object_added() + auto test = std::make_unique<TestInherit>(bus, objPath, + TestInherit::action::defer_emit); + + EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath))) + .Times(1); + EXPECT_CALL(sdbusMock, + sd_bus_emit_interfaces_added_strv(_, StrEq(objPath), _)) + .Times(0); + + // Now invoke emit_object_added() + test->emit_object_added(); + + // After destruction, the object will be removed + EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath))) + .Times(1); + EXPECT_CALL(sdbusMock, + sd_bus_emit_interfaces_removed_strv(_, StrEq(objPath), _)) + .Times(0); +} + +TEST_F(Object, ObjectAdded) +{ + // Simulate the typical usage of a service + sdbusplus::server::manager::manager objManager(bus, objPath); + bus.request_name(busName); + + EXPECT_CALL(sdbusMock, sd_bus_emit_object_added(_, StrEq(objPath))) + .Times(1); + EXPECT_CALL(sdbusMock, + sd_bus_emit_interfaces_added_strv(_, StrEq(objPath), _)) + .Times(0); + + // Note: this is the wrong usage! + // With the below code, the interface is added as expected, but after + // destruction of TestInherit, the whole object will be removed from DBus. + // Refer to InterfaceAddedOnly case for the correct usage. + auto test = std::make_unique<TestInherit>(bus, objPath); + + EXPECT_CALL(sdbusMock, sd_bus_emit_object_removed(_, StrEq(objPath))) + .Times(1); + EXPECT_CALL(sdbusMock, + sd_bus_emit_interfaces_removed_strv(_, StrEq(objPath), _)) + .Times(0); +} diff --git a/tools/sdbusplus/templates/interface.mako.server.hpp b/tools/sdbusplus/templates/interface.mako.server.hpp index 99ba769..1825d6b 100644 --- a/tools/sdbusplus/templates/interface.mako.server.hpp +++ b/tools/sdbusplus/templates/interface.mako.server.hpp @@ -121,6 +121,18 @@ ${p.camelCase}(${p.cppTypeParam(interface.name)} value); static ${e.name} convert${e.name}FromString(const std::string& s); % endfor + /** @brief Emit interface added */ + void emit_added() + { + _${"_".join(interface.name.split('.'))}_interface.emit_added(); + } + + /** @brief Emit interface removed */ + void emit_removed() + { + _${"_".join(interface.name.split('.'))}_interface.emit_removed(); + } + static constexpr auto interface = "${interface.name}"; private: |