1 #include <unistd.h> 2 #include <dbus/connection.hpp> 3 #include <dbus/endpoint.hpp> 4 #include <dbus/filter.hpp> 5 #include <dbus/match.hpp> 6 #include <dbus/message.hpp> 7 #include <dbus/properties.hpp> 8 #include <functional> 9 #include <vector> 10 #include <gmock/gmock.h> 11 #include <gtest/gtest.h> 12 13 static const std::string dbus_boilerplate( 14 "<!DOCTYPE node PUBLIC " 15 "\"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" " 16 "\"http://www.freedesktop.org/standards/dbus/1.0/" 17 "introspect.dtd\">\n"); 18 19 TEST(DbusPropertiesInterface, EmptyObjectServer) { 20 boost::asio::io_service io; 21 auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system); 22 23 // Set up the object server, and send some test events 24 dbus::DbusObjectServer foo(system_bus); 25 26 EXPECT_EQ(foo.get_xml_for_path("/"), dbus_boilerplate + "<node></node>"); 27 EXPECT_EQ(foo.get_xml_for_path(""), dbus_boilerplate + "<node></node>"); 28 } 29 30 TEST(DbusPropertiesInterface, BasicObjectServer) { 31 boost::asio::io_service io; 32 auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system); 33 34 // Set up the object server, and send some test events 35 dbus::DbusObjectServer foo(system_bus); 36 37 foo.register_object(std::make_shared<dbus::DbusObject>( 38 system_bus, "/org/freedesktop/NetworkManager")); 39 40 EXPECT_EQ(foo.get_xml_for_path("/"), dbus_boilerplate + 41 "<node><node " 42 "name=\"org\"></node></node>"); 43 EXPECT_EQ(foo.get_xml_for_path(""), dbus_boilerplate + 44 "<node><node " 45 "name=\"org\"></node></node>"); 46 47 EXPECT_EQ(foo.get_xml_for_path("/org"), 48 dbus_boilerplate + 49 "<node><node " 50 "name=\"freedesktop\"></node></node>"); 51 EXPECT_EQ(foo.get_xml_for_path("/org/freedesktop"), 52 dbus_boilerplate + 53 "<node><node " 54 "name=\"NetworkManager\"></node></node>"); 55 // TODO(Ed) turn this test back on once the signal interface stabilizes 56 /*EXPECT_EQ(foo.get_xml_for_path("/org/freedesktop/NetworkManager"), 57 dbus_boilerplate + "<node></node>");*/ 58 } 59 60 TEST(DbusPropertiesInterface, SharedNodeObjectServer) { 61 boost::asio::io_service io; 62 auto system_bus = std::make_shared<dbus::connection>(io, dbus::bus::system); 63 64 // Set up the object server, and send some test events 65 dbus::DbusObjectServer foo(system_bus); 66 67 foo.register_object( 68 std::make_shared<dbus::DbusObject>(system_bus, "/org/freedesktop/test1")); 69 foo.register_object( 70 std::make_shared<dbus::DbusObject>(system_bus, "/org/freedesktop/test2")); 71 72 EXPECT_EQ(foo.get_xml_for_path("/"), dbus_boilerplate + 73 "<node><node " 74 "name=\"org\"></node></node>"); 75 EXPECT_EQ(foo.get_xml_for_path(""), dbus_boilerplate + 76 "<node><node " 77 "name=\"org\"></node></node>"); 78 79 EXPECT_EQ(foo.get_xml_for_path("/org"), 80 dbus_boilerplate + 81 "<node><node " 82 "name=\"freedesktop\"></node></node>"); 83 EXPECT_EQ(foo.get_xml_for_path("/org/freedesktop"), 84 dbus_boilerplate + 85 "<node><node " 86 "name=\"test1\"></node><node name=\"test2\"></node></node>"); 87 // TODO(Ed) turn this test back on once the signal interface stabilizes 88 /* 89 EXPECT_EQ(foo.get_xml_for_path("/org/freedesktop/test1"), 90 dbus_boilerplate + "<node></node>"); 91 EXPECT_EQ(foo.get_xml_for_path("/org/freedesktop/test2"), 92 dbus_boilerplate + "<node></node>"); 93 */ 94 } 95 96 TEST(LambdaDbusMethodTest, Basic) { 97 bool lambda_called = false; 98 auto lambda = [&](int32_t x) { 99 EXPECT_EQ(x, 18); 100 lambda_called = true; 101 return std::make_tuple<int64_t, int32_t>(4L, 2); 102 }; 103 boost::asio::io_service io; 104 auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session); 105 auto dbus_method = 106 dbus::LambdaDbusMethod<decltype(lambda)>("foo", bus, lambda); 107 108 dbus::message m = 109 dbus::message::new_call(dbus::endpoint("org.freedesktop.Avahi", "/", 110 "org.freedesktop.Avahi.Server"), 111 "GetHostName"); 112 m.pack(static_cast<int32_t>(18)); 113 // Small thing that the dbus library normally does for us, but because we're 114 // bypassing it, we need to fill it in as if it was done 115 m.set_serial(585); 116 dbus_method.call(m); 117 EXPECT_EQ(lambda_called, true); 118 } 119 120 TEST(DbusPropertiesInterface, ObjectServer) { 121 boost::asio::io_service io; 122 auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session); 123 124 // Set up the object server, and send some test objects 125 dbus::DbusObjectServer foo(bus); 126 127 foo.register_object( 128 std::make_shared<dbus::DbusObject>(bus, "/org/freedesktop/test1")); 129 foo.register_object( 130 std::make_shared<dbus::DbusObject>(bus, "/org/freedesktop/test2")); 131 int completion_count(0); 132 133 std::array<std::string, 4> paths_to_test( 134 {{"/", "/org", "/org/freedesktop", "/org/freedesktop/test1"}}); 135 136 for (auto& path : paths_to_test) { 137 dbus::endpoint test_daemon(bus->get_unique_name(), path, 138 "org.freedesktop.DBus.Introspectable"); 139 dbus::message m = dbus::message::new_call(test_daemon, "Introspect"); 140 completion_count++; 141 bus->async_send( 142 m, [&](const boost::system::error_code ec, dbus::message r) { 143 if (ec) { 144 std::string error; 145 r.unpack(error); 146 FAIL() << ec << error; 147 } else { 148 std::string xml; 149 r.unpack(xml); 150 EXPECT_EQ(r.get_type(), "method_return"); 151 if (path == "/") { 152 EXPECT_EQ(xml, dbus_boilerplate + 153 "<node><node " 154 "name=\"org\"></node></node>"); 155 } else if (path == "/org") { 156 EXPECT_EQ(xml, dbus_boilerplate + 157 "<node><node " 158 "name=\"freedesktop\"></node></node>"); 159 } else if (path == "/org/freedesktop") { 160 EXPECT_EQ(xml, dbus_boilerplate + 161 "<node><node " 162 "name=\"test1\"></node><node " 163 "name=\"test2\"></node></node>"); 164 } else if (path == "/org/freedesktop/test1") { 165 } else { 166 FAIL() << "Unknown path: " << path; 167 } 168 } 169 completion_count--; 170 if (completion_count == 0) { 171 io.stop(); 172 } 173 }); 174 } 175 io.run(); 176 } 177 178 TEST(DbusPropertiesInterface, EmptyMethodServer) { 179 boost::asio::io_service io; 180 auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session); 181 182 // Set up the object server, and send some test objects 183 dbus::DbusObjectServer foo(bus); 184 foo.register_object( 185 std::make_shared<dbus::DbusObject>(bus, "/org/freedesktop/test1")); 186 187 dbus::endpoint test_daemon(bus->get_unique_name(), "/org/freedesktop/test1", 188 "org.freedesktop.DBus.Introspectable"); 189 dbus::message m = dbus::message::new_call(test_daemon, "Introspect"); 190 191 bus->async_send(m, [&](const boost::system::error_code ec, dbus::message r) { 192 if (ec) { 193 std::string error; 194 r.unpack(error); 195 FAIL() << ec << error; 196 } else { 197 std::cout << r; 198 std::string xml; 199 r.unpack(xml); 200 EXPECT_EQ(r.get_type(), "method_return"); 201 // TODO(ed) turn back on when method interface stabilizes 202 // EXPECT_EQ(xml, dbus_boilerplate + "<node></node>"); 203 } 204 205 io.stop(); 206 207 }); 208 209 io.run(); 210 } 211 212 std::tuple<int> test_method(uint32_t x) { 213 std::cout << "method called.\n"; 214 return std::make_tuple<int>(42); 215 } 216 217 class TestClass { 218 public: 219 static std::tuple<int> test_method(uint32_t x) { 220 std::cout << "method called.\n"; 221 return std::make_tuple<int>(42); 222 } 223 }; 224 225 TEST(DbusPropertiesInterface, MethodServer) { 226 boost::asio::io_service io; 227 auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session); 228 229 // Set up the object server, and send some test objects 230 dbus::DbusObjectServer foo(bus); 231 auto object = 232 std::make_shared<dbus::DbusObject>(bus, "/org/freedesktop/test1"); 233 foo.register_object(object); 234 235 auto iface = std::make_shared<dbus::DbusInterface>( 236 "org.freedesktop.My.Interface", bus); 237 object->register_interface(iface); 238 239 // Test multiple ways to register methods 240 // Basic lambda 241 iface->register_method("MyMethodLambda", [](uint32_t x) { 242 243 std::cout << "method called. Got:" << x << "\n"; 244 return std::make_tuple<int>(42); 245 }); 246 247 // std::function 248 std::function<typename std::tuple<int>(uint32_t)> my_function = 249 [](uint32_t x) { 250 251 std::cout << "method called. Got:" << x << "\n"; 252 return std::make_tuple<int>(42); 253 }; 254 255 iface->register_method("MyMethodStdFunction", my_function); 256 257 // Function pointer 258 iface->register_method("MyMethodFunctionPointer", &test_method); 259 260 // Class function pointer 261 TestClass t; 262 iface->register_method("MyClassFunctionPointer", t.test_method); 263 264 // const class function pointer 265 const TestClass t2; 266 iface->register_method("MyClassFunctionPointer", t2.test_method); 267 268 iface->register_method("VoidMethod", []() { 269 270 std::cout << "method called.\n"; 271 return (uint32_t)42; 272 }); 273 274 iface->register_method("VoidMethod", []() { 275 276 std::cout << "method called.\n"; 277 return std::make_tuple<int>(42); 278 }); 279 280 // Test multiple ways to register methods 281 // Basic lambda 282 iface->register_method("MyMethodLambda", {"x"}, {"return_value_name"}, 283 [](uint32_t x) { 284 std::cout << "method called. Got:" << x << "\n"; 285 return 42; 286 }); 287 288 dbus::endpoint test_daemon(bus->get_unique_name(), "/org/freedesktop/test1", 289 "org.freedesktop.DBus.Introspectable"); 290 dbus::message m = dbus::message::new_call(test_daemon, "Introspect"); 291 292 bus->async_send(m, [&](const boost::system::error_code ec, dbus::message r) { 293 if (ec) { 294 std::string error; 295 r.unpack(error); 296 297 FAIL() << ec << error; 298 } else { 299 std::string xml; 300 r.unpack(xml); 301 EXPECT_EQ(r.get_type(), "method_return"); 302 // todo(ED) 303 /* 304 EXPECT_EQ(xml, dbus_boilerplate + 305 "<node><interface " 306 "name=\"MyInterface\"><method " 307 "name=\"MyMethod\"></method></interface></node>"); 308 */ 309 } 310 io.stop(); 311 }); 312 313 io.run(); 314 } 315 316 TEST(DbusPropertiesInterface, PropertiesInterface) { 317 boost::asio::io_service io; 318 auto bus = std::make_shared<dbus::connection>(io, dbus::bus::session); 319 320 // Set up the object server, and send some test objects 321 dbus::DbusObjectServer foo(bus); 322 auto object = 323 std::make_shared<dbus::DbusObject>(bus, "/org/freedesktop/test1"); 324 foo.register_object(object); 325 326 auto iface = std::make_shared<dbus::DbusInterface>( 327 "org.freedesktop.My.Interface", bus); 328 object->register_interface(iface); 329 330 iface->set_property("foo", (uint32_t)26); 331 332 dbus::endpoint get_dbus_properties(bus->get_unique_name(), 333 "/org/freedesktop/test1", 334 "org.freedesktop.DBus.Properties", "Get"); 335 size_t outstanding_async_calls = 0; 336 337 outstanding_async_calls++; 338 bus->async_method_call( 339 [&](const boost::system::error_code ec, dbus::dbus_variant value) { 340 outstanding_async_calls--; 341 if (ec) { 342 FAIL() << ec; 343 } else { 344 EXPECT_EQ(boost::get<uint32_t>(value), 26); 345 } 346 if (outstanding_async_calls == 0) { 347 io.stop(); 348 } 349 }, 350 get_dbus_properties, "org.freedesktop.My.Interface", "foo"); 351 352 dbus::endpoint getall_dbus_properties( 353 bus->get_unique_name(), "/org/freedesktop/test1", 354 "org.freedesktop.DBus.Properties", "GetAll"); 355 356 outstanding_async_calls++; 357 bus->async_method_call( 358 [&](const boost::system::error_code ec, 359 std::vector<std::pair<std::string, dbus::dbus_variant>> value) { 360 outstanding_async_calls--; 361 if (ec) { 362 FAIL() << ec; 363 } else { 364 EXPECT_EQ(value.size(), 1); 365 EXPECT_EQ(value[0].first, "foo"); 366 EXPECT_EQ(value[0].second, dbus::dbus_variant((uint32_t)26)); 367 } 368 if (outstanding_async_calls == 0) { 369 io.stop(); 370 } 371 }, 372 getall_dbus_properties, "org.freedesktop.My.Interface"); 373 374 io.run(); 375 } 376