buffet: Add ExportedObjectManager delegate This makes it easy to export an object manager. We'll use this very soon to implement the ObjectManager interface on the root Buffet object. BUG=chromium:359190 TEST=Unittests Change-Id: I19d2da33b81557431c5787937c49a18e7d7bacb2 Reviewed-on: https://chromium-review.googlesource.com/196387 Reviewed-by: Alex Vakulenko <avakulenko@chromium.org> Commit-Queue: Christopher Wiley <wiley@chromium.org> Tested-by: Christopher Wiley <wiley@chromium.org>
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp index c4c5a1b..b94677c 100644 --- a/buffet/buffet.gyp +++ b/buffet/buffet.gyp
@@ -32,6 +32,7 @@ 'dbus_manager.cc', 'dbus_utils.cc', 'device_registration_info.cc', + 'exported_object_manager.cc', 'exported_property_set.cc', 'http_request.cc', 'http_connection_curl.cc', @@ -77,6 +78,7 @@ 'buffet_testrunner.cc', 'data_encoding_unittest.cc', 'device_registration_info_unittest.cc', + 'exported_object_manager_unittest.cc', 'exported_property_set_unittest.cc', 'http_connection_fake.cc', 'http_transport_fake.cc',
diff --git a/buffet/exported_object_manager.cc b/buffet/exported_object_manager.cc new file mode 100644 index 0000000..6458128 --- /dev/null +++ b/buffet/exported_object_manager.cc
@@ -0,0 +1,133 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "buffet/exported_object_manager.h" + +#include <dbus/object_manager.h> + +#include "buffet/async_event_sequencer.h" + +namespace buffet { + +namespace dbus_utils { + +ExportedObjectManager::ExportedObjectManager(dbus::Bus* bus, + const dbus::ObjectPath& path) + : bus_(bus), exported_object_(bus->GetExportedObject(path)), + weak_ptr_factory_(this) {} + +void ExportedObjectManager::Init(const OnInitFinish& cb) { + bus_->AssertOnOriginThread(); + scoped_refptr<dbus_utils::AsyncEventSequencer> sequencer( + new dbus_utils::AsyncEventSequencer()); + exported_object_->ExportMethod( + dbus::kObjectManagerInterface, + dbus::kObjectManagerGetManagedObjects, + base::Bind(&ExportedObjectManager::HandleGetManagedObjects, + weak_ptr_factory_.GetWeakPtr()), + sequencer->GetExportHandler( + dbus::kObjectManagerInterface, + dbus::kObjectManagerGetManagedObjects, + "Failed exporting GetManagedObjects method of ObjectManager", + false)); +} + +void ExportedObjectManager::ClaimInterface( + const dbus::ObjectPath& path, + const std::string& interface_name, + const PropertyWriter& property_writer) { + bus_->AssertOnOriginThread(); + // We're sending signals that look like: + // org.freedesktop.DBus.ObjectManager.InterfacesAdded ( + // OBJPATH object_path, + // DICT<STRING,DICT<STRING,VARIANT>> interfaces_and_properties); + dbus::Signal signal(dbus::kObjectManagerInterface, + dbus::kObjectManagerInterfacesAdded); + dbus::MessageWriter signal_writer(&signal); + dbus::MessageWriter all_interfaces(&signal); + dbus::MessageWriter each_interface(&signal); + signal_writer.AppendObjectPath(path); + signal_writer.OpenArray("{sa{sv}}", &all_interfaces); + all_interfaces.OpenDictEntry(&each_interface); + each_interface.AppendString(interface_name); + property_writer.Run(&each_interface); + all_interfaces.CloseContainer(&each_interface); + signal_writer.CloseContainer(&all_interfaces); + exported_object_->SendSignal(&signal); + registered_objects_[path][interface_name] = property_writer; +} + +void ExportedObjectManager::ReleaseInterface( + const dbus::ObjectPath& path, const std::string& interface_name) { + bus_->AssertOnOriginThread(); + auto interfaces_for_path_itr = registered_objects_.find(path); + CHECK(interfaces_for_path_itr != registered_objects_.end()) + << "Attempting to signal interface removal for path " << path.value() + << " which was never registered."; + auto interfaces_for_path = interfaces_for_path_itr->second; + auto property_for_interface_itr = interfaces_for_path.find(interface_name); + CHECK(property_for_interface_itr != interfaces_for_path.end()) + << "Attempted to remove interface " << interface_name << " from " + << path.value() << ", but this interface was never registered."; + interfaces_for_path.erase(interface_name); + if (interfaces_for_path.size() < 1) { + registered_objects_.erase(path); + } + // We're sending signals that look like: + // org.freedesktop.DBus.ObjectManager.InterfacesRemoved ( + // OBJPATH object_path, ARRAY<STRING> interfaces); + dbus::Signal signal(dbus::kObjectManagerInterface, + dbus::kObjectManagerInterfacesRemoved); + dbus::MessageWriter signal_writer(&signal); + signal_writer.AppendObjectPath(path); + dbus::MessageWriter interface_writer(nullptr); + signal_writer.OpenArray("s", &interface_writer); + interface_writer.AppendString(interface_name); + signal_writer.CloseContainer(&interface_writer); + exported_object_->SendSignal(&signal); +} + +void ExportedObjectManager::HandleGetManagedObjects( + dbus::MethodCall* method_call, + dbus::ExportedObject::ResponseSender response_sender) const { + // Implements the GetManagedObjects method: + // + // org.freedesktop.DBus.ObjectManager.GetManagedObjects ( + // out DICT<OBJPATH, + // DICT<STRING, + // DICT<STRING,VARIANT>>> ) + bus_->AssertOnOriginThread(); + scoped_ptr<dbus::Response> response( + dbus::Response::FromMethodCall(method_call)); + dbus::MessageWriter response_writer(response.get()); + dbus::MessageWriter all_object_paths(nullptr); + dbus::MessageWriter each_object_path(nullptr); + dbus::MessageWriter all_interfaces(nullptr); + dbus::MessageWriter each_interface(nullptr); + + response_writer.OpenArray("{oa{sa{sv}}}", &all_object_paths); + for (const auto path_pair : registered_objects_) { + const dbus::ObjectPath& path = path_pair.first; + const InterfaceProperties& interface2properties = path_pair.second; + all_object_paths.OpenDictEntry(&each_object_path); + each_object_path.AppendObjectPath(path); + each_object_path.OpenArray("{sa{sv}}", &all_interfaces); + for (const auto interface : interface2properties) { + const std::string& interface_name = interface.first; + const PropertyWriter& property_writer = interface.second; + all_interfaces.OpenDictEntry(&each_interface); + each_interface.AppendString(interface_name); + property_writer.Run(&each_interface); + all_interfaces.CloseContainer(&each_interface); + } + each_object_path.CloseContainer(&all_interfaces); + all_object_paths.CloseContainer(&each_object_path); + } + response_writer.CloseContainer(&all_object_paths); + response_sender.Run(response.Pass()); +} + +} // namespace dbus_utils + +} // namespace buffet
diff --git a/buffet/exported_object_manager.h b/buffet/exported_object_manager.h new file mode 100644 index 0000000..14a0b1a --- /dev/null +++ b/buffet/exported_object_manager.h
@@ -0,0 +1,118 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef EXPORTED_OBJECT_MANAGER_H_ +#define EXPORTED_OBJECT_MANAGER_H_ + +#include <map> +#include <string> + +#include <base/memory/weak_ptr.h> +#include <dbus/bus.h> +#include <dbus/exported_object.h> +#include <dbus/object_path.h> + +#include "buffet/exported_property_set.h" + +namespace buffet { + +namespace dbus_utils { + +// ExportedObjectManager is a delegate that implements the +// org.freedesktop.DBus.ObjectManager interface on behalf of another +// object. It handles sending signals when new interfaces are added. +// +// This class is very similar to the ExportedPropertySet class, except that +// it allows objects to expose an object manager interface rather than the +// properties interface. +// +// Example usage: +// +// class ExampleObjectManager { +// public: +// ExampleObjectManager(dbus::Bus* bus) +// : object_manager_(bus, "/my/objects/path") { } +// +// void Init(const OnInitFinish& cb) { object_manager_.Init(cb); } +// void ClaimInterface(const dbus::ObjectPath& path, +// const std::string& interface_name, +// const PropertyWriter& writer) { +// object_manager_->ClaimInterface(...); +// } +// void ReleaseInterface(const dbus::ObjectPath& path, +// const std::string& interface_name) { +// object_manager_->ReleaseInterface(...); +// } +// +// private: +// ExportedObjectManager object_manager_; +// }; +// +// class MyObjectClaimingAnInterface { +// public: +// MyObjectClaimingAnInterface(ExampleObjectManager* object_manager) +// : object_manager_(object_manager) {} +// +// void OnInitFinish(bool success) { +// if (!success) { /* handle that */ } +// object_manager_->ClaimInterface( +// my_path_, my_interface_, my_properties_.GetWriter()); +// } +// +// private: +// struct Properties : public ExportedPropertySet { +// public: +// /* Lots of interesting properties. */ +// }; +// +// Properties my_properties_; +// ExampleObjectManager* object_manager_; +// }; +class ExportedObjectManager { + public: + // Writes a dictionary of property name to property value variants to writer. + typedef base::Callback<void(dbus::MessageWriter* writer)> PropertyWriter; + typedef base::Callback<void(bool success)> OnInitFinish; + typedef std::map<std::string, PropertyWriter> InterfaceProperties; + + ExportedObjectManager(dbus::Bus* bus, const dbus::ObjectPath& path); + + // Registers methods implementing the ObjectManager interface on the object + // exported on the path given in the constructor. Must be called on the + // origin thread. + void Init(const OnInitFinish& cb); + + // Trigger a signal that |path| has added an interface |interface_name| + // with properties as given by |writer|. + void ClaimInterface(const dbus::ObjectPath& path, + const std::string& interface_name, + const PropertyWriter& writer); + + // Trigger a signal that |path| has removed an interface |interface_name|. + void ReleaseInterface(const dbus::ObjectPath& path, + const std::string& interface_name); + + private: + void HandleGetManagedObjects( + dbus::MethodCall* method_call, + dbus::ExportedObject::ResponseSender response_sender) const; + + // Both |bus_| and |exported_object_| outlive *this. + dbus::Bus* const bus_; + dbus::ExportedObject* const exported_object_; + // Tracks all objects currently known to the ExportedObjectManager. + std::map<dbus::ObjectPath, InterfaceProperties> registered_objects_; + + // We're going to register DBus callbacks that will outlive ourselves. + // These callbacks get scheduled on the origin thread. + base::WeakPtrFactory<ExportedObjectManager> weak_ptr_factory_; + friend class ExportedObjectManagerTest; + DISALLOW_COPY_AND_ASSIGN(ExportedObjectManager); +}; + +} // namespace dbus_utils + +} // namespace buffet + +#endif // EXPORTED_OBJECT_MANAGER_H_
diff --git a/buffet/exported_object_manager_unittest.cc b/buffet/exported_object_manager_unittest.cc new file mode 100644 index 0000000..696b76f --- /dev/null +++ b/buffet/exported_object_manager_unittest.cc
@@ -0,0 +1,205 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "buffet/exported_object_manager.h" + +#include <base/bind.h> +#include <dbus/mock_bus.h> +#include <dbus/mock_exported_object.h> +#include <dbus/object_manager.h> +#include <dbus/object_path.h> +#include <gtest/gtest.h> + +using ::testing::AnyNumber; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::Return; +using ::testing::_; + +namespace buffet { + +namespace dbus_utils { + +namespace { + +const dbus::ObjectPath kTestPath(std::string("/test/om_path")); +const dbus::ObjectPath kClaimedTestPath(std::string("/test/claimed_path")); +const std::string kClaimedInterface("claimed.interface"); +const std::string kTestPropertyName("PropertyName"); +const std::string kTestPropertyValue("PropertyValue"); + +void WriteTestPropertyDict(dbus::MessageWriter* writer) { + dbus::MessageWriter all_properties(nullptr); + dbus::MessageWriter each_property(nullptr); + writer->OpenArray("{sv}", &all_properties); + all_properties.OpenDictEntry(&each_property); + each_property.AppendString(kTestPropertyName); + each_property.AppendVariantOfString(kTestPropertyValue); + all_properties.CloseContainer(&each_property); + writer->CloseContainer(&all_properties); +} + +void ReadTestPropertyDict(dbus::MessageReader* reader) { + dbus::MessageReader all_properties(nullptr); + dbus::MessageReader each_property(nullptr); + ASSERT_TRUE(reader->PopArray(&all_properties)); + ASSERT_TRUE(all_properties.PopDictEntry(&each_property)); + std::string property_name; + std::string property_value; + ASSERT_TRUE(each_property.PopString(&property_name)); + ASSERT_TRUE(each_property.PopVariantOfString(&property_value)); + EXPECT_FALSE(each_property.HasMoreData()); + EXPECT_FALSE(all_properties.HasMoreData()); + EXPECT_EQ(property_name, kTestPropertyName); + EXPECT_EQ(property_value, kTestPropertyValue); +} + +void VerifyInterfaceClaimSignal(dbus::Signal* signal) { + EXPECT_EQ(signal->GetInterface(), + std::string(dbus::kObjectManagerInterface)); + EXPECT_EQ(signal->GetMember(), + std::string(dbus::kObjectManagerInterfacesAdded)); + // org.freedesktop.DBus.ObjectManager.InterfacesAdded ( + // OBJPATH object_path, + // DICT<STRING,DICT<STRING,VARIANT>> interfaces_and_properties); + dbus::MessageReader reader(signal); + dbus::MessageReader all_interfaces(nullptr); + dbus::MessageReader each_interface(nullptr); + dbus::ObjectPath path; + ASSERT_TRUE(reader.PopObjectPath(&path)); + ASSERT_TRUE(reader.PopArray(&all_interfaces)); + ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface)); + std::string interface_name; + ASSERT_TRUE(each_interface.PopString(&interface_name)); + ReadTestPropertyDict(&each_interface); + EXPECT_FALSE(each_interface.HasMoreData()); + EXPECT_FALSE(all_interfaces.HasMoreData()); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(interface_name, kClaimedInterface); + EXPECT_EQ(path, kClaimedTestPath); +} + +void VerifyInterfaceDropSignal(dbus::Signal* signal) { + EXPECT_EQ(signal->GetInterface(), + std::string(dbus::kObjectManagerInterface)); + EXPECT_EQ(signal->GetMember(), + std::string(dbus::kObjectManagerInterfacesRemoved)); + // org.freedesktop.DBus.ObjectManager.InterfacesRemoved ( + // OBJPATH object_path, ARRAY<STRING> interfaces); + dbus::MessageReader reader(signal); + dbus::MessageReader each_interface(nullptr); + dbus::ObjectPath path; + ASSERT_TRUE(reader.PopObjectPath(&path)); + ASSERT_TRUE(reader.PopArray(&each_interface)); + std::string interface_name; + ASSERT_TRUE(each_interface.PopString(&interface_name)); + EXPECT_FALSE(each_interface.HasMoreData()); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(interface_name, kClaimedInterface); + EXPECT_EQ(path, kClaimedTestPath); +} + +} // namespace + +class ExportedObjectManagerTest: public ::testing::Test { + public: + virtual void SetUp() { + dbus::Bus::Options options; + options.bus_type = dbus::Bus::SYSTEM; + bus_ = new dbus::MockBus(options); + // By default, don't worry about threading assertions. + EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); + EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); + // Use a mock exported object. + mock_exported_object_ = new dbus::MockExportedObject( + bus_.get(), kTestPath); + EXPECT_CALL(*bus_, GetExportedObject(kTestPath)) + .Times(1).WillOnce(Return(mock_exported_object_.get())); + om_.reset(new ExportedObjectManager(bus_.get(), kTestPath)); + property_writer_ = base::Bind(&WriteTestPropertyDict); + response_storer_ = base::Bind(&ExportedObjectManagerTest::StoreResponse, + base::Unretained(this)); + } + + void StoreResponse(scoped_ptr<dbus::Response> method_response) { + last_response_.reset(method_response.release()); + } + + void CallHandleGetManagedObjects( + dbus::MethodCall* method_call, + dbus::ExportedObject::ResponseSender sender) { + om_->HandleGetManagedObjects(method_call, response_storer_); + } + + scoped_refptr<dbus::MockBus> bus_; + scoped_refptr<dbus::MockExportedObject> mock_exported_object_; + scoped_ptr<ExportedObjectManager> om_; + ExportedObjectManager::PropertyWriter property_writer_; + dbus::ExportedObject::ResponseSender response_storer_; + scoped_ptr<dbus::Response> last_response_; +}; + +TEST_F(ExportedObjectManagerTest, ClaimInterfaceSendsSignals) { + EXPECT_CALL(*mock_exported_object_, SendSignal(_)) + .Times(1).WillOnce(Invoke(&VerifyInterfaceClaimSignal)); + om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_); +} + +TEST_F(ExportedObjectManagerTest, ReleaseInterfaceSendsSignals) { + InSequence dummy; + EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1); + EXPECT_CALL(*mock_exported_object_, SendSignal(_)) + .Times(1).WillOnce(Invoke(&VerifyInterfaceDropSignal)); + om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_); + om_->ReleaseInterface(kClaimedTestPath, kClaimedInterface); +} + +TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseEmptyCorrectness) { + dbus::MethodCall method_call(dbus::kObjectManagerInterface, + dbus::kObjectManagerGetManagedObjects); + method_call.SetSerial(123); + CallHandleGetManagedObjects(&method_call, response_storer_); + dbus::MessageReader reader(last_response_.get()); + dbus::MessageReader all_paths(nullptr); + ASSERT_TRUE(reader.PopArray(&all_paths)); + EXPECT_FALSE(reader.HasMoreData()); +} + +TEST_F(ExportedObjectManagerTest, GetManagedObjectsResponseCorrectness) { + // org.freedesktop.DBus.ObjectManager.GetManagedObjects ( + // out DICT<OBJPATH, + // DICT<STRING, + // DICT<STRING,VARIANT>>> ) + dbus::MethodCall method_call(dbus::kObjectManagerInterface, + dbus::kObjectManagerGetManagedObjects); + method_call.SetSerial(123); + EXPECT_CALL(*mock_exported_object_, SendSignal(_)).Times(1); + om_->ClaimInterface(kClaimedTestPath, kClaimedInterface, property_writer_); + CallHandleGetManagedObjects(&method_call, response_storer_); + dbus::MessageReader reader(last_response_.get()); + dbus::MessageReader all_paths(nullptr); + dbus::MessageReader each_path(nullptr); + dbus::MessageReader all_interfaces(nullptr); + dbus::MessageReader each_interface(nullptr); + ASSERT_TRUE(reader.PopArray(&all_paths)); + ASSERT_TRUE(all_paths.PopDictEntry(&each_path)); + dbus::ObjectPath path; + ASSERT_TRUE(each_path.PopObjectPath(&path)); + ASSERT_TRUE(each_path.PopArray(&all_interfaces)); + ASSERT_TRUE(all_interfaces.PopDictEntry(&each_interface)); + std::string interface_name; + ASSERT_TRUE(each_interface.PopString(&interface_name)); + ReadTestPropertyDict(&each_interface); + EXPECT_FALSE(each_interface.HasMoreData()); + EXPECT_FALSE(all_interfaces.HasMoreData()); + EXPECT_FALSE(each_path.HasMoreData()); + EXPECT_FALSE(all_paths.HasMoreData()); + EXPECT_FALSE(reader.HasMoreData()); + EXPECT_EQ(path, kClaimedTestPath); + EXPECT_EQ(interface_name, kClaimedInterface); +} + +} // namespace dbus_utils + +} // namespace buffet
diff --git a/buffet/exported_property_set.cc b/buffet/exported_property_set.cc index f6f7037..ba6b2c7 100644 --- a/buffet/exported_property_set.cc +++ b/buffet/exported_property_set.cc
@@ -84,28 +84,33 @@ GetBadArgsError(method_call, "Too many arguments to GetAll.")); return; } - auto property_map_itr = properties_.find(interface_name); - if (property_map_itr == properties_.end()) { - response_sender.Run( - GetBadArgsError(method_call, "No such interface on object.")); - return; - } scoped_ptr<dbus::Response> response( dbus::Response::FromMethodCall(method_call)); dbus::MessageWriter resp_writer(response.get()); - dbus::MessageWriter dict_writer(nullptr); - resp_writer.OpenArray("{sv}", &dict_writer); - for (const auto& kv : property_map_itr->second) { - dbus::MessageWriter entry_writer(nullptr); - dict_writer.OpenDictEntry(&entry_writer); - entry_writer.AppendString(kv.first); - kv.second->AppendValueToWriter(&entry_writer); - dict_writer.CloseContainer(&entry_writer); - } - resp_writer.CloseContainer(&dict_writer); + WritePropertiesDictToMessage(interface_name, &resp_writer); response_sender.Run(response.Pass()); } +void ExportedPropertySet::WritePropertiesDictToMessage( + const std::string& interface_name, + dbus::MessageWriter* writer) { + dbus::MessageWriter dict_writer(nullptr); + writer->OpenArray("{sv}", &dict_writer); + auto property_map_itr = properties_.find(interface_name); + if (property_map_itr != properties_.end()) { + for (const auto& kv : property_map_itr->second) { + dbus::MessageWriter entry_writer(nullptr); + dict_writer.OpenDictEntry(&entry_writer); + entry_writer.AppendString(kv.first); + kv.second->AppendValueToWriter(&entry_writer); + dict_writer.CloseContainer(&entry_writer); + } + } else { + LOG(WARNING) << "No properties found for interface interface_name"; + } + writer->CloseContainer(&dict_writer); +} + void ExportedPropertySet::HandleGet( dbus::MethodCall* method_call, dbus::ExportedObject::ResponseSender response_sender) {
diff --git a/buffet/exported_property_set.h b/buffet/exported_property_set.h index c464a8b..8e535b6 100644 --- a/buffet/exported_property_set.h +++ b/buffet/exported_property_set.h
@@ -11,7 +11,6 @@ #include <base/memory/weak_ptr.h> #include <dbus/exported_object.h> #include <dbus/message.h> -#include <gtest/gtest_prod.h> namespace buffet { @@ -105,6 +104,8 @@ // are exported to the DBus object. |cb| will be called on the origin // thread. void Init(const OnInitFinish& cb); + base::Callback<void(dbus::MessageWriter* writer)> GetPropertyWriter( + const std::string& interface); protected: void RegisterProperty(const std::string& interface_name, @@ -112,6 +113,11 @@ ExportedPropertyBase* exported_property); private: + // Used to write the dictionary of string->variant to a message. + // This dictionary represents the property name/value pairs for the + // given interface. + void WritePropertiesDictToMessage(const std::string& interface_name, + dbus::MessageWriter* writer); void HandleGetAll(dbus::MethodCall* method_call, dbus::ExportedObject::ResponseSender response_sender); void HandleGet(dbus::MethodCall* method_call,
diff --git a/buffet/exported_property_set_unittest.cc b/buffet/exported_property_set_unittest.cc index 959a46a..7687371 100644 --- a/buffet/exported_property_set_unittest.cc +++ b/buffet/exported_property_set_unittest.cc
@@ -201,11 +201,22 @@ } TEST_F(ExportedPropertySetTest, GetAllInvalidInterface) { - dbus::MethodCall method_call(dbus::kPropertiesInterface, - dbus::kPropertiesGetAll); + dbus::MethodCall method_call( + dbus::kPropertiesInterface, dbus::kPropertiesGetAll); + method_call.SetSerial(123); dbus::MessageWriter writer(&method_call); writer.AppendString("org.chromium.BadInterface"); - AssertGetAllReturnsError(&method_call); + auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse, + base::Unretained(this)); + p_->CallHandleGetAll(&method_call, response_sender); + dbus::MessageReader response_reader(last_response_.get()); + dbus::MessageReader dict_reader(nullptr); + ASSERT_TRUE(response_reader.PopArray(&dict_reader)); + // The response should just be a an empty array, since there are no properties + // on this interface. The spec doesn't say much about error conditions here, + // so I'm going to assume this is a valid implementation. + ASSERT_FALSE(dict_reader.HasMoreData()); + ASSERT_FALSE(response_reader.HasMoreData()); } TEST_F(ExportedPropertySetTest, GetAllExtraArgs) {