buffet: Add ExportedPropertySet delegate
This object makes it easy to export an org.freedesktop.DBus.Properties
interface.
BUG=chromium:356368
TEST=When integrated with the Manager, this correctly exposes
properties. This can be tested with buffet_BasicDBusAPI.
Change-Id: I6c871ebbd225b6305ca9d4a309fb7b47ed305f9b
Reviewed-on: https://chromium-review.googlesource.com/192001
Tested-by: Christopher Wiley <wiley@chromium.org>
Reviewed-by: Chris Sosa <sosa@chromium.org>
Reviewed-by: Alex Vakulenko <avakulenko@chromium.org>
Commit-Queue: Christopher Wiley <wiley@chromium.org>
diff --git a/buffet/buffet.gyp b/buffet/buffet.gyp
index d396525..f44d8dc 100644
--- a/buffet/buffet.gyp
+++ b/buffet/buffet.gyp
@@ -34,9 +34,10 @@
'type': 'static_library',
'sources': [
'data_encoding.cc',
- 'dbus_manager.cc',
'dbus_constants.cc',
+ 'dbus_manager.cc',
'dbus_utils.cc',
+ 'exported_property_set.cc',
'http_request.cc',
'http_transport_curl.cc',
'http_utils.cc',
@@ -73,6 +74,7 @@
'sources': [
'buffet_testrunner.cc',
'data_encoding_unittest.cc',
+ 'exported_property_set_unittest.cc',
'mime_utils_unittest.cc',
'string_utils_unittest.cc',
],
diff --git a/buffet/exported_property_set.cc b/buffet/exported_property_set.cc
new file mode 100644
index 0000000..fe1ef65
--- /dev/null
+++ b/buffet/exported_property_set.cc
@@ -0,0 +1,283 @@
+// 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_property_set.h"
+
+#include <base/bind.h>
+#include <dbus/property.h> // For kPropertyInterface
+
+#include "buffet/dbus_utils.h"
+
+namespace buffet {
+
+namespace dbus_utils {
+
+ExportedPropertySet::ExportedPropertySet(dbus::ExportedObject* exported_object)
+ : exported_object_(exported_object), weak_ptr_factory_(this) { }
+
+void ExportedPropertySet::ClaimPropertiesInterface() {
+ exported_object_->ExportMethodAndBlock(
+ dbus::kPropertiesInterface, dbus::kPropertiesGetAll,
+ base::Bind(&ExportedPropertySet::HandleGetAll,
+ weak_ptr_factory_.GetWeakPtr()));
+ exported_object_->ExportMethodAndBlock(
+ dbus::kPropertiesInterface, dbus::kPropertiesGet,
+ base::Bind(&ExportedPropertySet::HandleGet,
+ weak_ptr_factory_.GetWeakPtr()));
+ exported_object_->ExportMethodAndBlock(
+ dbus::kPropertiesInterface, dbus::kPropertiesSet,
+ base::Bind(&ExportedPropertySet::HandleSet,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+ExportedPropertySet::~ExportedPropertySet() { }
+
+void ExportedPropertySet::RegisterProperty(
+ const std::string& interface_name,
+ const std::string& property_name,
+ ExportedPropertyBase* exported_property) {
+ properties_[interface_name][property_name] = exported_property;
+ // Technically, the property set exists longer than the properties themselves,
+ // so we could use Unretained here rather than a weak pointer.
+ ExportedPropertyBase::OnUpdateCallback cb = base::Bind(
+ &ExportedPropertySet::HandlePropertyUpdated,
+ weak_ptr_factory_.GetWeakPtr(),
+ interface_name, property_name);
+ exported_property->SetUpdateCallback(cb);
+}
+
+void ExportedPropertySet::HandleGetAll(
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ dbus::MessageReader reader(method_call);
+ std::string interface_name;
+ if (!reader.PopString(&interface_name)) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "No interface name specified."));
+ return;
+ }
+ if (reader.HasMoreData()) {
+ response_sender.Run(
+ 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);
+ response_sender.Run(response.Pass());
+}
+
+void ExportedPropertySet::HandleGet(
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ dbus::MessageReader reader(method_call);
+ std::string interface_name, property_name;
+ if (!reader.PopString(&interface_name)) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "No interface name specified."));
+ return;
+ }
+ if (!reader.PopString(&property_name)) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "No property name specified."));
+ return;
+ }
+ if (reader.HasMoreData()) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "Too many arguments to Get."));
+ 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;
+ }
+ LOG(ERROR) << "Looking for " << property_name << " on " << interface_name;
+ auto property_itr = property_map_itr->second.find(property_name);
+ if (property_itr == property_map_itr->second.end()) {
+ response_sender.Run(
+ GetBadArgsError(method_call, "No such property on interface."));
+ return;
+ }
+ scoped_ptr<dbus::Response> response(
+ dbus::Response::FromMethodCall(method_call));
+ dbus::MessageWriter resp_writer(response.get());
+ property_itr->second->AppendValueToWriter(&resp_writer);
+ response_sender.Run(response.Pass());
+}
+
+void ExportedPropertySet::HandleSet(
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ scoped_ptr<dbus::ErrorResponse> error_resp(
+ dbus::ErrorResponse::FromMethodCall(
+ method_call, "org.freedesktop.DBus.Error.NotSupported", ""));
+ scoped_ptr<dbus::Response> response(error_resp.release());
+ response_sender.Run(response.Pass());
+}
+
+void ExportedPropertySet::HandlePropertyUpdated(
+ const std::string& interface,
+ const std::string& name,
+ const ExportedPropertyBase* property) {
+ // TODO(wiley): Send a signal from the exported object here.
+}
+
+template <typename T>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const T& value);
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const bool& value) {
+ writer->AppendVariantOfBool(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint8& value) {
+ writer->AppendVariantOfByte(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const int16& value) {
+ writer->AppendVariantOfInt16(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint16& value) {
+ writer->AppendVariantOfUint16(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const int32& value) {
+ writer->AppendVariantOfInt32(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint32& value) {
+ writer->AppendVariantOfUint32(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const int64& value) {
+ writer->AppendVariantOfInt64(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const uint64& value) {
+ writer->AppendVariantOfUint64(value);
+}
+
+template <>
+void AppendPropertyToWriter(dbus::MessageWriter* writer, const double& value) {
+ writer->AppendVariantOfDouble(value);
+}
+
+template <>
+void AppendPropertyToWriter(
+ dbus::MessageWriter* writer, const std::string& value) {
+ writer->AppendVariantOfString(value);
+}
+
+template <>
+void AppendPropertyToWriter(
+ dbus::MessageWriter* writer, const dbus::ObjectPath& value) {
+ writer->AppendVariantOfObjectPath(value);
+}
+
+template <>
+void AppendPropertyToWriter(
+ dbus::MessageWriter* writer, const std::vector<std::string>& value) {
+ dbus::MessageWriter variant_writer(nullptr);
+ writer->OpenVariant("as", &variant_writer);
+ variant_writer.AppendArrayOfStrings(value);
+ writer->CloseContainer(&variant_writer);
+}
+
+template <>
+void AppendPropertyToWriter(
+ dbus::MessageWriter* writer, const std::vector<dbus::ObjectPath>& value) {
+ dbus::MessageWriter variant_writer(nullptr);
+ writer->OpenVariant("ao", &variant_writer);
+ variant_writer.AppendArrayOfObjectPaths(value);
+ writer->CloseContainer(&variant_writer);
+}
+
+template <>
+void AppendPropertyToWriter(
+ dbus::MessageWriter* writer, const std::vector<uint8>& value) {
+ dbus::MessageWriter variant_writer(nullptr);
+ writer->OpenVariant("ay", &variant_writer);
+ variant_writer.AppendArrayOfBytes(value.data(), value.size());
+ writer->CloseContainer(&variant_writer);
+}
+
+template <typename T>
+ExportedProperty<T>::ExportedProperty() {}
+
+template <typename T>
+ExportedProperty<T>::~ExportedProperty() {}
+
+template <typename T>
+const T& ExportedProperty<T>::value() const { return value_; }
+
+template <typename T>
+void ExportedProperty<T>::SetValue(const T& new_value) {
+ if (value_ == new_value) {
+ return;
+ }
+ value_ = new_value;
+ // These is a brief period after the construction of an ExportedProperty
+ // when this callback is not initialized because the property has not
+ // been registered with the parent ExportedPropertySet. During this period
+ // users should be initializing values via SetValue, and no notifications
+ // should be triggered by the ExportedPropertySet.
+ if (!on_update_.is_null()) {
+ on_update_.Run(this);
+ }
+}
+
+template <typename T>
+void ExportedProperty<T>::SetUpdateCallback(const OnUpdateCallback& cb) {
+ on_update_ = cb;
+}
+
+template <typename T>
+void ExportedProperty<T>::AppendValueToWriter(dbus::MessageWriter* writer) {
+ AppendPropertyToWriter(writer, value_);
+}
+
+template class ExportedProperty<bool>;
+template class ExportedProperty<uint8>;
+template class ExportedProperty<int16>;
+template class ExportedProperty<uint16>;
+template class ExportedProperty<int32>;
+template class ExportedProperty<uint32>;
+template class ExportedProperty<int64>;
+template class ExportedProperty<uint64>;
+template class ExportedProperty<double>;
+template class ExportedProperty<std::string>;
+template class ExportedProperty<dbus::ObjectPath>;
+template class ExportedProperty<std::vector<std::string>>;
+template class ExportedProperty<std::vector<dbus::ObjectPath>>;
+template class ExportedProperty<std::vector<uint8>>;
+
+} // namespace dbus_utils
+
+} // namespace buffet
diff --git a/buffet/exported_property_set.h b/buffet/exported_property_set.h
new file mode 100644
index 0000000..7ed9d9b
--- /dev/null
+++ b/buffet/exported_property_set.h
@@ -0,0 +1,185 @@
+// 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 BUFFET_EXPORTED_PROPERTY_SET_H_
+#define BUFFET_EXPORTED_PROPERTY_SET_H_
+
+#include <map>
+#include <string>
+
+#include <base/memory/weak_ptr.h>
+#include <dbus/exported_object.h>
+#include <dbus/message.h>
+#include <gtest/gtest_prod.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+// This class may be used to implement the org.freedesktop.DBus.Properties
+// interface. It sends the update signal on property updates:
+//
+// org.freedesktop.DBus.Properties.PropertiesChanged (
+// STRING interface_name,
+// DICT<STRING,VARIANT> changed_properties,
+// ARRAY<STRING> invalidated_properties);
+//
+//
+// and implements the required methods of the interface:
+//
+// org.freedesktop.DBus.Properties.Get(in STRING interface_name,
+// in STRING property_name,
+// out VARIANT value);
+// org.freedesktop.DBus.Properties.Set(in STRING interface_name,
+// in STRING property_name,
+// in VARIANT value);
+// org.freedesktop.DBus.Properties.GetAll(in STRING interface_name,
+// out DICT<STRING,VARIANT> props);
+//
+// This class is very similar to the PropertySet class in Chrome, except that
+// it allows objects to expose properties rather than to consume them.
+//
+// Example usage:
+//
+// class ExampleObjectExportingProperties {
+// public:
+// ExampleObjectExportingProperties(ExportedObject* exported_object)
+// : p_(exported_object) {
+// // Initialize properties appropriately. Do this before
+// // claiming the Properties interface so that daemons watching
+// // this object don't see partial or inaccurate state.
+// p_.ClaimPropertiesInterface();
+// }
+//
+// private:
+// struct Properties : public buffet::dbus::ExportedPropertySet {
+// public:
+// buffet::dbus::ExportedProperty<std::string> name_;
+// buffet::dbus::ExportedProperty<uint16> version_;
+// buffet::dbus::ExportedProperty<dbus::ObjectPath> parent_;
+// buffet::dbus::ExportedProperty<std::vector<std::string>> children_;
+//
+// Properties(dbus::ExportedObject* exported_object)
+// : buffet::dbus::ExportedPropertySet(exported_object) {
+// RegisterProperty(kExampleInterfaceName, "Name", &name_);
+// RegisterProperty(kExampleInterfaceName, "Version", &version_);
+// RegisterProperty(kExampleInterfaceName, "Parent", &parent_);
+// RegisterProperty(kExampleInterfaceName, "Children", &children_);
+// }
+// virtual ~Properties() {}
+// };
+//
+// Properties p_;
+// };
+
+class ExportedPropertyBase {
+ public:
+ ExportedPropertyBase() {}
+ virtual ~ExportedPropertyBase() {}
+
+ typedef base::Callback<void(const ExportedPropertyBase*)> OnUpdateCallback;
+
+ // Called by ExportedPropertySet to register a callback. This callback
+ // triggers ExportedPropertySet to send a signal from the properties
+ // interface of the exported object.
+ virtual void SetUpdateCallback(const OnUpdateCallback& cb) = 0;
+
+ // Appends a variant of the contained value to the writer. This is
+ // needed to write out properties to Get and GetAll methods implemented
+ // by the ExportedPropertySet since it doesn't actually know the type
+ // of each property.
+ virtual void AppendValueToWriter(dbus::MessageWriter* writer) = 0;
+};
+
+class ExportedPropertySet {
+ public:
+ ExportedPropertySet(dbus::ExportedObject* exported_object);
+ ~ExportedPropertySet();
+
+ // Claims the org.freedesktop.DBus.Properties interface. This
+ // needs to be done after all properties are initialized to
+ // appropriate values.
+ void ClaimPropertiesInterface();
+
+ protected:
+ void RegisterProperty(const std::string& interface_name,
+ const std::string& property_name,
+ ExportedPropertyBase* exported_property);
+
+ private:
+ void HandleGetAll(dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender);
+ void HandleGet(dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender);
+ // While Properties.Set has a handler to complete the interface, we don't
+ // support writable properties. This is almost a feature, since bindings for
+ // many languages don't support errors coming back from invalid writes.
+ // Instead, use setters in exposed interfaces.
+ void HandleSet(dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender);
+
+ virtual void HandlePropertyUpdated(const std::string& interface,
+ const std::string& name,
+ const ExportedPropertyBase* property);
+
+ dbus::ExportedObject* exported_object_; // weak; owned by the Bus object.
+ // This is a map from interface name -> property name -> pointer to property.
+ std::map<std::string,
+ std::map<std::string, ExportedPropertyBase*>> properties_;
+
+ // D-Bus callbacks may last longer the property set exporting those methods.
+ base::WeakPtrFactory<ExportedPropertySet> weak_ptr_factory_;
+
+ friend class ExportedPropertySetTest;
+ DISALLOW_COPY_AND_ASSIGN(ExportedPropertySet);
+};
+
+template <typename T>
+class ExportedProperty : public ExportedPropertyBase {
+ public:
+ ExportedProperty();
+ virtual ~ExportedProperty() override;
+
+ // Retrieves the current value.
+ const T& value() const;
+
+ // Set the value exposed to remote applications. This triggers notifications
+ // of changes over the Properties interface.
+ void SetValue(const T& new_value);
+
+ // Called by ExportedPropertySet. This update callback triggers
+ // ExportedPropertySet to send a signal from the properties interface of the
+ // exported object.
+ virtual void SetUpdateCallback(const OnUpdateCallback& cb) override;
+
+ // Implementation provided by specialization.
+ virtual void AppendValueToWriter(dbus::MessageWriter* writer) override;
+
+ private:
+ OnUpdateCallback on_update_;
+ T value_{};
+
+ DISALLOW_COPY_AND_ASSIGN(ExportedProperty);
+};
+
+extern template class ExportedProperty<bool>;
+extern template class ExportedProperty<uint8>;
+extern template class ExportedProperty<int16>;
+extern template class ExportedProperty<uint16>;
+extern template class ExportedProperty<int32>;
+extern template class ExportedProperty<uint32>;
+extern template class ExportedProperty<int64>;
+extern template class ExportedProperty<uint64>;
+extern template class ExportedProperty<double>;
+extern template class ExportedProperty<std::string>;
+extern template class ExportedProperty<dbus::ObjectPath>;
+extern template class ExportedProperty<std::vector<std::string>>;
+extern template class ExportedProperty<std::vector<dbus::ObjectPath>>;
+extern template class ExportedProperty<std::vector<uint8>>;
+
+} // namespace dbus_utils
+
+} // namespace buffet
+
+#endif // BUFFET_EXPORTED_PROPERTY_SET_H_
diff --git a/buffet/exported_property_set_unittest.cc b/buffet/exported_property_set_unittest.cc
new file mode 100644
index 0000000..257614e
--- /dev/null
+++ b/buffet/exported_property_set_unittest.cc
@@ -0,0 +1,475 @@
+// 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_property_set.h"
+
+#include <string>
+#include <vector>
+
+#include <base/basictypes.h>
+#include <base/bind.h>
+#include <dbus/message.h>
+#include <dbus/property.h>
+#include <dbus/object_path.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace buffet {
+
+namespace dbus_utils {
+
+namespace {
+
+const char kBoolPropName[] = "BoolProp";
+const char kUint8PropName[] = "Uint8Prop";
+const char kInt16PropName[] = "Int16Prop";
+const char kUint16PropName[] = "Uint16Prop";
+const char kInt32PropName[] = "Int32Prop";
+const char kUint32PropName[] = "Uint32Prop";
+const char kInt64PropName[] = "Int64Prop";
+const char kUint64PropName[] = "Uint64Prop";
+const char kDoublePropName[] = "DoubleProp";
+const char kStringPropName[] = "StringProp";
+const char kPathPropName[] = "PathProp";
+const char kStringListPropName[] = "StringListProp";
+const char kPathListPropName[] = "PathListProp";
+const char kUint8ListPropName[] = "Uint8ListProp";
+
+const char kTestInterface1[] = "org.chromium.TestInterface1";
+const char kTestInterface2[] = "org.chromium.TestInterface2";
+const char kTestInterface3[] = "org.chromium.TestInterface3";
+
+const std::string kTestString("lies");
+const dbus::ObjectPath kTestObjectPathInit(std::string("/path_init"));
+const dbus::ObjectPath kTestObjectPathUpdate(std::string("/path_update"));
+
+} // namespace
+
+class ExportedPropertySetTest : public ::testing::Test {
+ public:
+ ExportedPropertySetTest() {}
+ struct Properties : public ExportedPropertySet {
+ public:
+ ExportedProperty<bool> bool_prop_;
+ ExportedProperty<uint8> uint8_prop_;
+ ExportedProperty<int16> int16_prop_;
+ ExportedProperty<uint16> uint16_prop_;
+ ExportedProperty<int32> int32_prop_;
+ ExportedProperty<uint32> uint32_prop_;
+ ExportedProperty<int64> int64_prop_;
+ ExportedProperty<uint64> uint64_prop_;
+ ExportedProperty<double> double_prop_;
+ ExportedProperty<std::string> string_prop_;
+ ExportedProperty<dbus::ObjectPath> path_prop_;
+ ExportedProperty<std::vector<std::string>> stringlist_prop_;
+ ExportedProperty<std::vector<dbus::ObjectPath>> pathlist_prop_;
+ ExportedProperty<std::vector<uint8>> uint8list_prop_;
+
+ Properties() : ExportedPropertySet(nullptr) {
+ // The empty string is not a valid value for an ObjectPath.
+ path_prop_.SetValue(kTestObjectPathInit);
+ RegisterProperty(kTestInterface1, kBoolPropName, &bool_prop_);
+ RegisterProperty(kTestInterface1, kUint8PropName, &uint8_prop_);
+ RegisterProperty(kTestInterface1, kInt16PropName, &int16_prop_);
+ // I chose this weird grouping because N=2 is about all the permutations
+ // of GetAll that I want to anticipate.
+ RegisterProperty(kTestInterface2, kUint16PropName, &uint16_prop_);
+ RegisterProperty(kTestInterface2, kInt32PropName, &int32_prop_);
+ RegisterProperty(kTestInterface3, kUint32PropName, &uint32_prop_);
+ RegisterProperty(kTestInterface3, kInt64PropName, &int64_prop_);
+ RegisterProperty(kTestInterface3, kUint64PropName, &uint64_prop_);
+ RegisterProperty(kTestInterface3, kDoublePropName, &double_prop_);
+ RegisterProperty(kTestInterface3, kStringPropName, &string_prop_);
+ RegisterProperty(kTestInterface3, kPathPropName, &path_prop_);
+ RegisterProperty(kTestInterface3, kStringListPropName, &stringlist_prop_);
+ RegisterProperty(kTestInterface3, kPathListPropName, &pathlist_prop_);
+ RegisterProperty(kTestInterface3, kUint8ListPropName, &uint8list_prop_);
+ }
+ virtual ~Properties() {}
+
+ void CallHandleGetAll(
+ dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ HandleGetAll(method_call, response_sender);
+ }
+
+ void CallHandleGet(dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ HandleGet(method_call, response_sender);
+ }
+
+ void CallHandleSet(dbus::MethodCall* method_call,
+ dbus::ExportedObject::ResponseSender response_sender) {
+ HandleSet(method_call, response_sender);
+ }
+
+ MOCK_METHOD3(PropertyUpdated, void(const std::string&, const std::string&,
+ const ExportedPropertyBase*));
+
+ private:
+ virtual void HandlePropertyUpdated(
+ const std::string& interface,
+ const std::string& name,
+ const ExportedPropertyBase* property) override {
+ PropertyUpdated(interface, name, property);
+ }
+ };
+
+ void StoreResponse(scoped_ptr<dbus::Response> method_response) {
+ last_response_.reset(method_response.release());
+ }
+
+ void AssertGetAllReturnsError(dbus::MethodCall* method_call) {
+ auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+ base::Unretained(this));
+ method_call->SetSerial(123);
+ p_.CallHandleGetAll(method_call, response_sender);
+ ASSERT_NE(dynamic_cast<dbus::ErrorResponse*>(last_response_.get()),
+ nullptr);
+ }
+
+ void AssertGetReturnsError(dbus::MethodCall* method_call) {
+ auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+ base::Unretained(this));
+ method_call->SetSerial(123);
+ p_.CallHandleGet(method_call, response_sender);
+ ASSERT_NE(dynamic_cast<dbus::ErrorResponse*>(last_response_.get()),
+ nullptr);
+ }
+
+ scoped_ptr<dbus::Response> GetPropertyOnInterface(
+ const std::string& interface_name, const std::string& property_name) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(interface_name);
+ writer.AppendString(property_name);
+ auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+ base::Unretained(this));
+ p_.CallHandleGet(&method_call, response_sender);
+ return last_response_.Pass();
+ }
+
+ scoped_ptr<dbus::Response> last_response_;
+ Properties p_;
+};
+
+TEST_F(ExportedPropertySetTest, UpdateNotifications) {
+ ::testing::InSequence dummy;
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface1, kBoolPropName,
+ &p_.bool_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface1, kUint8PropName,
+ &p_.uint8_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface1, kInt16PropName,
+ &p_.int16_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface2, kUint16PropName,
+ &p_.uint16_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface2, kInt32PropName,
+ &p_.int32_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kUint32PropName,
+ &p_.uint32_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kInt64PropName,
+ &p_.int64_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kUint64PropName,
+ &p_.uint64_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kDoublePropName,
+ &p_.double_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kStringPropName,
+ &p_.string_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kPathPropName,
+ &p_.path_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kStringListPropName,
+ &p_.stringlist_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kPathListPropName,
+ &p_.pathlist_prop_));
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface3, kUint8ListPropName,
+ &p_.uint8list_prop_));
+ p_.bool_prop_.SetValue(true);
+ p_.uint8_prop_.SetValue(1);
+ p_.int16_prop_.SetValue(1);
+ p_.uint16_prop_.SetValue(1);
+ p_.int32_prop_.SetValue(1);
+ p_.uint32_prop_.SetValue(1);
+ p_.int64_prop_.SetValue(1);
+ p_.uint64_prop_.SetValue(1);
+ p_.double_prop_.SetValue(1.0);
+ p_.string_prop_.SetValue(kTestString);
+ p_.path_prop_.SetValue(kTestObjectPathUpdate);
+ p_.stringlist_prop_.SetValue({kTestString});
+ p_.pathlist_prop_.SetValue({kTestObjectPathUpdate});
+ p_.uint8list_prop_.SetValue({1});
+}
+
+TEST_F(ExportedPropertySetTest, UpdateToSameValue) {
+ EXPECT_CALL(p_, PropertyUpdated(kTestInterface1, kBoolPropName,
+ &p_.bool_prop_)).Times(1);
+ p_.bool_prop_.SetValue(true);
+ p_.bool_prop_.SetValue(true);
+}
+
+TEST_F(ExportedPropertySetTest, GetAllNoArgs) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGetAll);
+ AssertGetAllReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetAllInvalidInterface) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGetAll);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString("org.chromium.BadInterface");
+ AssertGetAllReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetAllExtraArgs) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGetAll);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface1);
+ writer.AppendString(kTestInterface1);
+ AssertGetAllReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetAllCorrectness) {
+ dbus::MethodCall method_call(
+ dbus::kPropertiesInterface, dbus::kPropertiesGetAll);
+ method_call.SetSerial(123);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface2);
+ 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);
+ dbus::MessageReader entry_reader(nullptr);
+ ASSERT_TRUE(response_reader.PopArray(&dict_reader));
+ ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader));
+ std::string property_name;
+ ASSERT_TRUE(entry_reader.PopString(&property_name));
+ uint16 value16;
+ int32 value32;
+ if (property_name.compare(kUint16PropName) == 0) {
+ ASSERT_TRUE(entry_reader.PopVariantOfUint16(&value16));
+ ASSERT_FALSE(entry_reader.HasMoreData());
+ ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader));
+ ASSERT_TRUE(entry_reader.PopString(&property_name));
+ ASSERT_EQ(property_name.compare(kInt32PropName), 0);
+ ASSERT_TRUE(entry_reader.PopVariantOfInt32(&value32));
+ } else {
+ ASSERT_EQ(property_name.compare(kInt32PropName), 0);
+ ASSERT_TRUE(entry_reader.PopVariantOfInt32(&value32));
+ ASSERT_FALSE(entry_reader.HasMoreData());
+ ASSERT_TRUE(dict_reader.PopDictEntry(&entry_reader));
+ ASSERT_TRUE(entry_reader.PopString(&property_name));
+ ASSERT_EQ(property_name.compare(kUint16PropName), 0);
+ ASSERT_TRUE(entry_reader.PopVariantOfUint16(&value16));
+ }
+ ASSERT_FALSE(entry_reader.HasMoreData());
+ ASSERT_FALSE(dict_reader.HasMoreData());
+ ASSERT_FALSE(response_reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetNoArgs) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetInvalidInterface) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString("org.chromium.BadInterface");
+ writer.AppendString(kInt16PropName);
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetBadPropertyName) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface1);
+ writer.AppendString("IAmNotAProperty");
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetPropIfMismatch) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface1);
+ writer.AppendString(kStringPropName);
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetNoPropertyName) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface1);
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetExtraArgs) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesGet);
+ dbus::MessageWriter writer(&method_call);
+ writer.AppendString(kTestInterface1);
+ writer.AppendString(kBoolPropName);
+ writer.AppendString("Extra param");
+ AssertGetReturnsError(&method_call);
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithBool) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface1, kBoolPropName);
+ dbus::MessageReader reader(response.get());
+ bool value;
+ ASSERT_TRUE(reader.PopVariantOfBool(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint8) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface1, kUint8PropName);
+ dbus::MessageReader reader(response.get());
+ uint8 value;
+ ASSERT_TRUE(reader.PopVariantOfByte(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt16) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface1, kInt16PropName);
+ dbus::MessageReader reader(response.get());
+ int16 value;
+ ASSERT_TRUE(reader.PopVariantOfInt16(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint16) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface2, kUint16PropName);
+ dbus::MessageReader reader(response.get());
+ uint16 value;
+ ASSERT_TRUE(reader.PopVariantOfUint16(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt32) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface2, kInt32PropName);
+ dbus::MessageReader reader(response.get());
+ int32 value;
+ ASSERT_TRUE(reader.PopVariantOfInt32(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint32) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kUint32PropName);
+ dbus::MessageReader reader(response.get());
+ uint32 value;
+ ASSERT_TRUE(reader.PopVariantOfUint32(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithInt64) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kInt64PropName);
+ dbus::MessageReader reader(response.get());
+ int64 value;
+ ASSERT_TRUE(reader.PopVariantOfInt64(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint64) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kUint64PropName);
+ dbus::MessageReader reader(response.get());
+ uint64 value;
+ ASSERT_TRUE(reader.PopVariantOfUint64(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithDouble) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kDoublePropName);
+ dbus::MessageReader reader(response.get());
+ double value;
+ ASSERT_TRUE(reader.PopVariantOfDouble(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithString) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kStringPropName);
+ dbus::MessageReader reader(response.get());
+ std::string value;
+ ASSERT_TRUE(reader.PopVariantOfString(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithPath) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kPathPropName);
+ dbus::MessageReader reader(response.get());
+ dbus::ObjectPath value;
+ ASSERT_TRUE(reader.PopVariantOfObjectPath(&value));
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithStringList) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kStringListPropName);
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(nullptr);
+ std::vector<std::string> value;
+ ASSERT_TRUE(reader.PopVariant(&variant_reader));
+ ASSERT_TRUE(variant_reader.PopArrayOfStrings(&value));
+ ASSERT_FALSE(variant_reader.HasMoreData());
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithPathList) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kPathListPropName);
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(nullptr);
+ std::vector<dbus::ObjectPath> value;
+ ASSERT_TRUE(reader.PopVariant(&variant_reader));
+ ASSERT_TRUE(variant_reader.PopArrayOfObjectPaths(&value));
+ ASSERT_FALSE(variant_reader.HasMoreData());
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, GetWorksWithUint8List) {
+ scoped_ptr<dbus::Response> response = GetPropertyOnInterface(
+ kTestInterface3, kPathListPropName);
+ dbus::MessageReader reader(response.get());
+ dbus::MessageReader variant_reader(nullptr);
+ uint8* buffer;
+ size_t buffer_len;
+ ASSERT_TRUE(reader.PopVariant(&variant_reader));
+ // |buffer| remains under the control of the MessageReader.
+ ASSERT_TRUE(variant_reader.PopArrayOfBytes(&buffer, &buffer_len));
+ ASSERT_FALSE(variant_reader.HasMoreData());
+ ASSERT_FALSE(reader.HasMoreData());
+}
+
+TEST_F(ExportedPropertySetTest, SetFailsGracefully) {
+ dbus::MethodCall method_call(dbus::kPropertiesInterface,
+ dbus::kPropertiesSet);
+ method_call.SetSerial(123);
+ auto response_sender = base::Bind(&ExportedPropertySetTest::StoreResponse,
+ base::Unretained(this));
+ p_.CallHandleSet(&method_call, response_sender);
+ ASSERT_TRUE(
+ dynamic_cast<dbus::ErrorResponse*>(last_response_.get()) != nullptr);
+}
+
+} // namespace dbus_utils
+
+} // namespace buffet