diff options
author | Christopher Wiley <wiley@google.com> | 2015-07-29 17:27:03 -0700 |
---|---|---|
committer | Christopher Wiley <wiley@google.com> | 2015-07-29 17:27:03 -0700 |
commit | e3c51041cd39e382b9d68184124a3dd1cfcb06d4 (patch) | |
tree | 06142e16477dcecf01e0c6acf56993e0ebbbd7e2 | |
parent | c4a3805076ae13371b94da9fdc98670226ef4706 (diff) | |
parent | 5d162e5e777d4f516e34343830628e34e753c0c6 (diff) | |
download | dbus-binding-generator-e3c51041cd39e382b9d68184124a3dd1cfcb06d4.tar.gz |
Merge branch 'rewrite-chromeos-dbus-bindings' into merge-chromeos-dbus-bindings
30 files changed, 6506 insertions, 0 deletions
diff --git a/chromeos-dbus-bindings/adaptor_generator.cc b/chromeos-dbus-bindings/adaptor_generator.cc new file mode 100644 index 0000000..44897a1 --- /dev/null +++ b/chromeos-dbus-bindings/adaptor_generator.cc @@ -0,0 +1,500 @@ +// 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 "chromeos-dbus-bindings/adaptor_generator.h" + +#include <string> + +#include <base/logging.h> +#include <base/strings/string_util.h> +#include <base/strings/stringprintf.h> +#include <chromeos/strings/string_utils.h> + +#include "chromeos-dbus-bindings/dbus_signature.h" +#include "chromeos-dbus-bindings/indented_text.h" +#include "chromeos-dbus-bindings/interface.h" +#include "chromeos-dbus-bindings/name_parser.h" + +using base::StringPrintf; +using std::string; +using std::vector; + +namespace chromeos_dbus_bindings { + +// static +bool AdaptorGenerator::GenerateAdaptors( + const std::vector<Interface>& interfaces, + const base::FilePath& output_file) { + IndentedText text; + CHECK(!interfaces.empty()) << "At least one interface must be provided"; + + text.AddLine("// Automatic generation of D-Bus interfaces:"); + for (const auto& interface : interfaces) { + text.AddLine(StringPrintf("// - %s", interface.name.c_str())); + } + string header_guard = GenerateHeaderGuard(output_file); + text.AddLine(StringPrintf("#ifndef %s", header_guard.c_str())); + text.AddLine(StringPrintf("#define %s", header_guard.c_str())); + text.AddLine("#include <memory>"); + text.AddLine("#include <string>"); + text.AddLine("#include <tuple>"); + text.AddLine("#include <vector>"); + text.AddBlankLine(); + text.AddLine("#include <base/macros.h>"); + text.AddLine("#include <dbus/object_path.h>"); + text.AddLine("#include <chromeos/any.h>"); + text.AddLine("#include <chromeos/dbus/dbus_object.h>"); + text.AddLine("#include <chromeos/dbus/exported_object_manager.h>"); + text.AddLine("#include <chromeos/variant_dictionary.h>"); + + for (const auto& interface : interfaces) + GenerateInterfaceAdaptor(interface, &text); + + text.AddLine(StringPrintf("#endif // %s", header_guard.c_str())); + + return WriteTextToFile(output_file, text); +} + +// static +void AdaptorGenerator::GenerateInterfaceAdaptor( + const Interface& interface, + IndentedText *text) { + NameParser parser{interface.name}; + string itf_name = parser.MakeInterfaceName(false); + string class_name = parser.MakeAdaptorName(false); + string full_itf_name = parser.MakeFullCppName(); + + text->AddBlankLine(); + parser.AddOpenNamespaces(text, false); + + text->AddBlankLine(); + text->AddLine(StringPrintf("// Interface definition for %s.", + full_itf_name.c_str())); + text->AddComments(interface.doc_string); + text->AddLine(StringPrintf("class %s {", itf_name.c_str())); + text->AddLineWithOffset("public:", kScopeOffset); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("virtual ~%s() = default;", itf_name.c_str())); + AddInterfaceMethods(interface, text); + text->PopOffset(); + text->AddLine("};"); + + text->AddBlankLine(); + text->AddLine(StringPrintf("// Interface adaptor for %s.", + full_itf_name.c_str())); + text->AddLine(StringPrintf("class %s {", class_name.c_str())); + text->AddLineWithOffset("public:", kScopeOffset); + text->PushOffset(kBlockOffset); + AddConstructor(class_name, itf_name, text); + AddRegisterWithDBusObject(itf_name, interface, text); + AddSendSignalMethods(interface, text); + AddPropertyMethodImplementation(interface, text); + if (!interface.path.empty()) { + text->AddBlankLine(); + text->AddLine("static dbus::ObjectPath GetObjectPath() {"); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("return dbus::ObjectPath{\"%s\"};", + interface.path.c_str())); + text->PopOffset(); + text->AddLine("}"); + } + text->PopOffset(); + + text->AddBlankLine(); + text->AddLineWithOffset("private:", kScopeOffset); + text->PushOffset(kBlockOffset); + AddSignalDataMembers(interface, text); + AddPropertyDataMembers(interface, text); + + text->AddLine(StringPrintf( + "%s* interface_; // Owned by container of this adapter.", + itf_name.c_str())); + + text->AddBlankLine(); + text->AddLine(StringPrintf("DISALLOW_COPY_AND_ASSIGN(%s);", + class_name.c_str())); + text->PopOffset(); + text->AddLine("};"); + + text->AddBlankLine(); + parser.AddCloseNamespaces(text, false); +} + +// static +void AdaptorGenerator::AddConstructor(const string& class_name, + const string& itf_name, + IndentedText *text) { + text->AddLine(StringPrintf("%s(%s* interface) : interface_(interface) {}", + class_name.c_str(), itf_name.c_str())); +} + +// static +void AdaptorGenerator::AddRegisterWithDBusObject( + const std::string& itf_name, + const Interface& interface, + IndentedText *text) { + text->AddBlankLine(); + text->AddLine( + "void RegisterWithDBusObject(chromeos::dbus_utils::DBusObject* object) {"); + text->PushOffset(kBlockOffset); + text->AddLine("chromeos::dbus_utils::DBusInterface* itf ="); + text->AddLineWithOffset( + StringPrintf("object->AddOrGetInterface(\"%s\");", + interface.name.c_str()), kLineContinuationOffset); + RegisterInterface(itf_name, interface, text); + text->PopOffset(); + text->AddLine("}"); +} + +// static +void AdaptorGenerator::RegisterInterface(const string& itf_name, + const Interface& interface, + IndentedText *text) { + if (!interface.methods.empty()) + text->AddBlankLine(); + for (const auto& method : interface.methods) { + string add_handler_name; + switch (method.kind) { + case Interface::Method::Kind::kSimple: + add_handler_name = "AddSimpleMethodHandler"; + break; + case Interface::Method::Kind::kNormal: + if (method.include_dbus_message) + add_handler_name = "AddSimpleMethodHandlerWithErrorAndMessage"; + else + add_handler_name = "AddSimpleMethodHandlerWithError"; + break; + case Interface::Method::Kind::kAsync: + if (method.include_dbus_message) + add_handler_name = "AddMethodHandlerWithMessage"; + else + add_handler_name = "AddMethodHandler"; + break; + case Interface::Method::Kind::kRaw: + add_handler_name = "AddRawMethodHandler"; + break; + } + + text->AddLine(StringPrintf("itf->%s(", add_handler_name.c_str())); + text->PushOffset(kLineContinuationOffset); + text->AddLine(StringPrintf("\"%s\",", method.name.c_str())); + text->AddLine("base::Unretained(interface_),"); + text->AddLine(StringPrintf("&%s::%s);", itf_name.c_str(), + method.name.c_str())); + text->PopOffset(); + } + + // Register signals. + if (!interface.signals.empty()) + text->AddBlankLine(); + for (const auto& signal : interface.signals) { + string signal_var_name = StringPrintf("signal_%s_", signal.name.c_str()); + string signal_type_name = StringPrintf("Signal%sType", signal.name.c_str()); + text->AddLine(StringPrintf("%s = itf->RegisterSignalOfType<%s>(\"%s\");", + signal_var_name.c_str(), + signal_type_name.c_str(), + signal.name.c_str())); + } + + // Register exported properties. + if (!interface.properties.empty()) + text->AddBlankLine(); + for (const auto& property : interface.properties) { + string variable_name = NameParser{property.name}.MakeVariableName(); + string write_access; + if (property.access == "write") { + write_access = "kWriteOnly"; + } else if (property.access == "readwrite") { + write_access = "kReadWrite"; + } + if (!write_access.empty()) { + text->AddLine(StringPrintf("%s_.SetAccessMode(", variable_name.c_str())); + text->PushOffset(kLineContinuationOffset); + text->AddLine( + StringPrintf( + "chromeos::dbus_utils::ExportedPropertyBase::Access::%s);", + write_access.c_str())); + text->PopOffset(); + text->AddLine(StringPrintf("%s_.SetValidator(", variable_name.c_str())); + text->PushOffset(kLineContinuationOffset); + text->AddLineAndPushOffsetTo( + StringPrintf( + "base::Bind(&%s::Validate%s,", + NameParser{interface.name}.MakeAdaptorName(false).c_str(), + property.name.c_str()), + 1, '('); + text->AddLine("base::Unretained(this)));"); + text->PopOffset(); + text->PopOffset(); + } + text->AddLine(StringPrintf("itf->AddProperty(%sName(), &%s_);", + property.name.c_str(), variable_name.c_str())); + } +} + +// static +void AdaptorGenerator::AddInterfaceMethods(const Interface& interface, + IndentedText *text) { + IndentedText block; + DbusSignature signature; + if (!interface.methods.empty()) + block.AddBlankLine(); + + for (const auto& method : interface.methods) { + string const_method; + if (method.is_const) + const_method = " const"; + + string return_type = "void"; + vector<string> method_params; + auto input_arguments_copy = method.input_arguments; + auto output_arguments_copy = method.output_arguments; + switch (method.kind) { + case Interface::Method::Kind::kSimple: + if (output_arguments_copy.size() == 1) { + CHECK(signature.Parse(output_arguments_copy[0].type, &return_type)); + output_arguments_copy.clear(); + } + break; + case Interface::Method::Kind::kNormal: + method_params.push_back("chromeos::ErrorPtr* error"); + if (method.include_dbus_message) + method_params.push_back("dbus::Message* message"); + return_type = "bool"; + break; + case Interface::Method::Kind::kAsync: { + std::vector<std::string> out_types; + for (const auto& argument : output_arguments_copy) { + string param_type; + CHECK(signature.Parse(argument.type, ¶m_type)); + out_types.push_back(param_type); + } + method_params.push_back(base::StringPrintf( + "std::unique_ptr<chromeos::dbus_utils::DBusMethodResponse<%s>> " + "response", + chromeos::string_utils::Join(", ", out_types).c_str())); + if (method.include_dbus_message) + method_params.push_back("dbus::Message* message"); + output_arguments_copy.clear(); + break; + } + case Interface::Method::Kind::kRaw: + method_params.push_back("dbus::MethodCall* method_call"); + method_params.push_back("chromeos::dbus_utils::ResponseSender sender"); + // Raw methods don't take static parameters or return values directly. + input_arguments_copy.clear(); + output_arguments_copy.clear(); + break; + } + block.AddComments(method.doc_string); + string method_start = StringPrintf("virtual %s %s(", + return_type.c_str(), + method.name.c_str()); + string method_end = StringPrintf(")%s = 0;", const_method.c_str()); + int index = 0; + for (const auto& argument : input_arguments_copy) { + string param_type; + CHECK(signature.Parse(argument.type, ¶m_type)); + MakeConstReferenceIfNeeded(¶m_type); + string param_name = GetArgName("in", argument.name, ++index); + method_params.push_back(param_type + ' ' + param_name); + } + + for (const auto& argument : output_arguments_copy) { + string param_type; + CHECK(signature.Parse(argument.type, ¶m_type)); + string param_name = GetArgName("out", argument.name, ++index); + method_params.push_back(param_type + "* " + param_name); + } + + if (method_params.empty()) { + block.AddLine(method_start + method_end); + } else { + block.AddLine(method_start); + block.PushOffset(kLineContinuationOffset); + for (size_t i = 0; i < method_params.size() - 1; i++) + block.AddLine(method_params[i] + ','); + block.AddLine(method_params.back() + method_end); + block.PopOffset(); + } + } + text->AddBlock(block); +} + +// static +void AdaptorGenerator::AddSendSignalMethods( + const Interface& interface, + IndentedText *text) { + IndentedText block; + DbusSignature signature; + + if (!interface.signals.empty()) + block.AddBlankLine(); + + for (const auto& signal : interface.signals) { + block.AddComments(signal.doc_string); + string method_start = StringPrintf("void Send%sSignal(", + signal.name.c_str()); + string method_end = ") {"; + + int index = 0; + vector<string> method_params; + vector<string> param_names; + for (const auto& argument : signal.arguments) { + string param_type; + CHECK(signature.Parse(argument.type, ¶m_type)); + MakeConstReferenceIfNeeded(¶m_type); + string param_name = GetArgName("in", argument.name, ++index); + param_names.push_back(param_name); + method_params.push_back(param_type + ' ' + param_name); + } + + if (method_params.empty()) { + block.AddLine(method_start + method_end); + } else { + block.AddLine(method_start); + block.PushOffset(kLineContinuationOffset); + for (size_t i = 0; i < method_params.size() - 1; i++) + block.AddLine(method_params[i] + ','); + block.AddLine(method_params.back() + method_end); + block.PopOffset(); + } + + string args = chromeos::string_utils::Join(", ", param_names); + block.PushOffset(kBlockOffset); + block.AddLine(StringPrintf("auto signal = signal_%s_.lock();", + signal.name.c_str())); + block.AddLine("if (signal)"); + block.AddLineWithOffset(StringPrintf("signal->Send(%s);", args.c_str()), + kBlockOffset); + block.PopOffset(); + block.AddLine("}"); + } + text->AddBlock(block); +} + +// static +void AdaptorGenerator::AddSignalDataMembers(const Interface& interface, + IndentedText *text) { + IndentedText block; + DbusSignature signature; + + for (const auto& signal : interface.signals) { + string signal_type_name = StringPrintf("Signal%sType", signal.name.c_str()); + string signal_type_alias_begin = + StringPrintf("using %s = chromeos::dbus_utils::DBusSignal<", + signal_type_name.c_str()); + string signal_type_alias_end = ">;"; + vector<string> params; + for (const auto& argument : signal.arguments) { + string param; + CHECK(signature.Parse(argument.type, ¶m)); + if (!argument.name.empty()) + base::StringAppendF(¶m, " /*%s*/", argument.name.c_str()); + params.push_back(param); + } + if (params.empty()) { + block.AddLine(signal_type_alias_begin + signal_type_alias_end); + } else { + block.AddLine(signal_type_alias_begin); + block.PushOffset(kLineContinuationOffset); + for (size_t i = 0; i < params.size() - 1; i++) + block.AddLine(params[i] + ','); + block.AddLine(params.back() + signal_type_alias_end); + block.PopOffset(); + } + block.AddLine( + StringPrintf("std::weak_ptr<%s> signal_%s_;", + signal_type_name.c_str(), signal.name.c_str())); + block.AddBlankLine(); + } + text->AddBlock(block); +} + +// static +void AdaptorGenerator::AddPropertyMethodImplementation( + const Interface& interface, + IndentedText *text) { + IndentedText block; + DbusSignature signature; + + for (const auto& property : interface.properties) { + block.AddBlankLine(); + string type; + CHECK(signature.Parse(property.type, &type)); + string variable_name = NameParser{property.name}.MakeVariableName(); + + // Property name accessor. + block.AddComments(property.doc_string); + block.AddLine(StringPrintf("static const char* %sName() { return \"%s\"; }", + property.name.c_str(), property.name.c_str())); + + // Getter method. + block.AddLine(StringPrintf("%s Get%s() const {", + type.c_str(), + property.name.c_str())); + block.PushOffset(kBlockOffset); + block.AddLine(StringPrintf("return %s_.GetValue().Get<%s>();", + variable_name.c_str(), + type.c_str())); + block.PopOffset(); + block.AddLine("}"); + + // Setter method. + MakeConstReferenceIfNeeded(&type); + block.AddLine(StringPrintf("void Set%s(%s %s) {", + property.name.c_str(), + type.c_str(), + variable_name.c_str())); + block.PushOffset(kBlockOffset); + block.AddLine(StringPrintf("%s_.SetValue(%s);", + variable_name.c_str(), + variable_name.c_str())); + block.PopOffset(); + block.AddLine("}"); + + // Validation method for property with write access. + if (property.access != "read") { + CHECK(signature.Parse(property.type, &type)); + block.AddLine(StringPrintf("virtual bool Validate%s(", + property.name.c_str())); + block.PushOffset(kLineContinuationOffset); + // Explicitly specify the "value" parameter as const & to match the + // validator callback function signature. + block.AddLine( + StringPrintf( + "chromeos::ErrorPtr* /*error*/, const %s& /*value*/) {", + type.c_str())); + block.PopOffset(); + block.PushOffset(kBlockOffset); + block.AddLine("return true;"); + block.PopOffset(); + block.AddLine("}"); + } + } + text->AddBlock(block); +} + +// static +void AdaptorGenerator::AddPropertyDataMembers(const Interface& interface, + IndentedText *text) { + IndentedText block; + DbusSignature signature; + + for (const auto& property : interface.properties) { + string type; + CHECK(signature.Parse(property.type, &type)); + string variable_name = NameParser{property.name}.MakeVariableName(); + + block.AddLine( + StringPrintf("chromeos::dbus_utils::ExportedProperty<%s> %s_;", + type.c_str(), variable_name.c_str())); + } + if (!interface.properties.empty()) + block.AddBlankLine(); + + text->AddBlock(block); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/adaptor_generator.h b/chromeos-dbus-bindings/adaptor_generator.h new file mode 100644 index 0000000..57e2bf5 --- /dev/null +++ b/chromeos-dbus-bindings/adaptor_generator.h @@ -0,0 +1,79 @@ +// 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 CHROMEOS_DBUS_BINDINGS_ADAPTOR_GENERATOR_H_ +#define CHROMEOS_DBUS_BINDINGS_ADAPTOR_GENERATOR_H_ + +#include <string> +#include <vector> + +#include <base/macros.h> + +#include "chromeos-dbus-bindings/header_generator.h" +#include "chromeos-dbus-bindings/indented_text.h" + +namespace base { + +class FilePath; + +} // namespace base + +namespace chromeos_dbus_bindings { + +class IndentedText; +struct Interface; + +class AdaptorGenerator : public HeaderGenerator { + public: + static bool GenerateAdaptors(const std::vector<Interface>& interfaces, + const base::FilePath& output_file); + + private: + friend class AdaptorGeneratorTest; + + // Generates one interface adaptor. + static void GenerateInterfaceAdaptor(const Interface& interface, + IndentedText *text); + + // Generates the method prototypes for an interface declaration. + static void AddInterfaceMethods(const Interface& interface, + IndentedText *text); + + // Generates the constructor for the adaptor. + static void AddConstructor(const std::string& class_name, + const std::string& itf_name, + IndentedText *text); + + // Generates RegisterWithDBusObject() method. + static void AddRegisterWithDBusObject(const std::string& itf_name, + const Interface& interface, + IndentedText *text); + + // Generates the code to register the interface with a D-Bus object. + static void RegisterInterface(const std::string& itf_name, + const Interface& interface, + IndentedText *text); + + // Generates adaptor methods to send the signals. + static void AddSendSignalMethods(const Interface& interface, + IndentedText *text); + + // Generates DBusSignal data members for the signals. + static void AddSignalDataMembers(const Interface& interface, + IndentedText *text); + + // Generates adaptor accessor methods for the properties. + static void AddPropertyMethodImplementation(const Interface& interface, + IndentedText *text); + + // Generate ExportProperty data members for the properties. + static void AddPropertyDataMembers(const Interface& interface, + IndentedText *text); + + DISALLOW_COPY_AND_ASSIGN(AdaptorGenerator); +}; + +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_ADAPTOR_GENERATOR_H_ diff --git a/chromeos-dbus-bindings/adaptor_generator_unittest.cc b/chromeos-dbus-bindings/adaptor_generator_unittest.cc new file mode 100644 index 0000000..9d9fad7 --- /dev/null +++ b/chromeos-dbus-bindings/adaptor_generator_unittest.cc @@ -0,0 +1,313 @@ +// 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 "chromeos-dbus-bindings/adaptor_generator.h" + +#include <string> +#include <vector> + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <gtest/gtest.h> + +#include "chromeos-dbus-bindings/interface.h" +#include "chromeos-dbus-bindings/test_utils.h" + +using std::string; +using std::vector; +using testing::Test; + +namespace chromeos_dbus_bindings { + +namespace { + +const char kDBusTypeArryOfObjects[] = "ao"; +const char kDBusTypeBool[] = "b"; +const char kDBusTypeInt32[] = "i"; +const char kDBusTypeInt64[] = "x"; +const char kDBusTypeString[] = "s"; + +const char kPropertyAccessReadOnly[] = "read"; +const char kPropertyAccessReadWrite[] = "readwrite"; + +const char kInterfaceName[] = "org.chromium.Test"; +const char kInterfaceName2[] = "org.chromium.Test2"; + +const char kExpectedContent[] = R"literal_string( +#include <memory> +#include <string> +#include <tuple> +#include <vector> + +#include <base/macros.h> +#include <dbus/object_path.h> +#include <chromeos/any.h> +#include <chromeos/dbus/dbus_object.h> +#include <chromeos/dbus/exported_object_manager.h> +#include <chromeos/variant_dictionary.h> + +namespace org { +namespace chromium { + +// Interface definition for org::chromium::Test. +class TestInterface { + public: + virtual ~TestInterface() = default; + + virtual bool Kaneda( + chromeos::ErrorPtr* error, + dbus::Message* message, + const std::string& in_iwata, + const std::vector<dbus::ObjectPath>& in_clarke, + std::string* out_3) = 0; + virtual bool Tetsuo( + chromeos::ErrorPtr* error, + int32_t in_1, + int64_t* out_2) = 0; + virtual bool Kei( + chromeos::ErrorPtr* error) = 0; + virtual bool Kiyoko( + chromeos::ErrorPtr* error, + int64_t* out_akira, + std::string* out_2) = 0; +}; + +// Interface adaptor for org::chromium::Test. +class TestAdaptor { + public: + TestAdaptor(TestInterface* interface) : interface_(interface) {} + + void RegisterWithDBusObject(chromeos::dbus_utils::DBusObject* object) { + chromeos::dbus_utils::DBusInterface* itf = + object->AddOrGetInterface("org.chromium.Test"); + + itf->AddSimpleMethodHandlerWithErrorAndMessage( + "Kaneda", + base::Unretained(interface_), + &TestInterface::Kaneda); + itf->AddSimpleMethodHandlerWithError( + "Tetsuo", + base::Unretained(interface_), + &TestInterface::Tetsuo); + itf->AddSimpleMethodHandlerWithError( + "Kei", + base::Unretained(interface_), + &TestInterface::Kei); + itf->AddSimpleMethodHandlerWithError( + "Kiyoko", + base::Unretained(interface_), + &TestInterface::Kiyoko); + + signal_Update_ = itf->RegisterSignalOfType<SignalUpdateType>("Update"); + signal_Mapping_ = itf->RegisterSignalOfType<SignalMappingType>("Mapping"); + + itf->AddProperty(CharacterNameName(), &character_name_); + write_property_.SetAccessMode( + chromeos::dbus_utils::ExportedPropertyBase::Access::kReadWrite); + write_property_.SetValidator( + base::Bind(&TestAdaptor::ValidateWriteProperty, + base::Unretained(this))); + itf->AddProperty(WritePropertyName(), &write_property_); + } + + void SendUpdateSignal() { + auto signal = signal_Update_.lock(); + if (signal) + signal->Send(); + } + void SendMappingSignal( + const std::string& in_key, + const std::vector<dbus::ObjectPath>& in_2) { + auto signal = signal_Mapping_.lock(); + if (signal) + signal->Send(in_key, in_2); + } + + static const char* CharacterNameName() { return "CharacterName"; } + std::string GetCharacterName() const { + return character_name_.GetValue().Get<std::string>(); + } + void SetCharacterName(const std::string& character_name) { + character_name_.SetValue(character_name); + } + + static const char* WritePropertyName() { return "WriteProperty"; } + std::string GetWriteProperty() const { + return write_property_.GetValue().Get<std::string>(); + } + void SetWriteProperty(const std::string& write_property) { + write_property_.SetValue(write_property); + } + virtual bool ValidateWriteProperty( + chromeos::ErrorPtr* /*error*/, const std::string& /*value*/) { + return true; + } + + static dbus::ObjectPath GetObjectPath() { + return dbus::ObjectPath{"/org/chromium/Test"}; + } + + private: + using SignalUpdateType = chromeos::dbus_utils::DBusSignal<>; + std::weak_ptr<SignalUpdateType> signal_Update_; + + using SignalMappingType = chromeos::dbus_utils::DBusSignal< + std::string /*key*/, + std::vector<dbus::ObjectPath>>; + std::weak_ptr<SignalMappingType> signal_Mapping_; + + chromeos::dbus_utils::ExportedProperty<std::string> character_name_; + chromeos::dbus_utils::ExportedProperty<std::string> write_property_; + + TestInterface* interface_; // Owned by container of this adapter. + + DISALLOW_COPY_AND_ASSIGN(TestAdaptor); +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Interface definition for org::chromium::Test2. +class Test2Interface { + public: + virtual ~Test2Interface() = default; + + virtual std::string Kaneda2( + const std::string& in_iwata) const = 0; + virtual void Tetsuo2( + std::unique_ptr<chromeos::dbus_utils::DBusMethodResponse<int64_t>> response, + int32_t in_1) = 0; + virtual void Kei2( + std::unique_ptr<chromeos::dbus_utils::DBusMethodResponse<bool>> response, + dbus::Message* message) = 0; +}; + +// Interface adaptor for org::chromium::Test2. +class Test2Adaptor { + public: + Test2Adaptor(Test2Interface* interface) : interface_(interface) {} + + void RegisterWithDBusObject(chromeos::dbus_utils::DBusObject* object) { + chromeos::dbus_utils::DBusInterface* itf = + object->AddOrGetInterface("org.chromium.Test2"); + + itf->AddSimpleMethodHandler( + "Kaneda2", + base::Unretained(interface_), + &Test2Interface::Kaneda2); + itf->AddMethodHandler( + "Tetsuo2", + base::Unretained(interface_), + &Test2Interface::Tetsuo2); + itf->AddMethodHandlerWithMessage( + "Kei2", + base::Unretained(interface_), + &Test2Interface::Kei2); + } + + private: + Test2Interface* interface_; // Owned by container of this adapter. + + DISALLOW_COPY_AND_ASSIGN(Test2Adaptor); +}; + +} // namespace chromium +} // namespace org +)literal_string"; + +} // namespace +class AdaptorGeneratorTest : public Test { + public: + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + protected: + base::FilePath CreateInputFile(const string& contents) { + base::FilePath path; + EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &path)); + int written = base::WriteFile(path, contents.c_str(), contents.size()); + EXPECT_EQ(contents.size(), static_cast<size_t>(written)); + return path; + } + + base::ScopedTempDir temp_dir_; +}; + +TEST_F(AdaptorGeneratorTest, GenerateAdaptors) { + Interface interface; + interface.name = kInterfaceName; + interface.path = "/org/chromium/Test"; + interface.methods.emplace_back( + "Kaneda", + vector<Interface::Argument>{ + {"iwata", kDBusTypeString}, + {"clarke", kDBusTypeArryOfObjects}}, + vector<Interface::Argument>{{"", kDBusTypeString}}); + interface.methods.back().include_dbus_message = true; + interface.methods.emplace_back( + "Tetsuo", + vector<Interface::Argument>{{"", kDBusTypeInt32}}, + vector<Interface::Argument>{{"", kDBusTypeInt64}}); + interface.methods.emplace_back("Kei"); + // Interface methods with more than one return argument should be ignored. + interface.methods.emplace_back( + "Kiyoko", + vector<Interface::Argument>{}, + vector<Interface::Argument>{ + {"akira", kDBusTypeInt64}, + {"", kDBusTypeString}}); + // Signals generate helper methods to send them. + interface.signals.emplace_back( + "Update", + vector<Interface::Argument>{}); + interface.signals.emplace_back( + "Mapping", + vector<Interface::Argument>{ + {"key", kDBusTypeString}, + {"", kDBusTypeArryOfObjects}}); + interface.properties.emplace_back( + "CharacterName", + kDBusTypeString, + kPropertyAccessReadOnly); + interface.properties.emplace_back( + "WriteProperty", + kDBusTypeString, + kPropertyAccessReadWrite); + + Interface interface2; + interface2.name = kInterfaceName2; + interface2.methods.emplace_back( + "Kaneda2", + vector<Interface::Argument>{{"iwata", kDBusTypeString}}, + vector<Interface::Argument>{{"", kDBusTypeString}}); + interface2.methods.back().is_const = true; + interface2.methods.back().kind = Interface::Method::Kind::kSimple; + interface2.methods.emplace_back( + "Tetsuo2", + vector<Interface::Argument>{{"", kDBusTypeInt32}}, + vector<Interface::Argument>{{"", kDBusTypeInt64}}); + interface2.methods.back().kind = Interface::Method::Kind::kAsync; + interface2.methods.emplace_back( + "Kei2", + vector<Interface::Argument>{}, + vector<Interface::Argument>{{"", kDBusTypeBool}}); + interface2.methods.back().kind = Interface::Method::Kind::kAsync; + interface2.methods.back().include_dbus_message = true; + + base::FilePath output_path = temp_dir_.path().Append("output.h"); + EXPECT_TRUE(AdaptorGenerator::GenerateAdaptors({interface, interface2}, + output_path)); + string contents; + EXPECT_TRUE(base::ReadFileToString(output_path, &contents)); + // The header guards contain the (temporary) filename, so we search for + // the content we need within the string. + test_utils::EXPECT_TEXT_CONTAINED(kExpectedContent, contents); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp b/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp new file mode 100644 index 0000000..218edb7 --- /dev/null +++ b/chromeos-dbus-bindings/chromeos-dbus-bindings.gyp @@ -0,0 +1,89 @@ +{ + 'target_defaults': { + 'variables': { + 'deps': [ + 'libchrome-<(libbase_ver)', + 'libchromeos-<(libbase_ver)', + ], + }, + 'cflags': [ + '-Wextra', + '-Wno-unused-parameter', # for scoped_ptr.h, included indirectly + ], + 'cflags_cc': [ + '-fno-strict-aliasing', + '-Woverloaded-virtual', + ], + }, + 'targets': [ + { + 'target_name': 'libchromeos-dbus-bindings', + 'type': 'static_library', + 'sources': [ + 'adaptor_generator.cc', + 'dbus_signature.cc', + 'header_generator.cc', + 'indented_text.cc', + 'method_name_generator.cc', + 'name_parser.cc', + 'proxy_generator.cc', + 'xml_interface_parser.cc', + ], + 'variables': { + 'exported_deps': [ + 'expat', + ], + 'deps': [ + 'dbus-1', + '<@(exported_deps)', + ], + }, + 'all_dependent_settings': { + 'variables': { + 'deps': [ + '<@(exported_deps)', + ], + }, + }, + 'link_settings': { + 'variables': { + 'deps': [ + 'expat', + ], + }, + }, + }, + { + 'target_name': 'generate-chromeos-dbus-bindings', + 'type': 'executable', + 'dependencies': ['libchromeos-dbus-bindings'], + 'sources': [ + 'generate_chromeos_dbus_bindings.cc', + ] + }, + ], + 'conditions': [ + ['USE_test == 1', { + 'targets': [ + { + 'target_name': 'chromeos_dbus_bindings_unittest', + 'type': 'executable', + 'dependencies': ['libchromeos-dbus-bindings'], + 'includes': ['../common-mk/common_test.gypi'], + 'sources': [ + 'testrunner.cc', + 'adaptor_generator_unittest.cc', + 'dbus_signature_unittest.cc', + 'indented_text_unittest.cc', + 'method_name_generator_unittest.cc', + 'name_parser_unittest.cc', + 'proxy_generator_mock_unittest.cc', + 'proxy_generator_unittest.cc', + 'test_utils.cc', + 'xml_interface_parser_unittest.cc', + ], + }, + ], + }], + ], +} diff --git a/chromeos-dbus-bindings/dbus_signature.cc b/chromeos-dbus-bindings/dbus_signature.cc new file mode 100644 index 0000000..a6e59d4 --- /dev/null +++ b/chromeos-dbus-bindings/dbus_signature.cc @@ -0,0 +1,245 @@ +// 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 "chromeos-dbus-bindings/dbus_signature.h" + +#include <base/logging.h> +#include <base/strings/stringprintf.h> +#include <chromeos/strings/string_utils.h> +#include <dbus/dbus-protocol.h> + +using base::StringPrintf; +using std::string; +using std::vector; + +namespace chromeos_dbus_bindings { + +// static +const char DbusSignature::kArrayTypename[] = "std::vector"; +const char DbusSignature::kBooleanTypename[] = "bool"; +const char DbusSignature::kByteTypename[] = "uint8_t"; +const char DbusSignature::kDefaultObjectPathTypename[] = "dbus::ObjectPath"; +const char DbusSignature::kDictTypename[] = "std::map"; +const char DbusSignature::kDoubleTypename[] = "double"; +const char DbusSignature::kSigned16Typename[] = "int16_t"; +const char DbusSignature::kSigned32Typename[] = "int32_t"; +const char DbusSignature::kSigned64Typename[] = "int64_t"; +const char DbusSignature::kStringTypename[] = "std::string"; +const char DbusSignature::kUnixFdTypename[] = "dbus::FileDescriptor"; +const char DbusSignature::kUnsigned16Typename[] = "uint16_t"; +const char DbusSignature::kUnsigned32Typename[] = "uint32_t"; +const char DbusSignature::kUnsigned64Typename[] = "uint64_t"; +const char DbusSignature::kVariantTypename[] = "chromeos::Any"; +const char DbusSignature::kVariantDictTypename[] = + "chromeos::VariantDictionary"; +const char DbusSignature::kTupleTypename[] = "std::tuple"; + +DbusSignature::DbusSignature() + : object_path_typename_(kDefaultObjectPathTypename) {} + +bool DbusSignature::Parse(const string& signature, string* output) { + string::const_iterator end; + if (!GetTypenameForSignature( + signature.begin(), signature.end(), &end, output)) { + LOG(ERROR) << "Parse failed for signature " << signature; + return false; + } + if (end != signature.end()) { + LOG(WARNING) << "A portion of signature " << signature + << " is left unparsed: " << string(end, signature.end()); + } + return true; +} + +bool DbusSignature::GetTypenameForSignature( + string::const_iterator signature, + string::const_iterator end, + string::const_iterator* next, + string* output) { + if (signature == end) { + LOG(ERROR) << "Signature is empty"; + return false; + } + + string::const_iterator cur = signature; + int signature_value = *cur++; + switch (signature_value) { + case DBUS_STRUCT_BEGIN_CHAR: + if (!GetStructTypenameForSignature(cur, end, &cur, output)) { + return false; + } + break; + + case DBUS_TYPE_ARRAY: + if (!GetArrayTypenameForSignature(cur, end, &cur, output)) { + return false; + } + break; + + case DBUS_TYPE_BOOLEAN: + *output = kBooleanTypename; + break; + + case DBUS_TYPE_BYTE: + *output = kByteTypename; + break; + + case DBUS_TYPE_DOUBLE: + *output = kDoubleTypename; + break; + + case DBUS_TYPE_OBJECT_PATH: + *output = object_path_typename_; + break; + + case DBUS_TYPE_INT16: + *output = kSigned16Typename; + break; + + case DBUS_TYPE_INT32: + *output = kSigned32Typename; + break; + + case DBUS_TYPE_INT64: + *output = kSigned64Typename; + break; + + case DBUS_TYPE_STRING: + *output = kStringTypename; + break; + + case DBUS_TYPE_UNIX_FD: + *output = kUnixFdTypename; + break; + + case DBUS_TYPE_UINT16: + *output = kUnsigned16Typename; + break; + + case DBUS_TYPE_UINT32: + *output = kUnsigned32Typename; + break; + + case DBUS_TYPE_UINT64: + *output = kUnsigned64Typename; + break; + + case DBUS_TYPE_VARIANT: + *output = kVariantTypename; + break; + + default: + LOG(ERROR) << "Unexpected token " << *signature; + return false; + } + + if (next) { + *next = cur; + } + + return true; +} + +bool DbusSignature::GetArrayTypenameForSignature( + string::const_iterator signature, + string::const_iterator end, + string::const_iterator* next, + string* output) { + string::const_iterator cur = signature; + if (cur == end) { + LOG(ERROR) << "At end of string while reading array parameter"; + return false; + } + + if (*cur == DBUS_DICT_ENTRY_BEGIN_CHAR) { + vector<string> children; + ++cur; + while (cur != end && *cur != DBUS_DICT_ENTRY_END_CHAR) { + children.emplace_back(); + if (!GetTypenameForSignature(cur, end, &cur, &children.back())) { + LOG(ERROR) << "Unable to decode child elements starting at " + << string(cur, end); + return false; + } + } + if (cur == end) { + LOG(ERROR) << "At end of string while processing dict " + << "starting at " << string(signature, end); + return false; + } + + DCHECK_EQ(DBUS_DICT_ENTRY_END_CHAR, *cur); + ++cur; + + if (children.size() != 2) { + LOG(ERROR) << "Dict entry contains " << children.size() + << " members starting at " << string(signature, end) + << " where only 2 children is valid."; + return false; + } + string dict_signature{signature, cur}; + if (dict_signature == "{sv}") { + *output = kVariantDictTypename; + } else { + *output = StringPrintf("%s<%s, %s>", kDictTypename, + children[0].c_str(), children[1].c_str()); + } + } else { + string child; + if (!GetTypenameForSignature(cur, end, &cur, &child)) { + LOG(ERROR) << "Unable to decode child element starting at " + << string(cur, end); + return false; + } + *output = StringPrintf("%s<%s>", kArrayTypename, child.c_str()); + } + + if (next) { + *next = cur; + } + + return true; +} + +bool DbusSignature::GetStructTypenameForSignature( + string::const_iterator signature, + string::const_iterator end, + string::const_iterator* next, + string* output) { + string::const_iterator cur = signature; + if (cur == end) { + LOG(ERROR) << "At end of string while reading struct parameter"; + return false; + } + + vector<string> children; + while (cur != end && *cur != DBUS_STRUCT_END_CHAR) { + children.emplace_back(); + if (!GetTypenameForSignature(cur, end, &cur, &children.back())) { + LOG(ERROR) << "Unable to decode child elements starting at " + << string(cur, end); + return false; + } + } + if (cur == end) { + LOG(ERROR) << "At end of string while processing struct " + << "starting at " << string(signature, end); + return false; + } + + DCHECK_EQ(DBUS_STRUCT_END_CHAR, *cur); + ++cur; + + *output = StringPrintf( + "%s<%s>", kTupleTypename, + chromeos::string_utils::Join(", ", children).c_str()); + + if (next) { + *next = cur; + } + + return true; +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/dbus_signature.h b/chromeos-dbus-bindings/dbus_signature.h new file mode 100644 index 0000000..6a5a10b --- /dev/null +++ b/chromeos-dbus-bindings/dbus_signature.h @@ -0,0 +1,90 @@ +// 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 CHROMEOS_DBUS_BINDINGS_DBUS_SIGNATURE_H_ +#define CHROMEOS_DBUS_BINDINGS_DBUS_SIGNATURE_H_ + +#include <string> +#include <vector> + +#include <base/macros.h> +#include <gtest/gtest_prod.h> // for FRIEND_TEST + +namespace chromeos_dbus_bindings { + +class DbusSignature { + public: + DbusSignature(); + virtual ~DbusSignature() = default; + + // Returns a C++ typename in |output| for a D-Bus signature in |signature| + // and returns true on success. Returns false otherwise. + bool Parse(const std::string& signature, std::string* output); + + void set_object_path_typename(const std::string& object_path_typename) { + object_path_typename_ = object_path_typename; + } + + private: + friend class DbusSignatureTest; + FRIEND_TEST(DbusSignatureTest, DefaultObjectPathTypename); + FRIEND_TEST(DbusSignatureTest, ParseSuccesses); + + // Typenames are C++ syntax types. + static const char kArrayTypename[]; + static const char kBooleanTypename[]; + static const char kByteTypename[]; + static const char kDefaultObjectPathTypename[]; + static const char kDictTypename[]; + static const char kDoubleTypename[]; + static const char kSigned16Typename[]; + static const char kSigned32Typename[]; + static const char kSigned64Typename[]; + static const char kStringTypename[]; + static const char kUnixFdTypename[]; + static const char kUnsigned16Typename[]; + static const char kUnsigned32Typename[]; + static const char kUnsigned64Typename[]; + static const char kVariantTypename[]; + static const char kVariantDictTypename[]; + static const char kPairTypename[]; + static const char kTupleTypename[]; + + // Returns the C++ type name for the next D-Bus signature in the string at + // |signature| in |output|, as well as the next position within the string + // that parsing should continue |next|. It is not an error to pass a + // pointer to |signature| or nullptr as |next|. Returns true on success. + bool GetTypenameForSignature(std::string::const_iterator signature, + std::string::const_iterator end, + std::string::const_iterator* next, + std::string* output); + + // Utility task for GetTypenameForSignature() which handles array objects + // and decodes them into a map or vector depending on the encoded sub-elements + // in the array. The arguments and return values are the same + // as GetTypenameForSignature(). + bool GetArrayTypenameForSignature(std::string::const_iterator signature, + std::string::const_iterator end, + std::string::const_iterator* next, + std::string* output); + + // Utility task for GetTypenameForSignature() which handles STRUCT objects + // and decodes them into a pair or tuple depending on the number of structure + // elements. The arguments and return values are the same + // as GetTypenameForSignature(). + bool GetStructTypenameForSignature(std::string::const_iterator signature, + std::string::const_iterator end, + std::string::const_iterator* next, + std::string* output); + + + // The C++ typename to be used for D-Bus object pathnames. + std::string object_path_typename_; + + DISALLOW_COPY_AND_ASSIGN(DbusSignature); +}; + +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_DBUS_SIGNATURE_H_ diff --git a/chromeos-dbus-bindings/dbus_signature_unittest.cc b/chromeos-dbus-bindings/dbus_signature_unittest.cc new file mode 100644 index 0000000..96ebbe5 --- /dev/null +++ b/chromeos-dbus-bindings/dbus_signature_unittest.cc @@ -0,0 +1,120 @@ +// 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 "chromeos-dbus-bindings/dbus_signature.h" + +#include <map> +#include <string> + +#include <dbus/dbus-protocol.h> +#include <gtest/gtest.h> + +using std::map; +using std::string; +using testing::Test; + +namespace chromeos_dbus_bindings { + +namespace { + +// Failing signatures. +const char kEmptySignature[] = ""; +const char kEmptyDictSignature[] = "a{}"; +const char kMissingArraryParameterSignature[] = "a"; +const char kMissingArraryParameterInnerSignature[] = "a{sa}i"; +const char kOrphanDictSignature[] = "a{s{i}}"; +const char kTooFewDictMembersSignature[] = "a{s}"; +const char kTooManyDictMembersSignature[] = "a{sa{i}u}"; +const char kUnclosedDictOuterSignature[] = "a{s"; +const char kUnclosedDictInnerSignature[] = "a{a{u}"; +const char kUnexpectedCloseSignature[] = "a}i{"; +const char kUnknownSignature[] = "al"; + +// Define an object type name to disambiguate the typenames above. +const char kObjectPathTypename[] = "ObjectPathType"; + +} // namespace + +class DbusSignatureTest : public Test { + protected: + DbusSignature signature_; +}; + +TEST_F(DbusSignatureTest, ParseFailures) { + for (const auto& failing_string : { kEmptySignature, + kEmptyDictSignature, + kMissingArraryParameterSignature, + kMissingArraryParameterInnerSignature, + kOrphanDictSignature, + kTooFewDictMembersSignature, + kTooManyDictMembersSignature, + kUnclosedDictOuterSignature, + kUnclosedDictInnerSignature, + kUnexpectedCloseSignature, + kUnknownSignature }) { + string unused_output; + EXPECT_FALSE(signature_.Parse(failing_string, &unused_output)) + << "Expected signature " << failing_string + << " to fail but it succeeded"; + } +} + +TEST_F(DbusSignatureTest, DefaultObjectPathTypename) { + // The ParseSuccesses test below overrides the default object typename, so + // test the default behavior separately. + string output; + EXPECT_TRUE(signature_.Parse(DBUS_TYPE_OBJECT_PATH_AS_STRING, &output)); + EXPECT_EQ(DbusSignature::kDefaultObjectPathTypename, output); +} + +TEST_F(DbusSignatureTest, ParseSuccesses) { + const map<string, string> parse_values { + // Simple types. + { DBUS_TYPE_BOOLEAN_AS_STRING, DbusSignature::kBooleanTypename }, + { DBUS_TYPE_BYTE_AS_STRING, DbusSignature::kByteTypename }, + { DBUS_TYPE_DOUBLE_AS_STRING, DbusSignature::kDoubleTypename }, + { DBUS_TYPE_OBJECT_PATH_AS_STRING, kObjectPathTypename }, + { DBUS_TYPE_INT16_AS_STRING, DbusSignature::kSigned16Typename }, + { DBUS_TYPE_INT32_AS_STRING, DbusSignature::kSigned32Typename }, + { DBUS_TYPE_INT64_AS_STRING, DbusSignature::kSigned64Typename }, + { DBUS_TYPE_STRING_AS_STRING, DbusSignature::kStringTypename }, + { DBUS_TYPE_UNIX_FD_AS_STRING, DbusSignature::kUnixFdTypename }, + { DBUS_TYPE_UINT16_AS_STRING, DbusSignature::kUnsigned16Typename }, + { DBUS_TYPE_UINT32_AS_STRING, DbusSignature::kUnsigned32Typename }, + { DBUS_TYPE_UINT64_AS_STRING, DbusSignature::kUnsigned64Typename }, + { DBUS_TYPE_VARIANT_AS_STRING, DbusSignature::kVariantTypename }, + + // Complex types. + { "ab", "std::vector<bool>" }, + { "ay", "std::vector<uint8_t>" }, + { "aay", "std::vector<std::vector<uint8_t>>" }, + { "ao", "std::vector<ObjectPathType>" }, + { "a{oa{sa{sv}}}", "std::map<ObjectPathType, std::map<std::string, " + "chromeos::VariantDictionary>>" }, + { "a{os}", "std::map<ObjectPathType, std::string>" }, + { "as", "std::vector<std::string>" }, + { "a{ss}", "std::map<std::string, std::string>" }, + { "a{sa{ss}}", "std::map<std::string, std::map<std::string, " + "std::string>>"}, + { "a{sa{sv}}", "std::map<std::string, chromeos::VariantDictionary>" }, + { "a{sv}", "chromeos::VariantDictionary" }, + { "a{sv}Garbage", "chromeos::VariantDictionary" }, + { "at", "std::vector<uint64_t>" }, + { "a{iv}", "std::map<int32_t, chromeos::Any>" }, + { "(ib)", "std::tuple<int32_t, bool>" }, + { "(ibs)", "std::tuple<int32_t, bool, std::string>" }, + }; + signature_.set_object_path_typename(kObjectPathTypename); + for (const auto& parse_test : parse_values) { + string output; + EXPECT_TRUE(signature_.Parse(parse_test.first, &output)) + << "Expected signature " << parse_test.first + << " to succeed but it failed."; + EXPECT_EQ(parse_test.second, output) + << "Expected typename for " << parse_test.first + << " to be " << parse_test.second << " but instead it was " << output; + } +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc b/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc new file mode 100644 index 0000000..dd67a3c --- /dev/null +++ b/chromeos-dbus-bindings/generate_chromeos_dbus_bindings.cc @@ -0,0 +1,215 @@ +// 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 <memory> +#include <string> + +#include <base/command_line.h> +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/json/json_reader.h> +#include <base/logging.h> +#include <base/strings/string_util.h> +#include <base/values.h> +#include <chromeos/syslog_logging.h> + +#include "chromeos-dbus-bindings/adaptor_generator.h" +#include "chromeos-dbus-bindings/method_name_generator.h" +#include "chromeos-dbus-bindings/proxy_generator.h" +#include "chromeos-dbus-bindings/xml_interface_parser.h" + +using chromeos_dbus_bindings::AdaptorGenerator; +using chromeos_dbus_bindings::MethodNameGenerator; +using chromeos_dbus_bindings::ProxyGenerator; +using chromeos_dbus_bindings::ServiceConfig; + +namespace switches { + +static const char kHelp[] = "help"; +static const char kMethodNames[] = "method-names"; +static const char kAdaptor[] = "adaptor"; +static const char kProxy[] = "proxy"; +static const char kMock[] = "mock"; +static const char kServiceConfig[] = "service-config"; +static const char kHelpMessage[] = "\n" + "generate-chromeos-dbus-bindings itf1.xml [itf2.xml...] [switches]\n" + " itf1.xml, ... = the input interface file(s) [mandatory].\n" + "Available Switches: \n" + " --method-names=<method name header filename>\n" + " The output header file with string constants for each method name.\n" + " --adaptor=<adaptor header filename>\n" + " The output header file name containing the DBus adaptor class.\n" + " --proxy=<proxy header filename>\n" + " The output header file name containing the DBus proxy class.\n" + " --mock=<mock header filename>\n" + " The output header file name containing the DBus proxy mock class.\n" + " --service-config=<config.json>\n" + " The DBus service configuration file for the generator.\n"; + +} // namespace switches + +namespace { +// GYP sometimes enclosed the target file name in extra set of quotes like: +// generate-chromeos-dbus-bindings in.xml "--adaptor=\"out.h\"" +// So, this function helps us to remove them. +base::FilePath RemoveQuotes(const std::string& path) { + std::string unquoted; + base::TrimString(path, "\"'", &unquoted); + return base::FilePath{unquoted}; +} + +// Makes a canonical path by making the path absolute and by removing any +// '..' which makes base::ReadFileToString() to fail. +base::FilePath SanitizeFilePath(const std::string& path) { + base::FilePath path_in = RemoveQuotes(path); + base::FilePath path_out = base::MakeAbsoluteFilePath(path_in); + if (path_out.value().empty()) { + LOG(WARNING) << "Failed to canonicalize '" << path << "'"; + path_out = path_in; + } + return path_out; +} + + +// Load the service configuration from the provided JSON file. +bool LoadConfig(const base::FilePath& path, ServiceConfig *config) { + std::string contents; + if (!base::ReadFileToString(path, &contents)) + return false; + + std::unique_ptr<base::Value> json{base::JSONReader::Read(contents).release()}; + if (!json) + return false; + + base::DictionaryValue* dict = nullptr; // Aliased with |json|. + if (!json->GetAsDictionary(&dict)) + return false; + + dict->GetStringWithoutPathExpansion("service_name", &config->service_name); + + base::DictionaryValue* om_dict = nullptr; // Owned by |dict|. + if (dict->GetDictionaryWithoutPathExpansion("object_manager", &om_dict)) { + if (!om_dict->GetStringWithoutPathExpansion("name", + &config->object_manager.name) && + !config->service_name.empty()) { + config->object_manager.name = config->service_name + ".ObjectManager"; + } + om_dict->GetStringWithoutPathExpansion("object_path", + &config->object_manager.object_path); + if (config->object_manager.name.empty()) { + LOG(ERROR) << "Object manager name is missing."; + return false; + } + } + + base::ListValue* list = nullptr; // Owned by |dict|. + if (dict->GetListWithoutPathExpansion("ignore_interfaces", &list)) { + config->ignore_interfaces.reserve(list->GetSize()); + for (base::Value* item : *list) { + std::string interface_name; + if (!item->GetAsString(&interface_name)) { + LOG(ERROR) << "Invalid interface name in [ignore_interfaces] section"; + return false; + } + config->ignore_interfaces.push_back(interface_name); + } + } + + return true; +} + +} // anonymous namespace + +int main(int argc, char** argv) { + base::CommandLine::Init(argc, argv); + base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); + + // Setup logging to stderr. This also parses some implicit flags using the + // CommandLine singleton. + chromeos::InitLog(chromeos::kLogToStderr | chromeos::kLogHeader); + + if (cl->HasSwitch(switches::kHelp)) { + LOG(INFO) << switches::kHelpMessage; + return 0; + } + + auto input_files = cl->GetArgs(); + if (input_files.empty()) { + LOG(ERROR) << "At least one file must be specified."; + LOG(ERROR) << switches::kHelpMessage; + return 1; + } + + ServiceConfig config; + if (cl->HasSwitch(switches::kServiceConfig)) { + std::string config_file = cl->GetSwitchValueASCII(switches::kServiceConfig); + if (!config_file.empty() && + !LoadConfig(SanitizeFilePath(config_file), &config)) { + LOG(ERROR) << "Failed to load DBus service config file " << config_file; + return 1; + } + } + + chromeos_dbus_bindings::XmlInterfaceParser parser; + for (const auto& input : input_files) { + std::string contents; + if (!base::ReadFileToString(SanitizeFilePath(input), &contents)) { + LOG(ERROR) << "Failed to read file " << input; + return 1; + } + if (!parser.ParseXmlInterfaceFile(contents, config.ignore_interfaces)) { + LOG(ERROR) << "Failed to parse interface file " << input; + return 1; + } + } + + if (cl->HasSwitch(switches::kMethodNames)) { + std::string method_name_file = + cl->GetSwitchValueASCII(switches::kMethodNames); + VLOG(1) << "Outputting method names to " << method_name_file; + if (!MethodNameGenerator::GenerateMethodNames( + parser.interfaces(), + RemoveQuotes(method_name_file))) { + LOG(ERROR) << "Failed to output method names."; + return 1; + } + } + + if (cl->HasSwitch(switches::kAdaptor)) { + std::string adaptor_file = cl->GetSwitchValueASCII(switches::kAdaptor); + VLOG(1) << "Outputting adaptor to " << adaptor_file; + if (!AdaptorGenerator::GenerateAdaptors(parser.interfaces(), + RemoveQuotes(adaptor_file))) { + LOG(ERROR) << "Failed to output adaptor."; + return 1; + } + } + + base::FilePath proxy_path; // Used by both Proxy and Mock generation. + if (cl->HasSwitch(switches::kProxy)) { + std::string proxy_file = cl->GetSwitchValueASCII(switches::kProxy); + proxy_path = RemoveQuotes(proxy_file); + base::NormalizeFilePath(proxy_path, &proxy_path); + VLOG(1) << "Outputting proxy to " << proxy_path.value(); + if (!ProxyGenerator::GenerateProxies(config, parser.interfaces(), + proxy_path)) { + LOG(ERROR) << "Failed to output proxy."; + return 1; + } + } + + if (cl->HasSwitch(switches::kMock)) { + std::string mock_file = cl->GetSwitchValueASCII(switches::kMock); + base::FilePath mock_path = RemoveQuotes(mock_file); + base::NormalizeFilePath(mock_path, &mock_path); + VLOG(1) << "Outputting mock to " << mock_path.value(); + if (!ProxyGenerator::GenerateMocks(config, parser.interfaces(), mock_path, + proxy_path)) { + LOG(ERROR) << "Failed to output mock."; + return 1; + } + } + + return 0; +} diff --git a/chromeos-dbus-bindings/header_generator.cc b/chromeos-dbus-bindings/header_generator.cc new file mode 100644 index 0000000..0a24597 --- /dev/null +++ b/chromeos-dbus-bindings/header_generator.cc @@ -0,0 +1,70 @@ +// 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 "chromeos-dbus-bindings/header_generator.h" + +#include <string> + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/strings/string_util.h> +#include <base/strings/stringprintf.h> +#include <chromeos/strings/string_utils.h> + +#include "chromeos-dbus-bindings/indented_text.h" + +using std::string; +using std::vector; + +namespace chromeos_dbus_bindings { + +// static +string HeaderGenerator::GenerateHeaderGuard( + const base::FilePath& output_file) { + string guard = base::StringPrintf("____chromeos_dbus_binding__%s", + output_file.value().c_str()); + for (auto& c : guard) { + if (IsAsciiAlpha(c)) { + c = base::ToUpperASCII(c); + } else if (!IsAsciiDigit(c)) { + c = '_'; + } + } + return guard; +} + +// static +bool HeaderGenerator::IsIntegralType(const string& type) { + return type.find("::") == std::string::npos; +} + +// static +void HeaderGenerator::MakeConstReferenceIfNeeded(std::string* type) { + if (!IsIntegralType(*type)) { + *type = base::StringPrintf("const %s&", type->c_str()); + } +} + +// static +bool HeaderGenerator::WriteTextToFile( + const base::FilePath& output_file, const IndentedText &text) { + string contents = text.GetContents(); + int expected_write_return = contents.size(); + if (base::WriteFile(output_file, contents.c_str(), contents.size()) != + expected_write_return) { + LOG(ERROR) << "Failed to write file " << output_file.value(); + return false; + } + return true; +} + +// static +string HeaderGenerator::GetArgName(const char* prefix, + const string& arg_name, + int arg_index) { + string name = arg_name.empty() ? std::to_string(arg_index) : arg_name; + return base::StringPrintf("%s_%s", prefix, name.c_str()); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/header_generator.h b/chromeos-dbus-bindings/header_generator.h new file mode 100644 index 0000000..0c28bd7 --- /dev/null +++ b/chromeos-dbus-bindings/header_generator.h @@ -0,0 +1,81 @@ +// 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 CHROMEOS_DBUS_BINDINGS_HEADER_GENERATOR_H_ +#define CHROMEOS_DBUS_BINDINGS_HEADER_GENERATOR_H_ + +#include <string> +#include <vector> + +#include <base/macros.h> + +namespace base { + +class FilePath; + +}; + +namespace chromeos_dbus_bindings { + +struct Interface; +class IndentedText; + +// General D-Bus service configuration settings used by Adaptor/Proxy code +// generators. +struct ServiceConfig { + // D-Bus service name to be used when constructing proxy objects. + // If omitted (empty), the service name parameter will be added to the + // constructor of generated proxy class(es). + std::string service_name; + // Object Manager settings. + struct { + // The name of the Object Manager class to use. If empty, no object manager + // is generated in the proxy code (this also disables property support on + // proxy objects). + // This is a "fake" name used to generate namespaces and the actual class + // name for the object manager proxy. This name has no relationship to the + // actual D-Bus properties of the actual object manager. + std::string name; + // The D-Bus path to Object Manager instance. + std::string object_path; + } object_manager; + + // A list of interfaces we should ignore and not generate any adaptors and + // proxies for. + std::vector<std::string> ignore_interfaces; +}; + +class HeaderGenerator { + protected: + // Create a unique header guard string to protect multiple includes of header. + static std::string GenerateHeaderGuard(const base::FilePath& output_file); + + // Used to decide whether the argument should be a const reference. + static bool IsIntegralType(const std::string& type); + + // If |type| is a non-integral type, converts it into a const reference. + static void MakeConstReferenceIfNeeded(std::string* type); + + // Writes indented text to a file. + static bool WriteTextToFile(const base::FilePath& output_file, + const IndentedText& text); + + // Generate a name of a method/signal argument based on the name provided in + // the XML file. If |arg_name| is empty, it generates a name using + // the |arg_index| counter. + static std::string GetArgName(const char* prefix, + const std::string& arg_name, + int arg_index); + + static const int kScopeOffset = 1; + static const int kBlockOffset = 2; + static const int kLineContinuationOffset = 4; + + private: + DISALLOW_COPY_AND_ASSIGN(HeaderGenerator); +}; + +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_HEADER_GENERATOR_H_ diff --git a/chromeos-dbus-bindings/indented_text.cc b/chromeos-dbus-bindings/indented_text.cc new file mode 100644 index 0000000..8d42091 --- /dev/null +++ b/chromeos-dbus-bindings/indented_text.cc @@ -0,0 +1,140 @@ +// 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 "chromeos-dbus-bindings/indented_text.h" + +#include <string> +#include <utility> +#include <vector> + +#include <base/logging.h> +#include <base/strings/string_util.h> +#include <chromeos/strings/string_utils.h> + +using std::string; +using std::vector; + +namespace chromeos_dbus_bindings { + +IndentedText::IndentedText() : offset_(0) {} + +void IndentedText::AddBlankLine() { + AddLine(""); +} + +void IndentedText::AddBlock(const IndentedText& block) { + AddBlockWithOffset(block, 0); +} + +void IndentedText::AddBlockWithOffset(const IndentedText& block, size_t shift) { + for (const auto& member : block.contents_) { + AddLineWithOffset(member.first, member.second + shift); + } +} + +void IndentedText::AddLine(const std::string& line) { + AddLineWithOffset(line, 0); +} + +void IndentedText::AddLineWithOffset(const std::string& line, size_t shift) { + contents_.emplace_back(line, shift + offset_); +} + +void IndentedText::AddLineAndPushOffsetTo(const std::string& line, + size_t occurrence, + char c) { + AddLine(line); + size_t pos = 0; + while (occurrence > 0) { + pos = line.find(c, pos); + CHECK(pos != string::npos); + pos++; + occurrence--; + } + PushOffset(pos); +} + +void IndentedText::AddComments(const std::string& doc_string) { + // Try to retain indentation in the comments. Find the first non-empty line + // of the comment and find its whitespace indentation prefix. + // For all subsequent lines, remove the same whitespace prefix as found + // at the first line of the comment but keep any additional spaces to + // maintain the comment layout. + auto lines = chromeos::string_utils::Split(doc_string, "\n", false, false); + vector<string> lines_out; + lines_out.reserve(lines.size()); + bool first_nonempty_found = false; + std::string trim_prefix; + for (string line : lines) { + base::TrimWhitespaceASCII(line, base::TRIM_TRAILING, &line); + if (!first_nonempty_found) { + size_t pos = line.find_first_not_of(" \t"); + if (pos != std::string::npos) { + first_nonempty_found = true; + trim_prefix = line.substr(0, pos); + lines_out.push_back(line.substr(pos)); + } + } else { + if (base::StartsWithASCII(line, trim_prefix, false)) { + line = line.substr(trim_prefix.length()); + } else { + base::TrimWhitespaceASCII(line, base::TRIM_LEADING, &line); + } + lines_out.push_back(line); + } + } + + // We already eliminated all empty lines at the beginning of the comment + // block. Now remove the trailing empty lines. + while (!lines_out.empty() && lines_out.back().empty()) + lines_out.pop_back(); + + for (const string& line : lines_out) { + const bool all_whitespace = (line.find_first_not_of(" \t") == string::npos); + if (all_whitespace) { + AddLine("//"); + } else { + AddLine("// " + line); + } + } +} + +string IndentedText::GetContents() const { + string output; + for (const string& line : GetLines()) { + output.append(line); + output.append("\n"); + } + return output; +} + +std::vector<std::string> IndentedText::GetLines() const { + vector<string> result; + for (const auto& member : contents_) { + const string& line = member.first; + size_t shift = line.empty() ? 0 : member.second; + string indent(shift, ' '); + result.push_back(indent + line); + } + return result; +} + +void IndentedText::PushOffset(size_t shift) { + offset_ += shift; + offset_history_.push_back(shift); +} + +void IndentedText::PopOffset() { + CHECK(!offset_history_.empty()); + offset_ -= offset_history_.back(); + offset_history_.pop_back(); +} + +void IndentedText::Reset() { + offset_ = 0; + offset_history_.clear(); + contents_.clear(); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/indented_text.h b/chromeos-dbus-bindings/indented_text.h new file mode 100644 index 0000000..6651a58 --- /dev/null +++ b/chromeos-dbus-bindings/indented_text.h @@ -0,0 +1,70 @@ +// 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 CHROMEOS_DBUS_BINDINGS_INDENTED_TEXT_H_ +#define CHROMEOS_DBUS_BINDINGS_INDENTED_TEXT_H_ + +#include <string> +#include <utility> +#include <vector> + +#include <base/macros.h> + +namespace chromeos_dbus_bindings { + +class IndentedText { + public: + IndentedText(); + virtual ~IndentedText() = default; + + // Insert a blank line. + void AddBlankLine(); + + // Insert a block of indented text. + void AddBlock(const IndentedText& block); + void AddBlockWithOffset(const IndentedText& block, size_t shift); + + // Add a line at the current indentation. + void AddLine(const std::string& line); + void AddLineWithOffset(const std::string& line, size_t shift); + // Adds a line and pushes an offset past the |nth_occurrence| of character |c| + // in that line, effectively allowing to align following line to the position + // following that character. + void AddLineAndPushOffsetTo(const std::string& line, + size_t nth_occurrence, + char c); + + // Adds a block of comments. + void AddComments(const std::string& doc_string); + + // Return a string representing the indented text. + std::string GetContents() const; + + // Return a list of lines representing the intended indented text, not + // including the \n. + std::vector<std::string> GetLines() const; + + // Add or remove an offset to the current stack of indentation offsets. + void PushOffset(size_t shift); + void PopOffset(); + + // Reset to initial state. + void Reset(); + + + private: + using IndentedLine = std::pair<std::string, size_t>; + + friend class IndentedTextTest; + + size_t offset_; + std::vector<size_t> offset_history_; + std::vector<IndentedLine> contents_; + + DISALLOW_COPY_AND_ASSIGN(IndentedText); +}; + +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_INDENTED_TEXT_H_ diff --git a/chromeos-dbus-bindings/indented_text_unittest.cc b/chromeos-dbus-bindings/indented_text_unittest.cc new file mode 100644 index 0000000..a75c396 --- /dev/null +++ b/chromeos-dbus-bindings/indented_text_unittest.cc @@ -0,0 +1,183 @@ +// 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 "chromeos-dbus-bindings/indented_text.h" + +#include <string> +#include <vector> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +using std::string; +using std::vector; +using testing::ElementsAre; +using testing::Test; + +namespace chromeos_dbus_bindings { + +class IndentedTextTest : public Test { + protected: + size_t GetOffset() const { return text_.offset_; } + const vector<size_t>& GetHistory() const { return text_.offset_history_; } + IndentedText text_; +}; + +TEST_F(IndentedTextTest, Constructor) { + EXPECT_EQ("", text_.GetContents()); + EXPECT_EQ(0u, GetOffset()); + EXPECT_TRUE(GetHistory().empty()); +} + +TEST_F(IndentedTextTest, AddLine) { + const char kTestString0[] = "test"; + text_.AddLine(kTestString0); + EXPECT_EQ(string(kTestString0) + "\n", text_.GetContents()); + EXPECT_EQ(0u, GetOffset()); + EXPECT_TRUE(GetHistory().empty()); + + const char kTestString1[] = "me"; + text_.AddLine(kTestString1); + EXPECT_EQ(string(kTestString0) + "\n" + kTestString1 + "\n", + text_.GetContents()); + EXPECT_EQ(0u, GetOffset()); + EXPECT_TRUE(GetHistory().empty()); +} + +TEST_F(IndentedTextTest, AddLineWithOffset) { + const char kTestString[] = "test"; + const int kShift = 4; + text_.AddLineWithOffset(kTestString, kShift); + EXPECT_EQ(string(kShift, ' ') + kTestString + "\n", text_.GetContents()); +} + +TEST_F(IndentedTextTest, AddLineAndPushOffsetTo) { + text_.AddLineAndPushOffsetTo("foo(bar(baz", 2, '('); + EXPECT_THAT(GetHistory(), ElementsAre(8)); +} + +TEST_F(IndentedTextTest, AddBlock) { + IndentedText block0; + const char kTestString[] = "test"; + block0.AddLineWithOffset(kTestString, 10); + block0.AddLineWithOffset(kTestString, 20); + IndentedText block1; + block1.AddLineWithOffset(kTestString, 5); + block1.AddLineWithOffset(kTestString, 15); + text_.AddBlock(block0); + text_.AddBlock(block1); + EXPECT_EQ(block0.GetContents() + block1.GetContents(), text_.GetContents()); +} + +TEST_F(IndentedTextTest, AddBlockWithOffset) { + const char kTestString[] = "test"; + IndentedText block; + const size_t kOffset0 = 0; + block.AddLineWithOffset(kTestString, kOffset0); + const size_t kOffset1 = 4; + block.AddLineWithOffset(kTestString, kOffset1); + const size_t kOffset2 = 20; + text_.AddBlockWithOffset(block, kOffset2); + EXPECT_EQ(string(kOffset2 + kOffset0, ' ') + kTestString + "\n" + + string(kOffset2 + kOffset1, ' ') + kTestString + "\n", + text_.GetContents()); +} + +TEST_F(IndentedTextTest, PushPop) { + const char kTestString[] = "test"; + text_.AddLine(kTestString); + + const size_t kShift0 = 2; + text_.PushOffset(kShift0); + EXPECT_EQ(2u, GetOffset()); + EXPECT_THAT(GetHistory(), ElementsAre(kShift0)); + text_.AddLine(kTestString); + + const size_t kShift1 = 4; + text_.PushOffset(kShift1); + EXPECT_EQ(kShift0 + kShift1, GetOffset()); + EXPECT_THAT(GetHistory(), ElementsAre(kShift0, kShift1)); + text_.AddLine(kTestString); + + text_.PopOffset(); + text_.AddLine(kTestString); + EXPECT_EQ(2u, GetOffset()); + EXPECT_THAT(GetHistory(), ElementsAre(kShift0)); + + text_.PopOffset(); + text_.AddLine(kTestString); + EXPECT_EQ(0u, GetOffset()); + EXPECT_TRUE(GetHistory().empty()); + + EXPECT_EQ(string(kTestString) + "\n" + + string(kShift0, ' ') + kTestString + "\n" + + string(kShift0 + kShift1, ' ') + kTestString + "\n" + + string(kShift0, ' ') + kTestString + "\n" + + string(kTestString) + "\n", + text_.GetContents()); +} + +TEST_F(IndentedTextTest, Reset) { + text_.PushOffset(10); + text_.AddLine("test"); + EXPECT_NE("", text_.GetContents()); + EXPECT_NE(0u, GetOffset()); + EXPECT_FALSE(GetHistory().empty()); + text_.Reset(); + EXPECT_EQ("", text_.GetContents()); + EXPECT_EQ(0u, GetOffset()); + EXPECT_TRUE(GetHistory().empty()); +} + +TEST_F(IndentedTextTest, AddComments_Empty) { + text_.AddComments(""); + EXPECT_EQ("", text_.GetContents()); +} + +TEST_F(IndentedTextTest, AddComments_WhitespaceOnly) { + text_.AddComments(" \n \t \n"); + EXPECT_EQ("", text_.GetContents()); +} + +TEST_F(IndentedTextTest, AddComments_EmptyLines) { + string comment_block = R"( + + line1 + + line2 + + + )"; + text_.AddComments(comment_block); + EXPECT_EQ("// line1\n" + "//\n" + "// line2\n", text_.GetContents()); +} + +TEST_F(IndentedTextTest, AddComments_Indentation) { + string comment_block = R"( + line1 + - bullet1 + line2 + - bullet2 + line3 + )"; + text_.AddComments(comment_block); + EXPECT_EQ("// line1\n" + "// - bullet1\n" + "// line2\n" + "// - bullet2\n" + "// line3\n", text_.GetContents()); +} + +TEST_F(IndentedTextTest, GetLines) { + text_.AddLine("no indent"); + text_.PushOffset(2); + text_.AddLine("2 spaces"); + text_.AddLine(""); + + EXPECT_EQ((vector<string>{"no indent", " 2 spaces", ""}), text_.GetLines()); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/interface.h b/chromeos-dbus-bindings/interface.h new file mode 100644 index 0000000..73b1481 --- /dev/null +++ b/chromeos-dbus-bindings/interface.h @@ -0,0 +1,83 @@ +// 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 CHROMEOS_DBUS_BINDINGS_INTERFACE_H_ +#define CHROMEOS_DBUS_BINDINGS_INTERFACE_H_ + +#include <string> +#include <vector> + +namespace chromeos_dbus_bindings { + +struct Interface { + struct Argument { + Argument(const std::string& name_in, + const std::string& type_in) : name(name_in), type(type_in) {} + std::string name; + std::string type; + }; + struct Method { + enum class Kind { + kSimple, + kNormal, + kAsync, + kRaw + }; + Method(const std::string& name_in, + const std::vector<Argument>& input_arguments_in, + const std::vector<Argument>& output_arguments_in) + : name(name_in), + input_arguments(input_arguments_in), + output_arguments(output_arguments_in) {} + Method(const std::string& name_in, + const std::vector<Argument>& input_arguments_in) + : name(name_in), + input_arguments(input_arguments_in) {} + explicit Method(const std::string& name_in) : name(name_in) {} + std::string name; + std::vector<Argument> input_arguments; + std::vector<Argument> output_arguments; + std::string doc_string; + Kind kind{Kind::kNormal}; + bool is_const{false}; + bool include_dbus_message{false}; + }; + struct Signal { + Signal(const std::string& name_in, + const std::vector<Argument>& arguments_in) + : name(name_in), arguments(arguments_in) {} + explicit Signal(const std::string& name_in) : name(name_in) {} + std::string name; + std::vector<Argument> arguments; + std::string doc_string; + }; + struct Property { + Property(const std::string& name_in, + const std::string& type_in, + const std::string& access_in) + : name(name_in), type(type_in), access(access_in) {} + std::string name; + std::string type; + std::string access; + std::string doc_string; + }; + + Interface() = default; + Interface(const std::string& name_in, + const std::vector<Method>& methods_in, + const std::vector<Signal>& signals_in, + const std::vector<Property>& properties_in) + : name(name_in), methods(methods_in), signals(signals_in), + properties(properties_in) {} + std::string name; + std::string path; + std::vector<Method> methods; + std::vector<Signal> signals; + std::vector<Property> properties; + std::string doc_string; +}; + +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_INTERFACE_H_ diff --git a/chromeos-dbus-bindings/method_name_generator.cc b/chromeos-dbus-bindings/method_name_generator.cc new file mode 100644 index 0000000..5332660 --- /dev/null +++ b/chromeos-dbus-bindings/method_name_generator.cc @@ -0,0 +1,46 @@ +// 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 "chromeos-dbus-bindings/method_name_generator.h" + +#include <base/files/file_path.h> +#include <base/strings/stringprintf.h> + +#include "chromeos-dbus-bindings/indented_text.h" +#include "chromeos-dbus-bindings/interface.h" +#include "chromeos-dbus-bindings/name_parser.h" + +using std::string; +using std::vector; + +namespace chromeos_dbus_bindings { + +// static +string MethodNameGenerator::GenerateMethodNameConstant( + const string& method_name) { + return "k" + method_name + "Method"; +} + +// static +bool MethodNameGenerator::GenerateMethodNames( + const vector<Interface>& interfaces, + const base::FilePath& output_file) { + string contents; + IndentedText text; + for (const auto& interface : interfaces) { + text.AddBlankLine(); + NameParser parser{interface.name}; + parser.AddOpenNamespaces(&text, true); + for (const auto& method : interface.methods) { + text.AddLine( + base::StringPrintf("const char %s[] = \"%s\";", + GenerateMethodNameConstant(method.name).c_str(), + method.name.c_str())); + } + parser.AddCloseNamespaces(&text, true); + } + return HeaderGenerator::WriteTextToFile(output_file, text); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/method_name_generator.h b/chromeos-dbus-bindings/method_name_generator.h new file mode 100644 index 0000000..24a3dc1 --- /dev/null +++ b/chromeos-dbus-bindings/method_name_generator.h @@ -0,0 +1,39 @@ +// 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 CHROMEOS_DBUS_BINDINGS_METHOD_NAME_GENERATOR_H_ +#define CHROMEOS_DBUS_BINDINGS_METHOD_NAME_GENERATOR_H_ + +#include <string> +#include <vector> + +#include <base/macros.h> + +#include "chromeos-dbus-bindings/header_generator.h" + +namespace base { + +class FilePath; + +} // namespace base + +namespace chromeos_dbus_bindings { + +struct Interface; + +class MethodNameGenerator : public HeaderGenerator { + public: + static bool GenerateMethodNames(const std::vector<Interface>& interfaces, + const base::FilePath& output_file); + static std::string GenerateMethodNameConstant(const std::string& method_name); + + private: + friend class MethodNameGeneratorTest; + + DISALLOW_COPY_AND_ASSIGN(MethodNameGenerator); +}; + +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_METHOD_NAME_GENERATOR_H_ diff --git a/chromeos-dbus-bindings/method_name_generator_unittest.cc b/chromeos-dbus-bindings/method_name_generator_unittest.cc new file mode 100644 index 0000000..040c188 --- /dev/null +++ b/chromeos-dbus-bindings/method_name_generator_unittest.cc @@ -0,0 +1,69 @@ +// 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 "chromeos-dbus-bindings/method_name_generator.h" + +#include <string> +#include <vector> + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <gtest/gtest.h> + +#include "chromeos-dbus-bindings/interface.h" + +using std::string; +using testing::Test; + +namespace chromeos_dbus_bindings { + +namespace { + +const char kMethodName0[] = "Zircon"; +const char kMethodName1[] = "Encrusted"; +const char kMethodName2[] = "Tweezers"; +const char kExpectedOutput[] = R"( +namespace MyInterface { +const char kZirconMethod[] = "Zircon"; +const char kEncrustedMethod[] = "Encrusted"; +const char kTweezersMethod[] = "Tweezers"; +} // namespace MyInterface +)"; + +} // namespace + +class MethodNameGeneratorTest : public Test { + public: + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + protected: + base::FilePath CreateInputFile(const string& contents) { + base::FilePath path; + EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &path)); + int written = base::WriteFile(path, contents.c_str(), contents.size()); + EXPECT_EQ(contents.size(), static_cast<size_t>(written)); + return path; + } + + base::ScopedTempDir temp_dir_; +}; + +TEST_F(MethodNameGeneratorTest, GnerateMethodNames) { + Interface interface; + interface.name = "MyInterface"; + interface.methods.emplace_back(kMethodName0); + interface.methods.emplace_back(kMethodName1); + interface.methods.emplace_back(kMethodName2); + base::FilePath output_path = temp_dir_.path().Append("output.h"); + EXPECT_TRUE(MethodNameGenerator::GenerateMethodNames({interface}, + output_path)); + string contents; + EXPECT_TRUE(base::ReadFileToString(output_path, &contents)); + EXPECT_STREQ(kExpectedOutput, contents.c_str()); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/name_parser.cc b/chromeos-dbus-bindings/name_parser.cc new file mode 100644 index 0000000..35cbb61 --- /dev/null +++ b/chromeos-dbus-bindings/name_parser.cc @@ -0,0 +1,98 @@ +// 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 "chromeos-dbus-bindings/name_parser.h" + +#include <string> + +#include <base/strings/string_util.h> +#include <base/strings/stringprintf.h> +#include <chromeos/strings/string_utils.h> + +#include "chromeos-dbus-bindings/indented_text.h" + +namespace chromeos_dbus_bindings { + +namespace { + +void AddOpenNamespace(IndentedText *text, const std::string& name) { + text->AddLine(base::StringPrintf("namespace %s {", name.c_str())); +} + +void AddCloseNamespace(IndentedText *text, const std::string& name) { + text->AddLine(base::StringPrintf("} // namespace %s", name.c_str())); +} + +} // anonymous namespace + +NameParser::NameParser(const std::string& name) + : namespaces{chromeos::string_utils::Split(name, ".")} { + CHECK(!namespaces.empty()) << "Empty name specified"; + type_name = namespaces.back(); + namespaces.pop_back(); +} + +std::string NameParser::MakeFullyQualified(const std::string& name) const { + std::vector<std::string> parts = namespaces; + parts.push_back(name); + return chromeos::string_utils::Join("::", parts); +} + +std::string NameParser::MakeFullCppName() const { + return MakeFullyQualified(type_name); +} + +std::string NameParser::MakeVariableName() const { + // Convert CamelCase name to google_style variable name. + std::string result; + bool last_upper = true; + for (char c : type_name) { + bool is_upper = isupper(c); + if (is_upper) { + if (!last_upper) + result += '_'; + c = base::ToLowerASCII(c); + } + last_upper = is_upper; + result.push_back(c); + } + return result; +} + +std::string NameParser::MakeInterfaceName(bool fully_qualified) const { + std::string interface_name = type_name + "Interface"; + return fully_qualified ? MakeFullyQualified(interface_name) : interface_name; +} + +std::string NameParser::MakeProxyName(bool fully_qualified) const { + std::string proxy_name = type_name + "Proxy"; + return fully_qualified ? MakeFullyQualified(proxy_name) : proxy_name; +} + +std::string NameParser::MakeAdaptorName(bool fully_qualified) const { + std::string adaptor_name = type_name + "Adaptor"; + return fully_qualified ? MakeFullyQualified(adaptor_name) : adaptor_name; +} + +void NameParser::AddOpenNamespaces(IndentedText *text, + bool add_main_type) const { + for (const auto& ns : namespaces) { + AddOpenNamespace(text, ns); + } + + if (add_main_type) + AddOpenNamespace(text, type_name); +} + +void NameParser::AddCloseNamespaces(IndentedText *text, + bool add_main_type) const { + if (add_main_type) + AddCloseNamespace(text, type_name); + + for (auto it = namespaces.rbegin(); it != namespaces.rend(); ++it) { + AddCloseNamespace(text, *it); + } +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/name_parser.h b/chromeos-dbus-bindings/name_parser.h new file mode 100644 index 0000000..0282819 --- /dev/null +++ b/chromeos-dbus-bindings/name_parser.h @@ -0,0 +1,63 @@ +// 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 CHROMEOS_DBUS_BINDINGS_NAME_PARSER_H_ +#define CHROMEOS_DBUS_BINDINGS_NAME_PARSER_H_ + +#include <string> +#include <vector> + +#include <base/macros.h> + +namespace chromeos_dbus_bindings { + +struct Interface; +class IndentedText; + +// A helper class that allows to decompose D-Bus name strings such as +// "org.chromium.TestInterface" into components and be able to construct the +// corresponding C++ identifiers, namespaces, variable names, etc. +class NameParser { + public: + explicit NameParser(const std::string& name); + + // Returns fully-qualified C++ type name for the current D-Bus name + // for example "org::chromium::TestInterface". + std::string MakeFullCppName() const; + + // Returns a variable name suitable for object of this type. + // For example "test_interface". + std::string MakeVariableName() const; + + // Returns a name of an interface for the given type, optionally qualifying + // it with the C++ namespaces. + std::string MakeInterfaceName(bool fully_qualified) const; + + // Returns a name of a proxy class for the given type, optionally qualifying + // it with the C++ namespaces. + std::string MakeProxyName(bool fully_qualified) const; + + // Returns a name of an adaptor class for the given type, optionally + // qualifying it with the C++ namespaces. + std::string MakeAdaptorName(bool fully_qualified) const; + + // Adds opening "namespace ... {" statements to |text|. + // If |add_main_type| is true, adds the main type name as a namespace as well. + void AddOpenNamespaces(IndentedText *text, bool add_main_type) const; + + // Adds closing "} // namespace ..." statements to |text|. + // If |add_main_type| is true, adds the main type name as a namespace as well. + void AddCloseNamespaces(IndentedText *text, bool add_main_type) const; + + std::string type_name; // e.g. "TestInterface". + std::vector<std::string> namespaces; // e.g. {"org", "chromium"}. + + private: + // Helper function to prepend the C++ namespaces to the |name|. + std::string MakeFullyQualified(const std::string& name) const; +}; + +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_NAME_PARSER_H_ diff --git a/chromeos-dbus-bindings/name_parser_unittest.cc b/chromeos-dbus-bindings/name_parser_unittest.cc new file mode 100644 index 0000000..25c6c92 --- /dev/null +++ b/chromeos-dbus-bindings/name_parser_unittest.cc @@ -0,0 +1,123 @@ +// 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 "chromeos-dbus-bindings/name_parser.h" + +#include <map> +#include <string> + +#include <gmock/gmock.h> +#include <gtest/gtest.h> + +#include "chromeos-dbus-bindings/indented_text.h" + +namespace chromeos_dbus_bindings { + +TEST(NameParser, Parsing_Empty) { + EXPECT_DEATH(NameParser{""}, "Empty name specified"); +} + +TEST(NameParser, Parsing_NoNamespaces) { + NameParser parser{"foo"}; + EXPECT_EQ("foo", parser.type_name); + EXPECT_TRUE(parser.namespaces.empty()); +} + +TEST(NameParser, Parsing_FullyQualified) { + NameParser parser{"foo.bar.FooBar"}; + EXPECT_EQ("FooBar", parser.type_name); + EXPECT_THAT(parser.namespaces, testing::ElementsAre("foo", "bar")); +} + +TEST(NameParser, MakeFullCppName) { + NameParser parser{"foo.bar.FooBar"}; + EXPECT_EQ("foo::bar::FooBar", parser.MakeFullCppName()); +} + +TEST(NameParser, MakeVariableName) { + NameParser parser{"foo.bar.FooBar"}; + EXPECT_EQ("foo_bar", parser.MakeVariableName()); +} + +TEST(NameParser, MakeVariableName_NoCapitals) { + NameParser parser{"foo"}; + EXPECT_EQ("foo", parser.MakeVariableName()); +} + +TEST(NameParser, MakeVariableName_NoInitialCapital) { + NameParser parser{"fooBarBaz"}; + EXPECT_EQ("foo_bar_baz", parser.MakeVariableName()); +} + +TEST(NameParser, MakeVariableName_AllCapitals) { + NameParser parser{"UUID"}; + EXPECT_EQ("uuid", parser.MakeVariableName()); +} + +TEST(NameParser, MakeVariableName_MixedCapital) { + NameParser parser{"FOObarBaz"}; + EXPECT_EQ("foobar_baz", parser.MakeVariableName()); +} + +TEST(NameParser, MakeInterfaceName) { + NameParser parser{"foo.bar.FooBar"}; + EXPECT_EQ("FooBarInterface", parser.MakeInterfaceName(false)); + EXPECT_EQ("foo::bar::FooBarInterface", parser.MakeInterfaceName(true)); +} + +TEST(NameParser, MakeProxyName) { + NameParser parser{"foo.bar.FooBar"}; + EXPECT_EQ("FooBarProxy", parser.MakeProxyName(false)); + EXPECT_EQ("foo::bar::FooBarProxy", parser.MakeProxyName(true)); +} + +TEST(NameParser, MakeAdaptorName) { + NameParser parser{"foo.bar.FooBar"}; + EXPECT_EQ("FooBarAdaptor", parser.MakeAdaptorName(false)); + EXPECT_EQ("foo::bar::FooBarAdaptor", parser.MakeAdaptorName(true)); +} + +TEST(NameParser, AddOpenNamespaces) { + std::string expected = + "namespace foo {\n" + "namespace bar {\n"; + NameParser parser{"foo.bar.FooBar"}; + IndentedText text; + parser.AddOpenNamespaces(&text, false); + EXPECT_EQ(expected, text.GetContents()); +} + +TEST(NameParser, AddOpenNamespaces_WithMainType) { + std::string expected = + "namespace foo {\n" + "namespace bar {\n" + "namespace FooBar {\n"; + NameParser parser{"foo.bar.FooBar"}; + IndentedText text; + parser.AddOpenNamespaces(&text, true); + EXPECT_EQ(expected, text.GetContents()); +} + +TEST(NameParser, AddCloseNamespaces) { + std::string expected = + "} // namespace bar\n" + "} // namespace foo\n"; + NameParser parser{"foo.bar.FooBar"}; + IndentedText text; + parser.AddCloseNamespaces(&text, false); + EXPECT_EQ(expected, text.GetContents()); +} + +TEST(NameParser, AddCloseNamespaces_WithMainType) { + std::string expected = + "} // namespace FooBar\n" + "} // namespace bar\n" + "} // namespace foo\n"; + NameParser parser{"foo.bar.FooBar"}; + IndentedText text; + parser.AddCloseNamespaces(&text, true); + EXPECT_EQ(expected, text.GetContents()); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/proxy_generator.cc b/chromeos-dbus-bindings/proxy_generator.cc new file mode 100644 index 0000000..2e96975 --- /dev/null +++ b/chromeos-dbus-bindings/proxy_generator.cc @@ -0,0 +1,1270 @@ +// 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 "chromeos-dbus-bindings/proxy_generator.h" + +#include <utility> + +#include <base/files/file_path.h> +#include <base/logging.h> +#include <base/strings/stringprintf.h> +#include <chromeos/strings/string_utils.h> + +#include "chromeos-dbus-bindings/dbus_signature.h" +#include "chromeos-dbus-bindings/indented_text.h" +#include "chromeos-dbus-bindings/name_parser.h" + +using base::StringPrintf; +using std::pair; +using std::string; +using std::vector; + +namespace chromeos_dbus_bindings { + +namespace { +// Helper struct to encapsulate information about method call parameter during +// code generation. +struct ParamDef { + ParamDef(const string& param_type, const string& param_name, bool param_ref) + : type(param_type), name(param_name), is_const_ref(param_ref) {} + + string type; + string name; + bool is_const_ref; +}; + +string GetParamString(const ParamDef& param_def) { + return StringPrintf(param_def.is_const_ref ? "const %s& %s" : "%s* %s", + param_def.type.c_str(), param_def.name.c_str()); +} +} // anonymous namespace + +// static +bool ProxyGenerator::GenerateProxies( + const ServiceConfig& config, + const std::vector<Interface>& interfaces, + const base::FilePath& output_file) { + IndentedText text; + + text.AddLine("// Automatic generation of D-Bus interfaces:"); + for (const auto& interface : interfaces) { + text.AddLine(StringPrintf("// - %s", interface.name.c_str())); + } + string header_guard = GenerateHeaderGuard(output_file); + text.AddLine(StringPrintf("#ifndef %s", header_guard.c_str())); + text.AddLine(StringPrintf("#define %s", header_guard.c_str())); + text.AddLine("#include <memory>"); + text.AddLine("#include <string>"); + text.AddLine("#include <vector>"); + text.AddBlankLine(); + text.AddLine("#include <base/bind.h>"); + text.AddLine("#include <base/callback.h>"); + text.AddLine("#include <base/logging.h>"); + text.AddLine("#include <base/macros.h>"); + text.AddLine("#include <base/memory/ref_counted.h>"); + text.AddLine("#include <chromeos/any.h>"); + text.AddLine("#include <chromeos/dbus/dbus_method_invoker.h>"); + text.AddLine("#include <chromeos/dbus/dbus_property.h>"); + text.AddLine("#include <chromeos/dbus/dbus_signal_handler.h>"); + text.AddLine("#include <chromeos/errors/error.h>"); + text.AddLine("#include <chromeos/variant_dictionary.h>"); + text.AddLine("#include <dbus/bus.h>"); + text.AddLine("#include <dbus/message.h>"); + text.AddLine("#include <dbus/object_manager.h>"); + text.AddLine("#include <dbus/object_path.h>"); + text.AddLine("#include <dbus/object_proxy.h>"); + text.AddBlankLine(); + + if (!config.object_manager.name.empty()) { + // Add forward-declaration for Object Manager proxy class. + NameParser parser{config.object_manager.name}; + parser.AddOpenNamespaces(&text, false); + text.AddLine(StringPrintf("class %s;", + parser.MakeProxyName(false).c_str())); + parser.AddCloseNamespaces(&text, false); + text.AddBlankLine(); + } + + for (const auto& interface : interfaces) { + GenerateInterfaceProxyInterface(config, interface, &text); + GenerateInterfaceProxy(config, interface, &text); + } + + ObjectManager::GenerateProxy(config, interfaces, &text); + + text.AddLine(StringPrintf("#endif // %s", header_guard.c_str())); + return WriteTextToFile(output_file, text); +} + +// static +bool ProxyGenerator::GenerateMocks(const ServiceConfig& config, + const std::vector<Interface>& interfaces, + const base::FilePath& mock_file, + const base::FilePath& proxy_file) { + IndentedText text; + + text.AddLine("// Automatic generation of D-Bus interface mock proxies for:"); + for (const auto& interface : interfaces) { + text.AddLine(StringPrintf("// - %s", interface.name.c_str())); + } + string header_guard = GenerateHeaderGuard(mock_file); + text.AddLine(StringPrintf("#ifndef %s", header_guard.c_str())); + text.AddLine(StringPrintf("#define %s", header_guard.c_str())); + text.AddLine("#include <string>"); + text.AddLine("#include <vector>"); + text.AddBlankLine(); + text.AddLine("#include <base/callback_forward.h>"); + text.AddLine("#include <base/macros.h>"); + text.AddLine("#include <chromeos/any.h>"); + text.AddLine("#include <chromeos/errors/error.h>"); + text.AddLine("#include <chromeos/variant_dictionary.h>"); + text.AddLine("#include <gmock/gmock.h>"); + text.AddBlankLine(); + + if (!proxy_file.empty()) { + // If we have a proxy header file, it would have the proxy interfaces we + // need to base our mocks on, so we need to include that header file. + // Generate a relative path from |mock_file| to |proxy_file|. + + // First, get the path components for both source and destination paths. + std::vector<base::FilePath::StringType> src_components; + mock_file.DirName().GetComponents(&src_components); + std::vector<base::FilePath::StringType> dest_components; + proxy_file.DirName().GetComponents(&dest_components); + + // Find the common root. + + // I wish we had C++14 and its 4-parameter version of std::mismatch()... + auto src_end = src_components.end(); + if (src_components.size() > dest_components.size()) + src_end = src_components.begin() + dest_components.size(); + + auto mismatch_pair = + std::mismatch(src_components.begin(), src_end, dest_components.begin()); + + // For each remaining components in the |src_components|, generate the + // parent directory references (".."). + size_t src_count = std::distance(mismatch_pair.first, src_components.end()); + std::vector<base::FilePath::StringType> components{ + src_count, base::FilePath::kParentDirectory}; + // Append the remaining components from |dest_components|. + components.insert(components.end(), + mismatch_pair.second, dest_components.end()); + // Finally, add the base name of the target file name. + components.push_back(proxy_file.BaseName().value()); + // Now reconstruct the relative path. + base::FilePath relative_path{base::FilePath::kCurrentDirectory}; + for (const auto& component : components) + relative_path = relative_path.Append(component); + text.AddLine(StringPrintf("#include \"%s\"", + relative_path.value().c_str())); + text.AddBlankLine(); + } + + for (const auto& interface : interfaces) { + // If we have no proxy file, we need the abstract interfaces generated here. + if (proxy_file.empty()) + GenerateInterfaceProxyInterface(config, interface, &text); + GenerateInterfaceMock(config, interface, &text); + } + + text.AddLine(StringPrintf("#endif // %s", header_guard.c_str())); + return WriteTextToFile(mock_file, text); +} + +// static +void ProxyGenerator::GenerateInterfaceProxyInterface( + const ServiceConfig& config, + const Interface& interface, + IndentedText* text) { + NameParser parser{interface.name}; + string proxy_name = parser.MakeProxyName(false); + string base_interface_name = proxy_name + "Interface"; + + parser.AddOpenNamespaces(text, false); + text->AddBlankLine(); + + text->AddLine(StringPrintf("// Abstract interface proxy for %s.", + parser.MakeFullCppName().c_str())); + text->AddComments(interface.doc_string); + text->AddLine(StringPrintf("class %s {", base_interface_name.c_str())); + text->AddLineWithOffset("public:", kScopeOffset); + text->PushOffset(kBlockOffset); + text->AddLine( + StringPrintf("virtual ~%s() = default;", base_interface_name.c_str())); + + for (const auto& method : interface.methods) { + AddMethodProxy(method, interface.name, true, text); + AddAsyncMethodProxy(method, interface.name, true, text); + } + for (const auto& signal : interface.signals) { + AddSignalHandlerRegistration(signal, interface.name, true, text); + } + AddProperties(config, interface, true, text); + + text->PopOffset(); + text->AddLine("};"); + text->AddBlankLine(); + + parser.AddCloseNamespaces(text, false); + text->AddBlankLine(); +} + +// static +void ProxyGenerator::GenerateInterfaceProxy(const ServiceConfig& config, + const Interface& interface, + IndentedText* text) { + NameParser parser{interface.name}; + string proxy_name = parser.MakeProxyName(false); + string base_interface_name = proxy_name + "Interface"; + + parser.AddOpenNamespaces(text, false); + text->AddBlankLine(); + + text->AddLine(StringPrintf("// Interface proxy for %s.", + parser.MakeFullCppName().c_str())); + text->AddComments(interface.doc_string); + text->AddLine(StringPrintf("class %s final : public %s {", + proxy_name.c_str(), base_interface_name.c_str())); + text->AddLineWithOffset("public:", kScopeOffset); + text->PushOffset(kBlockOffset); + AddPropertySet(config, interface, text); + AddConstructor(config, interface, proxy_name, text); + AddDestructor(proxy_name, text); + for (const auto& signal : interface.signals) { + AddSignalHandlerRegistration(signal, interface.name, false, text); + } + AddReleaseObjectProxy(text); + AddGetObjectPath(text); + AddGetObjectProxy(text); + if (!config.object_manager.name.empty() && !interface.properties.empty()) + AddPropertyPublicMethods(proxy_name, text); + for (const auto& method : interface.methods) { + AddMethodProxy(method, interface.name, false, text); + AddAsyncMethodProxy(method, interface.name, false, text); + } + AddProperties(config, interface, false, text); + + text->PopOffset(); + text->AddBlankLine(); + text->AddLineWithOffset("private:", kScopeOffset); + + text->PushOffset(kBlockOffset); + if (!config.object_manager.name.empty() && !interface.properties.empty()) + AddOnPropertyChanged(text); + text->AddLine("scoped_refptr<dbus::Bus> bus_;"); + if (config.service_name.empty()) { + text->AddLine("std::string service_name_;"); + } else { + text->AddLine(StringPrintf("const std::string service_name_{\"%s\"};", + config.service_name.c_str())); + } + if (interface.path.empty()) { + text->AddLine("dbus::ObjectPath object_path_;"); + } else { + text->AddLine(StringPrintf("const dbus::ObjectPath object_path_{\"%s\"};", + interface.path.c_str())); + } + if (!config.object_manager.name.empty() && !interface.properties.empty()) { + text->AddLine("PropertySet* property_set_;"); + text->AddLine(StringPrintf("base::Callback<void(%s*, const std::string&)> " + "on_property_changed_;", + proxy_name.c_str())); + } + text->AddLine("dbus::ObjectProxy* dbus_object_proxy_;"); + text->AddBlankLine(); + + if (!config.object_manager.name.empty() && !interface.properties.empty()) { + text->AddLine(StringPrintf( + "friend class %s;", + NameParser{config.object_manager.name}.MakeProxyName(true).c_str())); + } + text->AddLine(StringPrintf("DISALLOW_COPY_AND_ASSIGN(%s);", + proxy_name.c_str())); + text->PopOffset(); + text->AddLine("};"); + + text->AddBlankLine(); + + parser.AddCloseNamespaces(text, false); + + text->AddBlankLine(); +} + +// static +void ProxyGenerator::GenerateInterfaceMock(const ServiceConfig& config, + const Interface& interface, + IndentedText* text) { + NameParser parser{interface.name}; + string proxy_name = parser.MakeProxyName(false); + string base_interface_name = proxy_name + "Interface"; + string mock_name = proxy_name + "Mock"; + + parser.AddOpenNamespaces(text, false); + text->AddBlankLine(); + + text->AddLine(StringPrintf("// Mock object for %s.", + base_interface_name.c_str())); + text->AddLine(StringPrintf("class %s final : public %s {", + mock_name.c_str(), base_interface_name.c_str())); + text->AddLineWithOffset("public:", kScopeOffset); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("%s() = default;", mock_name.c_str())); + text->AddBlankLine(); + + for (const auto& method : interface.methods) { + AddMethodMock(method, interface.name, text); + AddAsyncMethodMock(method, interface.name, text); + } + for (const auto& signal : interface.signals) { + AddSignalHandlerRegistrationMock(signal, text); + } + + DbusSignature signature; + for (const auto& prop : interface.properties) { + string type; + CHECK(signature.Parse(prop.type, &type)); + MakeConstReferenceIfNeeded(&type); + string name = NameParser{prop.name}.MakeVariableName(); + text->AddLine(StringPrintf("MOCK_CONST_METHOD0(%s, %s());", + name.c_str(), type.c_str())); + } + + text->PopOffset(); + text->AddBlankLine(); + text->AddLineWithOffset("private:", kScopeOffset); + text->AddLineWithOffset(StringPrintf("DISALLOW_COPY_AND_ASSIGN(%s);", + mock_name.c_str()), + kBlockOffset); + text->AddLine("};"); + + parser.AddCloseNamespaces(text, false); + text->AddBlankLine(); +} + +// static +void ProxyGenerator::AddConstructor(const ServiceConfig& config, + const Interface& interface, + const string& class_name, + IndentedText* text) { + IndentedText block; + vector<ParamDef> args{{"scoped_refptr<dbus::Bus>", "bus", true}}; + if (config.service_name.empty()) + args.emplace_back("std::string", "service_name", true); + if (interface.path.empty()) + args.emplace_back("dbus::ObjectPath", "object_path", true); + if (!config.object_manager.name.empty() && !interface.properties.empty()) + args.emplace_back("PropertySet", "property_set", false); + + if (args.size() == 1) { + block.AddLine(StringPrintf("%s(%s) :", class_name.c_str(), + GetParamString(args.front()).c_str())); + } else { + block.AddLine(StringPrintf("%s(", class_name.c_str())); + block.PushOffset(kLineContinuationOffset); + for (size_t i = 0; i < args.size() - 1; i++) { + block.AddLine(StringPrintf("%s,", GetParamString(args[i]).c_str())); + } + block.AddLine(StringPrintf("%s) :", GetParamString(args.back()).c_str())); + } + block.PushOffset(kLineContinuationOffset); + for (const auto& arg : args) { + block.AddLine(StringPrintf("%s_{%s},", arg.name.c_str(), + arg.name.c_str())); + } + block.AddLine("dbus_object_proxy_{"); + block.AddLineWithOffset( + "bus_->GetObjectProxy(service_name_, object_path_)} {", + kLineContinuationOffset); + block.PopOffset(); + if (args.size() > 1) + block.PopOffset(); + block.AddLine("}"); + block.AddBlankLine(); + text->AddBlock(block); +} + +// static +void ProxyGenerator::AddDestructor(const string& class_name, + IndentedText* text) { + IndentedText block; + block.AddLine(StringPrintf("~%s() override {", class_name.c_str())); + block.AddLine("}"); + text->AddBlock(block); +} + +// static +void ProxyGenerator::AddReleaseObjectProxy(IndentedText* text) { + text->AddBlankLine(); + text->AddLine("void ReleaseObjectProxy(const base::Closure& callback) {"); + text->AddLineWithOffset( + "bus_->RemoveObjectProxy(service_name_, object_path_, callback);", + kBlockOffset); + text->AddLine("}"); +} + +// static +void ProxyGenerator::AddGetObjectPath(IndentedText* text) { + text->AddBlankLine(); + text->AddLine("const dbus::ObjectPath& GetObjectPath() const {"); + text->AddLineWithOffset("return object_path_;", kBlockOffset); + text->AddLine("}"); +} + +// static +void ProxyGenerator::AddGetObjectProxy(IndentedText* text) { + text->AddBlankLine(); + text->AddLine("dbus::ObjectProxy* GetObjectProxy() const { " + "return dbus_object_proxy_; }"); +} + +// static +void ProxyGenerator::AddPropertyPublicMethods(const string& class_name, + IndentedText* text) { + text->AddBlankLine(); + text->AddLine("void SetPropertyChangedCallback("); + text->AddLineWithOffset( + StringPrintf("const base::Callback<void(%s*, " + "const std::string&)>& callback) {", class_name.c_str()), + kLineContinuationOffset); + text->AddLineWithOffset("on_property_changed_ = callback;", kBlockOffset); + text->AddLine("}"); + text->AddBlankLine(); + + text->AddLine("const PropertySet* GetProperties() const " + "{ return property_set_; }"); + text->AddLine("PropertySet* GetProperties() { return property_set_; }"); +} + +// static +void ProxyGenerator::AddOnPropertyChanged(IndentedText* text) { + text->AddLine("void OnPropertyChanged(const std::string& property_name) {"); + text->PushOffset(kBlockOffset); + text->AddLine("if (!on_property_changed_.is_null())"); + text->PushOffset(kBlockOffset); + text->AddLine("on_property_changed_.Run(this, property_name);"); + text->PopOffset(); + text->PopOffset(); + text->AddLine("}"); + text->AddBlankLine(); +} + +void ProxyGenerator::AddSignalHandlerRegistration( + const Interface::Signal& signal, + const string& interface_name, + bool declaration_only, + IndentedText* text) { + IndentedText block; + block.AddBlankLine(); + block.AddLine(StringPrintf("%svoid Register%sSignalHandler(", + declaration_only ? "virtual " : "", + signal.name.c_str())); + block.PushOffset(kLineContinuationOffset); + AddSignalCallbackArg(signal, false, &block); + block.AddLine(StringPrintf( + "dbus::ObjectProxy::OnConnectedCallback on_connected_callback)%s", + declaration_only ? " = 0;" : " override {")); + if (!declaration_only) { + block.PopOffset(); // Method signature arguments + block.PushOffset(kBlockOffset); + block.AddLine("chromeos::dbus_utils::ConnectToSignal("); + block.PushOffset(kLineContinuationOffset); + block.AddLine("dbus_object_proxy_,"); + block.AddLine(StringPrintf("\"%s\",", interface_name.c_str())); + block.AddLine(StringPrintf("\"%s\",", signal.name.c_str())); + block.AddLine("signal_callback,"); + block.AddLine("on_connected_callback);"); + block.PopOffset(); // Function call line continuation + block.PopOffset(); // Method body + block.AddLine("}"); + } + text->AddBlock(block); +} + +// static +void ProxyGenerator::AddPropertySet(const ServiceConfig& config, + const Interface& interface, + IndentedText* text) { + // Must have ObjectManager in order for property system to work correctly. + if (config.object_manager.name.empty()) + return; + + IndentedText block; + block.AddLine("class PropertySet : public dbus::PropertySet {"); + block.AddLineWithOffset("public:", kScopeOffset); + block.PushOffset(kBlockOffset); + block.AddLineAndPushOffsetTo("PropertySet(dbus::ObjectProxy* object_proxy,", + 1, '('); + block.AddLine("const PropertyChangedCallback& callback)"); + block.PopOffset(); + block.PushOffset(kLineContinuationOffset); + block.AddLineAndPushOffsetTo(": dbus::PropertySet{object_proxy,", 1, '{'); + block.AddLine(StringPrintf("\"%s\",", interface.name.c_str())); + block.AddLine("callback} {"); + block.PopOffset(); + block.PopOffset(); + block.PushOffset(kBlockOffset); + for (const auto& prop : interface.properties) { + block.AddLine( + StringPrintf("RegisterProperty(%sName(), &%s);", + prop.name.c_str(), + NameParser{prop.name}.MakeVariableName().c_str())); + } + block.PopOffset(); + block.AddLine("}"); + block.AddBlankLine(); + + DbusSignature signature; + for (const auto& prop : interface.properties) { + string type; + CHECK(signature.Parse(prop.type, &type)); + block.AddLine( + StringPrintf("chromeos::dbus_utils::Property<%s> %s;", + type.c_str(), + NameParser{prop.name}.MakeVariableName().c_str())); + } + block.AddBlankLine(); + + block.PopOffset(); + block.AddLineWithOffset("private:", kScopeOffset); + block.AddLineWithOffset("DISALLOW_COPY_AND_ASSIGN(PropertySet);", + kBlockOffset); + block.AddLine("};"); + block.AddBlankLine(); + + text->AddBlock(block); +} + +// static +void ProxyGenerator::AddProperties(const ServiceConfig& config, + const Interface& interface, + bool declaration_only, + IndentedText* text) { + // Must have ObjectManager in order for property system to work correctly. + if (config.object_manager.name.empty()) + return; + + if (declaration_only && !interface.properties.empty()) + text->AddBlankLine(); + + DbusSignature signature; + for (const auto& prop : interface.properties) { + if (declaration_only) { + text->AddLine( + StringPrintf("static const char* %sName() { return \"%s\"; }", + prop.name.c_str(), + prop.name.c_str())); + } + string type; + CHECK(signature.Parse(prop.type, &type)); + MakeConstReferenceIfNeeded(&type); + string name = NameParser{prop.name}.MakeVariableName(); + if (!declaration_only) + text->AddBlankLine(); + text->AddLine( + StringPrintf("%s%s %s() const%s", + declaration_only ? "virtual " : "", + type.c_str(), + name.c_str(), + declaration_only ? " = 0;" : " override {")); + if (!declaration_only) { + text->AddLineWithOffset( + StringPrintf("return property_set_->%s.value();", name.c_str()), + kBlockOffset); + text->AddLine("}"); + } + } +} + +// static +void ProxyGenerator::AddMethodProxy(const Interface::Method& method, + const string& interface_name, + bool declaration_only, + IndentedText* text) { + IndentedText block; + DbusSignature signature; + block.AddBlankLine(); + block.AddComments(method.doc_string); + block.AddLine(StringPrintf("%sbool %s(", + declaration_only ? "virtual " : "", + method.name.c_str())); + block.PushOffset(kLineContinuationOffset); + vector<string> argument_names; + int argument_number = 0; + for (const auto& argument : method.input_arguments) { + string argument_type; + CHECK(signature.Parse(argument.type, &argument_type)); + MakeConstReferenceIfNeeded(&argument_type); + string argument_name = GetArgName("in", argument.name, ++argument_number); + argument_names.push_back(argument_name); + block.AddLine(StringPrintf( + "%s %s,", argument_type.c_str(), argument_name.c_str())); + } + vector<string> out_param_names{"response.get()", "error"}; + for (const auto& argument : method.output_arguments) { + string argument_type; + CHECK(signature.Parse(argument.type, &argument_type)); + string argument_name = GetArgName("out", argument.name, ++argument_number); + out_param_names.push_back(argument_name); + block.AddLine(StringPrintf( + "%s* %s,", argument_type.c_str(), argument_name.c_str())); + } + block.AddLine("chromeos::ErrorPtr* error,"); + block.AddLine( + StringPrintf("int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)%s", + declaration_only ? " = 0;" : " override {")); + block.PopOffset(); + if (!declaration_only) { + block.PushOffset(kBlockOffset); + + block.AddLine( + "auto response = chromeos::dbus_utils::CallMethodAndBlockWithTimeout("); + block.PushOffset(kLineContinuationOffset); + block.AddLine("timeout_ms,"); + block.AddLine("dbus_object_proxy_,"); + block.AddLine(StringPrintf("\"%s\",", interface_name.c_str())); + block.AddLine(StringPrintf("\"%s\",", method.name.c_str())); + string last_argument = "error"; + for (const auto& argument_name : argument_names) { + block.AddLine(StringPrintf("%s,", last_argument.c_str())); + last_argument = argument_name; + } + block.AddLine(StringPrintf("%s);", last_argument.c_str())); + block.PopOffset(); + + block.AddLine("return response && " + "chromeos::dbus_utils::ExtractMethodCallResults("); + block.PushOffset(kLineContinuationOffset); + block.AddLine(chromeos::string_utils::Join(", ", out_param_names) + ");"); + block.PopOffset(); + block.PopOffset(); + block.AddLine("}"); + } + text->AddBlock(block); +} + +// static +void ProxyGenerator::AddAsyncMethodProxy(const Interface::Method& method, + const string& interface_name, + bool declaration_only, + IndentedText* text) { + IndentedText block; + DbusSignature signature; + block.AddBlankLine(); + block.AddComments(method.doc_string); + block.AddLine(StringPrintf("%svoid %sAsync(", + declaration_only ? "virtual " : "", + method.name.c_str())); + block.PushOffset(kLineContinuationOffset); + vector<string> argument_names; + int argument_number = 0; + for (const auto& argument : method.input_arguments) { + string argument_type; + CHECK(signature.Parse(argument.type, &argument_type)); + MakeConstReferenceIfNeeded(&argument_type); + string argument_name = GetArgName("in", argument.name, ++argument_number); + argument_names.push_back(argument_name); + block.AddLine(StringPrintf( + "%s %s,", argument_type.c_str(), argument_name.c_str())); + } + vector<string> out_params; + for (const auto& argument : method.output_arguments) { + string argument_type; + CHECK(signature.Parse(argument.type, &argument_type)); + MakeConstReferenceIfNeeded(&argument_type); + if (!argument.name.empty()) + base::StringAppendF(&argument_type, " /*%s*/", argument.name.c_str()); + out_params.push_back(argument_type); + } + block.AddLine(StringPrintf( + "const base::Callback<void(%s)>& success_callback,", + chromeos::string_utils::Join(", ", out_params).c_str())); + block.AddLine( + "const base::Callback<void(chromeos::Error*)>& error_callback,"); + block.AddLine( + StringPrintf("int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT)%s", + declaration_only ? " = 0;" : " override {")); + block.PopOffset(); + if (!declaration_only) { + block.PushOffset(kBlockOffset); + + block.AddLine("chromeos::dbus_utils::CallMethodWithTimeout("); + block.PushOffset(kLineContinuationOffset); + block.AddLine("timeout_ms,"); + block.AddLine("dbus_object_proxy_,"); + block.AddLine(StringPrintf("\"%s\",", interface_name.c_str())); + block.AddLine(StringPrintf("\"%s\",", method.name.c_str())); + block.AddLine("success_callback,"); + string last_argument = "error_callback"; + for (const auto& argument_name : argument_names) { + block.AddLine(StringPrintf("%s,", last_argument.c_str())); + last_argument = argument_name; + } + block.AddLine(StringPrintf("%s);", last_argument.c_str())); + block.PopOffset(); + + block.PopOffset(); + block.AddLine("}"); + } + text->AddBlock(block); +} + +// static +void ProxyGenerator::AddMethodMock(const Interface::Method& method, + const string& interface_name, + IndentedText* text) { + IndentedText block; + DbusSignature signature; + vector<string> arguments; + for (const auto& argument : method.input_arguments) { + string argument_type; + CHECK(signature.Parse(argument.type, &argument_type)); + MakeConstReferenceIfNeeded(&argument_type); + if (!argument.name.empty()) + base::StringAppendF(&argument_type, " /*in_%s*/", argument.name.c_str()); + arguments.push_back(argument_type); + } + for (const auto& argument : method.output_arguments) { + string argument_type; + CHECK(signature.Parse(argument.type, &argument_type)); + argument_type += '*'; + if (!argument.name.empty()) + base::StringAppendF(&argument_type, " /*out_%s*/", argument.name.c_str()); + arguments.push_back(argument_type); + } + arguments.push_back("chromeos::ErrorPtr* /*error*/"); + arguments.push_back("int /*timeout_ms*/"); + + block.AddLineAndPushOffsetTo( + StringPrintf("MOCK_METHOD%ju(%s,", arguments.size(), method.name.c_str()), + 1, '('); + block.AddLineAndPushOffsetTo( + StringPrintf("bool(%s,", arguments.front().c_str()), 1, '('); + for (size_t i = 1; i < arguments.size() - 1; i++) + block.AddLine(StringPrintf("%s,", arguments[i].c_str())); + block.AddLine(StringPrintf("%s));", arguments.back().c_str())); + block.PopOffset(); + block.PopOffset(); + text->AddBlock(block); +} + +// static +void ProxyGenerator::AddAsyncMethodMock(const Interface::Method& method, + const string& interface_name, + IndentedText* text) { + IndentedText block; + DbusSignature signature; + vector<string> arguments; + for (const auto& argument : method.input_arguments) { + string argument_type; + CHECK(signature.Parse(argument.type, &argument_type)); + MakeConstReferenceIfNeeded(&argument_type); + if (!argument.name.empty()) + base::StringAppendF(&argument_type, " /*in_%s*/", argument.name.c_str()); + arguments.push_back(argument_type); + } + vector<string> out_params; + for (const auto& argument : method.output_arguments) { + string argument_type; + CHECK(signature.Parse(argument.type, &argument_type)); + MakeConstReferenceIfNeeded(&argument_type); + if (!argument.name.empty()) + base::StringAppendF(&argument_type, " /*%s*/", argument.name.c_str()); + out_params.push_back(argument_type); + } + arguments.push_back(StringPrintf( + "const base::Callback<void(%s)>& /*success_callback*/", + chromeos::string_utils::Join(", ", out_params).c_str())); + arguments.push_back( + "const base::Callback<void(chromeos::Error*)>& /*error_callback*/"); + arguments.push_back("int /*timeout_ms*/"); + + block.AddLineAndPushOffsetTo( + StringPrintf("MOCK_METHOD%ju(%sAsync,", arguments.size(), + method.name.c_str()), 1, '('); + block.AddLineAndPushOffsetTo( + StringPrintf("void(%s,", arguments.front().c_str()), 1, '('); + for (size_t i = 1; i < arguments.size() - 1; i++) + block.AddLine(StringPrintf("%s,", arguments[i].c_str())); + block.AddLine(StringPrintf("%s));", arguments.back().c_str())); + block.PopOffset(); + block.PopOffset(); + text->AddBlock(block); +} + +// static +void ProxyGenerator::AddSignalHandlerRegistrationMock( + const Interface::Signal& signal, + IndentedText* text) { + IndentedText callback_arg_text; + AddSignalCallbackArg(signal, true, &callback_arg_text); + vector<string> arg_lines = callback_arg_text.GetLines(); + + IndentedText block; + block.AddLineAndPushOffsetTo( + StringPrintf("MOCK_METHOD2(Register%sSignalHandler,", + signal.name.c_str()), + 1, '('); + for (size_t i = 0; i < arg_lines.size(); ++i) { + if (i == 0) + block.AddLineAndPushOffsetTo("void(" + arg_lines[i], 1, '('); + else + block.AddLine(arg_lines[i]); + } + block.AddLine( + "dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/));"); + text->AddBlock(block); +} + +// static +void ProxyGenerator::AddSignalCallbackArg(const Interface::Signal& signal, + bool comment_arg_name, + IndentedText* block) { + DbusSignature signature; + string signal_callback = StringPrintf("%ssignal_callback%s", + comment_arg_name ? "/*" : "", + comment_arg_name ? "*/" : ""); + if (signal.arguments.empty()) { + block->AddLine(StringPrintf("const base::Closure& %s,", + signal_callback.c_str())); + } else { + string last_argument; + string prefix{"const base::Callback<void("}; + for (const auto argument : signal.arguments) { + if (!last_argument.empty()) { + if (!prefix.empty()) { + block->AddLineAndPushOffsetTo( + StringPrintf("%s%s,", prefix.c_str(), last_argument.c_str()), + 1, '('); + prefix.clear(); + } else { + block->AddLine(StringPrintf("%s,", last_argument.c_str())); + } + } + CHECK(signature.Parse(argument.type, &last_argument)); + MakeConstReferenceIfNeeded(&last_argument); + } + block->AddLine(StringPrintf("%s%s)>& %s,", + prefix.c_str(), + last_argument.c_str(), + signal_callback.c_str())); + if (prefix.empty()) { + block->PopOffset(); + } + } +} + +// static +void ProxyGenerator::ObjectManager::GenerateProxy( + const ServiceConfig& config, + const std::vector<Interface>& interfaces, + IndentedText* text) { + if (config.object_manager.name.empty()) + return; + + NameParser object_manager{config.object_manager.name}; + object_manager.AddOpenNamespaces(text, false); + text->AddBlankLine(); + + string class_name = object_manager.type_name + "Proxy"; + text->AddLine(StringPrintf("class %s : " + "public dbus::ObjectManager::Interface {", + class_name.c_str())); + text->AddLineWithOffset("public:", kScopeOffset); + text->PushOffset(kBlockOffset); + + AddConstructor(config, class_name, interfaces, text); + AddDestructor(class_name, interfaces, text); + AddGetObjectManagerProxy(text); + for (const auto& itf : interfaces) { + AddInterfaceAccessors(itf, text); + } + text->PopOffset(); + + text->AddLineWithOffset("private:", kScopeOffset); + text->PushOffset(kBlockOffset); + AddOnPropertyChanged(interfaces, text); + AddObjectAdded(config, interfaces, text); + AddObjectRemoved(interfaces, text); + AddCreateProperties(interfaces, class_name, text); + AddDataMembers(config, interfaces, class_name, text); + + text->AddLine(StringPrintf("DISALLOW_COPY_AND_ASSIGN(%s);", + class_name.c_str())); + text->PopOffset(); + text->AddLine("};"); + text->AddBlankLine(); + object_manager.AddCloseNamespaces(text, false); + text->AddBlankLine(); +} + +void ProxyGenerator::ObjectManager::AddConstructor( + const ServiceConfig& config, + const std::string& class_name, + const std::vector<Interface>& interfaces, + IndentedText* text) { + if (config.service_name.empty()) { + text->AddLineAndPushOffsetTo( + StringPrintf("%s(const scoped_refptr<dbus::Bus>& bus,", + class_name.c_str()), + 1, '('); + text->AddLine("const std::string& service_name)"); + text->PopOffset(); + } else { + text->AddLine(StringPrintf("%s(const scoped_refptr<dbus::Bus>& bus)", + class_name.c_str())); + } + text->PushOffset(kLineContinuationOffset); + text->AddLine(": bus_{bus},"); + text->PushOffset(kBlockOffset); + if (config.service_name.empty()) { + text->AddLine("service_name_{service_name},"); + } + text->AddLine("dbus_object_manager_{bus->GetObjectManager("); + text->PushOffset(kLineContinuationOffset); + if (config.service_name.empty()) { + text->AddLine("service_name,"); + } else { + text->AddLine(StringPrintf("\"%s\",", config.service_name.c_str())); + } + text->AddLine(StringPrintf("dbus::ObjectPath{\"%s\"})} {", + config.object_manager.object_path.c_str())); + text->PopOffset(); + text->PopOffset(); + text->PopOffset(); + text->PushOffset(kBlockOffset); + for (const auto& itf : interfaces) { + text->AddLine( + StringPrintf("dbus_object_manager_->RegisterInterface(\"%s\", this);", + itf.name.c_str())); + } + text->PopOffset(); + text->AddLine("}"); + text->AddBlankLine(); +} + +void ProxyGenerator::ObjectManager::AddDestructor( + const std::string& class_name, + const std::vector<Interface>& interfaces, + IndentedText* text) { + text->AddLine(StringPrintf("~%s() override {", class_name.c_str())); + text->PushOffset(kBlockOffset); + for (const auto& itf : interfaces) { + text->AddLine( + StringPrintf("dbus_object_manager_->UnregisterInterface(\"%s\");", + itf.name.c_str())); + } + text->PopOffset(); + text->AddLine("}"); + text->AddBlankLine(); +} + +void ProxyGenerator::ObjectManager::AddGetObjectManagerProxy( + IndentedText* text) { + text->AddLine("dbus::ObjectManager* GetObjectManagerProxy() const {"); + text->AddLineWithOffset("return dbus_object_manager_;", kBlockOffset); + text->AddLine("}"); + text->AddBlankLine(); +} + +void ProxyGenerator::ObjectManager::AddInterfaceAccessors( + const Interface& interface, + IndentedText* text) { + NameParser itf_name{interface.name}; + string map_name = itf_name.MakeVariableName() + "_instances_"; + + // GetProxy(). + if (interface.path.empty()) { + // We have no fixed path, so there could be multiple instances of this itf. + text->AddLine(StringPrintf("%s* Get%s(", + itf_name.MakeProxyName(true).c_str(), + itf_name.MakeProxyName(false).c_str())); + text->PushOffset(kLineContinuationOffset); + text->AddLine("const dbus::ObjectPath& object_path) {"); + text->PopOffset(); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("auto p = %s.find(object_path);", + map_name.c_str())); + text->AddLine(StringPrintf("if (p != %s.end())", map_name.c_str())); + text->PushOffset(kBlockOffset); + text->AddLine("return p->second.get();"); + text->PopOffset(); + text->AddLine("return nullptr;"); + text->PopOffset(); + text->AddLine("}"); + } else { + // We have a fixed path, so the object could be considered a "singleton". + // Skip the object_path parameter and return the first available instance. + text->AddLine(StringPrintf("%s* Get%s() {", + itf_name.MakeProxyName(true).c_str(), + itf_name.MakeProxyName(false).c_str())); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("if (%s.empty())", map_name.c_str())); + text->AddLineWithOffset("return nullptr;", kBlockOffset); + text->AddLine(StringPrintf("return %s.begin()->second.get();", + map_name.c_str())); + text->PopOffset(); + text->AddLine("}"); + } + + // GetInstances(). + text->AddLine(StringPrintf("std::vector<%s*> Get%sInstances() const {", + itf_name.MakeProxyName(true).c_str(), + itf_name.type_name.c_str())); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("std::vector<%s*> values;", + itf_name.MakeProxyName(true).c_str())); + text->AddLine(StringPrintf("values.reserve(%s.size());", map_name.c_str())); + text->AddLine(StringPrintf("for (const auto& pair : %s)", map_name.c_str())); + text->AddLineWithOffset("values.push_back(pair.second.get());", kBlockOffset); + text->AddLine("return values;"); + text->PopOffset(); + text->AddLine("}"); + + // SetAddedCallback(). + text->AddLine(StringPrintf("void Set%sAddedCallback(", + itf_name.type_name.c_str())); + text->PushOffset(kLineContinuationOffset); + text->AddLine( + StringPrintf("const base::Callback<void(%s*)>& callback) {", + itf_name.MakeProxyName(true).c_str())); + text->PopOffset(); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("on_%s_added_ = callback;", + itf_name.MakeVariableName().c_str())); + text->PopOffset(); + text->AddLine("}"); + + // SetRemovedCallback(). + text->AddLine(StringPrintf("void Set%sRemovedCallback(", + itf_name.type_name.c_str())); + text->PushOffset(kLineContinuationOffset); + text->AddLine("const base::Callback<void(const dbus::ObjectPath&)>& " + "callback) {"); + text->PopOffset(); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("on_%s_removed_ = callback;", + itf_name.MakeVariableName().c_str())); + text->PopOffset(); + text->AddLine("}"); + + text->AddBlankLine(); +} + +void ProxyGenerator::ObjectManager::AddOnPropertyChanged( + const std::vector<Interface>& interfaces, + IndentedText* text) { + text->AddLineAndPushOffsetTo("void OnPropertyChanged(" + "const dbus::ObjectPath& object_path,", + 1, '('); + text->AddLine("const std::string& interface_name,"); + text->AddLine("const std::string& property_name) {"); + text->PopOffset(); + text->PushOffset(kBlockOffset); + for (const auto& itf : interfaces) { + if (itf.properties.empty()) + continue; + + NameParser itf_name{itf.name}; + text->AddLine(StringPrintf("if (interface_name == \"%s\") {", + itf.name.c_str())); + text->PushOffset(kBlockOffset); + string map_name = itf_name.MakeVariableName() + "_instances_"; + text->AddLine(StringPrintf("auto p = %s.find(object_path);", + map_name.c_str())); + text->AddLine(StringPrintf("if (p == %s.end())", map_name.c_str())); + text->PushOffset(kBlockOffset); + text->AddLine("return;"); + text->PopOffset(); + text->AddLine("p->second->OnPropertyChanged(property_name);"); + text->AddLine("return;"); + text->PopOffset(); + text->AddLine("}"); + } + text->PopOffset(); + text->AddLine("}"); + text->AddBlankLine(); +} + +void ProxyGenerator::ObjectManager::AddObjectAdded( + const ServiceConfig& config, + const std::vector<Interface>& interfaces, + IndentedText* text) { + text->AddLine("void ObjectAdded("); + text->PushOffset(kLineContinuationOffset); + text->AddLine("const dbus::ObjectPath& object_path,"); + text->AddLine("const std::string& interface_name) override {"); + text->PopOffset(); + text->PushOffset(kBlockOffset); + for (const auto& itf : interfaces) { + NameParser itf_name{itf.name}; + string var_name = itf_name.MakeVariableName(); + text->AddLine(StringPrintf("if (interface_name == \"%s\") {", + itf.name.c_str())); + text->PushOffset(kBlockOffset); + if (!itf.properties.empty()) { + text->AddLine("auto property_set ="); + text->PushOffset(kLineContinuationOffset); + text->AddLine(StringPrintf("static_cast<%s::PropertySet*>(", + itf_name.MakeProxyName(true).c_str())); + text->PushOffset(kLineContinuationOffset); + text->AddLine("dbus_object_manager_->GetProperties(object_path, " + "interface_name));"); + text->PopOffset(); + text->PopOffset(); + } + text->AddLine(StringPrintf("std::unique_ptr<%s> %s_proxy{", + itf_name.MakeProxyName(true).c_str(), + var_name.c_str())); + text->PushOffset(kBlockOffset); + string new_instance = StringPrintf("new %s{bus_", + itf_name.MakeProxyName(true).c_str()); + if (config.service_name.empty()) { + new_instance += ", service_name_"; + } + if (itf.path.empty()) + new_instance += ", object_path"; + if (!itf.properties.empty()) + new_instance += ", property_set"; + new_instance += "}"; + text->AddLine(new_instance); + text->PopOffset(); + text->AddLine("};"); + text->AddLine(StringPrintf("auto p = %s_instances_.emplace(object_path, " + "std::move(%s_proxy));", + var_name.c_str(), var_name.c_str())); + text->AddLine(StringPrintf("if (!on_%s_added_.is_null())", + var_name.c_str())); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("on_%s_added_.Run(p.first->second.get());", + var_name.c_str())); + text->PopOffset(); + text->AddLine("return;"); + text->PopOffset(); + text->AddLine("}"); + } + text->PopOffset(); + text->AddLine("}"); + text->AddBlankLine(); +} + +void ProxyGenerator::ObjectManager::AddObjectRemoved( + const std::vector<Interface>& interfaces, + IndentedText* text) { + text->AddLine("void ObjectRemoved("); + text->PushOffset(kLineContinuationOffset); + text->AddLine("const dbus::ObjectPath& object_path,"); + text->AddLine("const std::string& interface_name) override {"); + text->PopOffset(); + text->PushOffset(kBlockOffset); + for (const auto& itf : interfaces) { + NameParser itf_name{itf.name}; + string var_name = itf_name.MakeVariableName(); + text->AddLine(StringPrintf("if (interface_name == \"%s\") {", + itf.name.c_str())); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("auto p = %s_instances_.find(object_path);", + var_name.c_str())); + text->AddLine(StringPrintf("if (p != %s_instances_.end()) {", + var_name.c_str())); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("if (!on_%s_removed_.is_null())", + var_name.c_str())); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("on_%s_removed_.Run(object_path);", + var_name.c_str())); + text->PopOffset(); + text->AddLine(StringPrintf("%s_instances_.erase(p);", + var_name.c_str())); + text->PopOffset(); + text->AddLine("}"); + text->AddLine("return;"); + text->PopOffset(); + text->AddLine("}"); + } + text->PopOffset(); + text->AddLine("}"); + text->AddBlankLine(); +} + +void ProxyGenerator::ObjectManager::AddCreateProperties( + const std::vector<Interface>& interfaces, + const std::string& class_name, + IndentedText* text) { + text->AddLine("dbus::PropertySet* CreateProperties("); + text->PushOffset(kLineContinuationOffset); + text->AddLine("dbus::ObjectProxy* object_proxy,"); + text->AddLine("const dbus::ObjectPath& object_path,"); + text->AddLine("const std::string& interface_name) override {"); + text->PopOffset(); + text->PushOffset(kBlockOffset); + for (const auto& itf : interfaces) { + NameParser itf_name{itf.name}; + text->AddLine(StringPrintf("if (interface_name == \"%s\") {", + itf.name.c_str())); + text->PushOffset(kBlockOffset); + text->AddLine(StringPrintf("return new %s::PropertySet{", + itf_name.MakeProxyName(true).c_str())); + text->PushOffset(kLineContinuationOffset); + text->AddLine("object_proxy,"); + text->AddLineAndPushOffsetTo( + StringPrintf("base::Bind(&%s::OnPropertyChanged,", + class_name.c_str()), + 1, '('); + text->AddLine("weak_ptr_factory_.GetWeakPtr(),"); + text->AddLine("object_path,"); + text->AddLine("interface_name)"); + text->PopOffset(); + text->PopOffset(); + text->AddLine("};"); + text->PopOffset(); + text->AddLine("}"); + } + text->AddLineAndPushOffsetTo("LOG(FATAL) << \"Creating properties for " + "unsupported interface \"", 1, ' '); + text->AddLine("<< interface_name;"); + text->PopOffset(); + text->AddLine("return nullptr;"); + text->PopOffset(); + text->AddLine("}"); + text->AddBlankLine(); +} + +void ProxyGenerator::ObjectManager::AddDataMembers( + const ServiceConfig& config, + const std::vector<Interface>& interfaces, + const std::string& class_name, + IndentedText* text) { + text->AddLine("scoped_refptr<dbus::Bus> bus_;"); + if (config.service_name.empty()) { + text->AddLine("std::string service_name_;"); + } + text->AddLine("dbus::ObjectManager* dbus_object_manager_;"); + for (const auto& itf : interfaces) { + NameParser itf_name{itf.name}; + string var_name = itf_name.MakeVariableName(); + text->AddLineAndPushOffsetTo("std::map<dbus::ObjectPath,", 1, '<'); + text->AddLine(StringPrintf("std::unique_ptr<%s>> %s_instances_;", + itf_name.MakeProxyName(true).c_str(), + var_name.c_str())); + text->PopOffset(); + text->AddLine(StringPrintf("base::Callback<void(%s*)> on_%s_added_;", + itf_name.MakeProxyName(true).c_str(), + var_name.c_str())); + text->AddLine(StringPrintf("base::Callback<void(const dbus::ObjectPath&)> " + "on_%s_removed_;", + var_name.c_str())); + } + text->AddLine( + StringPrintf("base::WeakPtrFactory<%s> weak_ptr_factory_{this};", + class_name.c_str())); + text->AddBlankLine(); +} + +// static +string ProxyGenerator::GetHandlerNameForSignal(const string& signal) { + return StringPrintf("On%sSignal", signal.c_str()); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/proxy_generator.h b/chromeos-dbus-bindings/proxy_generator.h new file mode 100644 index 0000000..ba9a224 --- /dev/null +++ b/chromeos-dbus-bindings/proxy_generator.h @@ -0,0 +1,188 @@ +// 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 CHROMEOS_DBUS_BINDINGS_PROXY_GENERATOR_H_ +#define CHROMEOS_DBUS_BINDINGS_PROXY_GENERATOR_H_ + +#include <string> +#include <vector> + +#include <base/macros.h> + +#include "chromeos-dbus-bindings/header_generator.h" +#include "chromeos-dbus-bindings/indented_text.h" +#include "chromeos-dbus-bindings/interface.h" + +namespace base { + +class FilePath; + +} // namespace base + +namespace chromeos_dbus_bindings { + +class IndentedText; +struct Interface; + +class ProxyGenerator : public HeaderGenerator { + public: + static bool GenerateProxies(const ServiceConfig& config, + const std::vector<Interface>& interfaces, + const base::FilePath& output_file); + + static bool GenerateMocks(const ServiceConfig& config, + const std::vector<Interface>& interfaces, + const base::FilePath& mock_file, + const base::FilePath& proxy_file); + + private: + friend class ProxyGeneratorTest; + + // Generates an abstract interface for one D-Bus interface proxy. + static void GenerateInterfaceProxyInterface(const ServiceConfig& config, + const Interface& interface, + IndentedText* text); + + // Generates one interface proxy. + static void GenerateInterfaceProxy(const ServiceConfig& config, + const Interface& interface, + IndentedText* text); + + // Generates one interface mock object. + static void GenerateInterfaceMock(const ServiceConfig& config, + const Interface& interface, + IndentedText* text); + + // Generates the constructor and destructor for the proxy. + static void AddConstructor(const ServiceConfig& config, + const Interface& interface, + const std::string& class_name, + IndentedText* text); + static void AddDestructor(const std::string& class_name, + IndentedText* text); + + // Generates ReleaseObjectProxy() method to release ownership + // of the object proxy. + static void AddReleaseObjectProxy(IndentedText* text); + + // Generates AddGetObjectPath() method. + static void AddGetObjectPath(IndentedText* text); + + // Generates GetObjectProxy() method. + static void AddGetObjectProxy(IndentedText* text); + + // Generates SetPropertyChangedCallback/GetProperties() methods. + static void AddPropertyPublicMethods(const std::string& class_name, + IndentedText* text); + + // Generates OnPropertyChanged() method. + static void AddOnPropertyChanged(IndentedText* text); + + // Generates logic permitting users to register handlers for signals. + static void AddSignalHandlerRegistration(const Interface::Signal& signal, + const std::string& interface_name, + bool declaration_only, + IndentedText* text); + + // Generates the property set class to contain interface properties. + static void AddPropertySet(const ServiceConfig& config, + const Interface& interface, + IndentedText* text); + + // Generates the property accessors. + static void AddProperties(const ServiceConfig& config, + const Interface& interface, + bool declaration_only, + IndentedText* text); + + // Generates a native C++ method which calls a D-Bus method on the proxy. + static void AddMethodProxy(const Interface::Method& interface, + const std::string& interface_name, + bool declaration_only, + IndentedText* text); + + // Generates a native C++ method which calls a D-Bus method asynchronously. + static void AddAsyncMethodProxy(const Interface::Method& interface, + const std::string& interface_name, + bool declaration_only, + IndentedText* text); + + // Generates a mock for blocking D-Bus method. + static void AddMethodMock(const Interface::Method& interface, + const std::string& interface_name, + IndentedText* text); + + // Generates a mock for asynchronous D-Bus method. + static void AddAsyncMethodMock(const Interface::Method& interface, + const std::string& interface_name, + IndentedText* text); + + // Generates a mock for the signal handler registration method. + static void AddSignalHandlerRegistrationMock( + const Interface::Signal& signal, + IndentedText* text); + + // Generate the signal callback argument of a signal handler. + static void AddSignalCallbackArg(const Interface::Signal& signal, + bool comment_arg_name, + IndentedText* block); + + // Generates the Object Manager proxy class. + struct ObjectManager { + // Generates the top-level class for Object Manager proxy. + static void GenerateProxy(const ServiceConfig& config, + const std::vector<Interface>& interfaces, + IndentedText* text); + + // Generates Object Manager constructor. + static void AddConstructor(const ServiceConfig& config, + const std::string& class_name, + const std::vector<Interface>& interfaces, + IndentedText* text); + + // Generates Object Manager destructor. + static void AddDestructor(const std::string& class_name, + const std::vector<Interface>& interfaces, + IndentedText* text); + + // Generates GetObjectManagerProxy() method. + static void AddGetObjectManagerProxy(IndentedText* text); + + // Generates code for interface-specific accessor methods + static void AddInterfaceAccessors(const Interface& interface, + IndentedText* text); + + // Generates OnPropertyChanged() method. + static void AddOnPropertyChanged(const std::vector<Interface>& interfaces, + IndentedText* text); + + // Generates ObjectAdded() method. + static void AddObjectAdded(const ServiceConfig& config, + const std::vector<Interface>& interfaces, + IndentedText* text); + + // Generates ObjectRemoved() method. + static void AddObjectRemoved(const std::vector<Interface>& interfaces, + IndentedText* text); + + // Generates CreateProperties() method. + static void AddCreateProperties(const std::vector<Interface>& interfaces, + const std::string& class_name, + IndentedText* text); + + // Generates data members of the class. + static void AddDataMembers(const ServiceConfig& config, + const std::vector<Interface>& interfaces, + const std::string& class_name, + IndentedText* text); + }; + // Generates the signal handler name for a given signal name. + static std::string GetHandlerNameForSignal(const std::string& signal); + + DISALLOW_COPY_AND_ASSIGN(ProxyGenerator); +}; + +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_PROXY_GENERATOR_H_ diff --git a/chromeos-dbus-bindings/proxy_generator_mock_unittest.cc b/chromeos-dbus-bindings/proxy_generator_mock_unittest.cc new file mode 100644 index 0000000..a07a8c9 --- /dev/null +++ b/chromeos-dbus-bindings/proxy_generator_mock_unittest.cc @@ -0,0 +1,197 @@ +// Copyright 2015 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 "chromeos-dbus-bindings/proxy_generator.h" + +#include <string> +#include <vector> + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <gtest/gtest.h> + +#include "chromeos-dbus-bindings/interface.h" +#include "chromeos-dbus-bindings/test_utils.h" + +using std::string; +using std::vector; +using testing::Test; + +namespace chromeos_dbus_bindings { + +namespace { + +const char kDBusTypeArryOfObjects[] = "ao"; +const char kDBusTypeArryOfStrings[] = "as"; +const char kDBusTypeBool[] = "b"; +const char kDBusTypeByte[] = "y"; +const char kDBusTypeInt32[] = "i"; +const char kDBusTypeInt64[] = "x"; +const char kDBusTypeString[] = "s"; + +const char kExpectedContent[] = R"literal_string( +#include <string> +#include <vector> + +#include <base/callback_forward.h> +#include <base/macros.h> +#include <chromeos/any.h> +#include <chromeos/errors/error.h> +#include <chromeos/variant_dictionary.h> +#include <gmock/gmock.h> + +#include "proxies.h" + +namespace org { +namespace chromium { + +// Mock object for TestInterfaceProxyInterface. +class TestInterfaceProxyMock final : public TestInterfaceProxyInterface { + public: + TestInterfaceProxyMock() = default; + + MOCK_METHOD5(Elements, + bool(const std::string& /*in_space_walk*/, + const std::vector<dbus::ObjectPath>& /*in_ramblin_man*/, + std::string*, + chromeos::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD5(ElementsAsync, + void(const std::string& /*in_space_walk*/, + const std::vector<dbus::ObjectPath>& /*in_ramblin_man*/, + const base::Callback<void(const std::string&)>& /*success_callback*/, + const base::Callback<void(chromeos::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(ReturnToPatagonia, + bool(int64_t*, + chromeos::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(ReturnToPatagoniaAsync, + void(const base::Callback<void(int64_t)>& /*success_callback*/, + const base::Callback<void(chromeos::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD3(NiceWeatherForDucks, + bool(bool, + chromeos::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD4(NiceWeatherForDucksAsync, + void(bool, + const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(chromeos::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(ExperimentNumberSix, + bool(chromeos::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(ExperimentNumberSixAsync, + void(const base::Callback<void()>& /*success_callback*/, + const base::Callback<void(chromeos::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + MOCK_METHOD2(RegisterCloserSignalHandler, + void(const base::Closure& /*signal_callback*/, + dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/)); + MOCK_METHOD2(RegisterTheCurseOfKaZarSignalHandler, + void(const base::Callback<void(const std::vector<std::string>&, + uint8_t)>& /*signal_callback*/, + dbus::ObjectProxy::OnConnectedCallback /*on_connected_callback*/)); + + private: + DISALLOW_COPY_AND_ASSIGN(TestInterfaceProxyMock); +}; +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Mock object for TestInterface2ProxyInterface. +class TestInterface2ProxyMock final : public TestInterface2ProxyInterface { + public: + TestInterface2ProxyMock() = default; + + MOCK_METHOD4(GetPersonInfo, + bool(std::string* /*out_name*/, + int32_t* /*out_age*/, + chromeos::ErrorPtr* /*error*/, + int /*timeout_ms*/)); + MOCK_METHOD3(GetPersonInfoAsync, + void(const base::Callback<void(const std::string& /*name*/, int32_t /*age*/)>& /*success_callback*/, + const base::Callback<void(chromeos::Error*)>& /*error_callback*/, + int /*timeout_ms*/)); + + private: + DISALLOW_COPY_AND_ASSIGN(TestInterface2ProxyMock); +}; +} // namespace chromium +} // namespace org +)literal_string"; + +} // namespace + +class ProxyGeneratorMockTest : public Test { + public: + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + protected: + base::FilePath CreateInputFile(const string& contents) { + base::FilePath path; + EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &path)); + int written = base::WriteFile(path, contents.c_str(), contents.size()); + EXPECT_EQ(contents.size(), static_cast<size_t>(written)); + return path; + } + + base::ScopedTempDir temp_dir_; +}; + +TEST_F(ProxyGeneratorMockTest, GenerateMocks) { + Interface interface; + interface.name = "org.chromium.TestInterface"; + interface.path = "/org/chromium/Test"; + interface.methods.emplace_back( + "Elements", + vector<Interface::Argument>{ + {"space_walk", kDBusTypeString}, + {"ramblin_man", kDBusTypeArryOfObjects}}, + vector<Interface::Argument>{{"", kDBusTypeString}}); + interface.methods.emplace_back( + "ReturnToPatagonia", + vector<Interface::Argument>{}, + vector<Interface::Argument>{{"", kDBusTypeInt64}}); + interface.methods.emplace_back( + "NiceWeatherForDucks", + vector<Interface::Argument>{{"", kDBusTypeBool}}, + vector<Interface::Argument>{}); + interface.methods.emplace_back("ExperimentNumberSix"); + interface.signals.emplace_back("Closer"); + interface.signals.emplace_back( + "TheCurseOfKaZar", + vector<Interface::Argument>{ + {"", kDBusTypeArryOfStrings}, + {"", kDBusTypeByte}}); + interface.methods.back().doc_string = "Comment line1\nline2"; + Interface interface2; + interface2.name = "org.chromium.TestInterface2"; + interface2.methods.emplace_back( + "GetPersonInfo", + vector<Interface::Argument>{}, + vector<Interface::Argument>{ + {"name", kDBusTypeString}, + {"age", kDBusTypeInt32}}); + vector<Interface> interfaces{interface, interface2}; + base::FilePath output_path = temp_dir_.path().Append("output.h"); + base::FilePath proxy_path = temp_dir_.path().Append("proxies.h"); + ServiceConfig config; + EXPECT_TRUE(ProxyGenerator::GenerateMocks(config, interfaces, output_path, + proxy_path)); + string contents; + EXPECT_TRUE(base::ReadFileToString(output_path, &contents)); + // The header guards contain the (temporary) filename, so we search for + // the content we need within the string. + test_utils::EXPECT_TEXT_CONTAINED(kExpectedContent, contents); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/proxy_generator_unittest.cc b/chromeos-dbus-bindings/proxy_generator_unittest.cc new file mode 100644 index 0000000..75b7e9f --- /dev/null +++ b/chromeos-dbus-bindings/proxy_generator_unittest.cc @@ -0,0 +1,1389 @@ +// 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 "chromeos-dbus-bindings/proxy_generator.h" + +#include <string> +#include <vector> + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <gtest/gtest.h> + +#include "chromeos-dbus-bindings/interface.h" +#include "chromeos-dbus-bindings/test_utils.h" + +using std::string; +using std::vector; +using testing::Test; + +namespace chromeos_dbus_bindings { + +namespace { + +const char kDBusTypeArryOfObjects[] = "ao"; +const char kDBusTypeArryOfStrings[] = "as"; +const char kDBusTypeBool[] = "b"; +const char kDBusTypeByte[] = "y"; +const char kDBusTypeInt32[] = "i"; +const char kDBusTypeInt64[] = "x"; +const char kDBusTypeString[] = "s"; + +const char kExpectedContent[] = R"literal_string( +#include <memory> +#include <string> +#include <vector> + +#include <base/bind.h> +#include <base/callback.h> +#include <base/logging.h> +#include <base/macros.h> +#include <base/memory/ref_counted.h> +#include <chromeos/any.h> +#include <chromeos/dbus/dbus_method_invoker.h> +#include <chromeos/dbus/dbus_property.h> +#include <chromeos/dbus/dbus_signal_handler.h> +#include <chromeos/errors/error.h> +#include <chromeos/variant_dictionary.h> +#include <dbus/bus.h> +#include <dbus/message.h> +#include <dbus/object_manager.h> +#include <dbus/object_path.h> +#include <dbus/object_proxy.h> + +namespace org { +namespace chromium { + +// Abstract interface proxy for org::chromium::TestInterface. +class TestInterfaceProxyInterface { + public: + virtual ~TestInterfaceProxyInterface() = default; + + virtual bool Elements( + const std::string& in_space_walk, + const std::vector<dbus::ObjectPath>& in_ramblin_man, + std::string* out_3, + chromeos::ErrorPtr* error, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0; + + virtual void ElementsAsync( + const std::string& in_space_walk, + const std::vector<dbus::ObjectPath>& in_ramblin_man, + const base::Callback<void(const std::string&)>& success_callback, + const base::Callback<void(chromeos::Error*)>& error_callback, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0; + + virtual bool ReturnToPatagonia( + int64_t* out_1, + chromeos::ErrorPtr* error, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0; + + virtual void ReturnToPatagoniaAsync( + const base::Callback<void(int64_t)>& success_callback, + const base::Callback<void(chromeos::Error*)>& error_callback, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0; + + virtual bool NiceWeatherForDucks( + bool in_1, + chromeos::ErrorPtr* error, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0; + + virtual void NiceWeatherForDucksAsync( + bool in_1, + const base::Callback<void()>& success_callback, + const base::Callback<void(chromeos::Error*)>& error_callback, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0; + + // Comment line1 + // line2 + virtual bool ExperimentNumberSix( + chromeos::ErrorPtr* error, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0; + + // Comment line1 + // line2 + virtual void ExperimentNumberSixAsync( + const base::Callback<void()>& success_callback, + const base::Callback<void(chromeos::Error*)>& error_callback, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0; + + virtual void RegisterCloserSignalHandler( + const base::Closure& signal_callback, + dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0; + + virtual void RegisterTheCurseOfKaZarSignalHandler( + const base::Callback<void(const std::vector<std::string>&, + uint8_t)>& signal_callback, + dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0; +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Interface proxy for org::chromium::TestInterface. +class TestInterfaceProxy final : public TestInterfaceProxyInterface { + public: + TestInterfaceProxy( + const scoped_refptr<dbus::Bus>& bus, + const std::string& service_name) : + bus_{bus}, + service_name_{service_name}, + dbus_object_proxy_{ + bus_->GetObjectProxy(service_name_, object_path_)} { + } + + ~TestInterfaceProxy() override { + } + + void RegisterCloserSignalHandler( + const base::Closure& signal_callback, + dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override { + chromeos::dbus_utils::ConnectToSignal( + dbus_object_proxy_, + "org.chromium.TestInterface", + "Closer", + signal_callback, + on_connected_callback); + } + + void RegisterTheCurseOfKaZarSignalHandler( + const base::Callback<void(const std::vector<std::string>&, + uint8_t)>& signal_callback, + dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override { + chromeos::dbus_utils::ConnectToSignal( + dbus_object_proxy_, + "org.chromium.TestInterface", + "TheCurseOfKaZar", + signal_callback, + on_connected_callback); + } + + void ReleaseObjectProxy(const base::Closure& callback) { + bus_->RemoveObjectProxy(service_name_, object_path_, callback); + } + + const dbus::ObjectPath& GetObjectPath() const { + return object_path_; + } + + dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; } + + bool Elements( + const std::string& in_space_walk, + const std::vector<dbus::ObjectPath>& in_ramblin_man, + std::string* out_3, + chromeos::ErrorPtr* error, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override { + auto response = chromeos::dbus_utils::CallMethodAndBlockWithTimeout( + timeout_ms, + dbus_object_proxy_, + "org.chromium.TestInterface", + "Elements", + error, + in_space_walk, + in_ramblin_man); + return response && chromeos::dbus_utils::ExtractMethodCallResults( + response.get(), error, out_3); + } + + void ElementsAsync( + const std::string& in_space_walk, + const std::vector<dbus::ObjectPath>& in_ramblin_man, + const base::Callback<void(const std::string&)>& success_callback, + const base::Callback<void(chromeos::Error*)>& error_callback, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override { + chromeos::dbus_utils::CallMethodWithTimeout( + timeout_ms, + dbus_object_proxy_, + "org.chromium.TestInterface", + "Elements", + success_callback, + error_callback, + in_space_walk, + in_ramblin_man); + } + + bool ReturnToPatagonia( + int64_t* out_1, + chromeos::ErrorPtr* error, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override { + auto response = chromeos::dbus_utils::CallMethodAndBlockWithTimeout( + timeout_ms, + dbus_object_proxy_, + "org.chromium.TestInterface", + "ReturnToPatagonia", + error); + return response && chromeos::dbus_utils::ExtractMethodCallResults( + response.get(), error, out_1); + } + + void ReturnToPatagoniaAsync( + const base::Callback<void(int64_t)>& success_callback, + const base::Callback<void(chromeos::Error*)>& error_callback, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override { + chromeos::dbus_utils::CallMethodWithTimeout( + timeout_ms, + dbus_object_proxy_, + "org.chromium.TestInterface", + "ReturnToPatagonia", + success_callback, + error_callback); + } + + bool NiceWeatherForDucks( + bool in_1, + chromeos::ErrorPtr* error, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override { + auto response = chromeos::dbus_utils::CallMethodAndBlockWithTimeout( + timeout_ms, + dbus_object_proxy_, + "org.chromium.TestInterface", + "NiceWeatherForDucks", + error, + in_1); + return response && chromeos::dbus_utils::ExtractMethodCallResults( + response.get(), error); + } + + void NiceWeatherForDucksAsync( + bool in_1, + const base::Callback<void()>& success_callback, + const base::Callback<void(chromeos::Error*)>& error_callback, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override { + chromeos::dbus_utils::CallMethodWithTimeout( + timeout_ms, + dbus_object_proxy_, + "org.chromium.TestInterface", + "NiceWeatherForDucks", + success_callback, + error_callback, + in_1); + } + + // Comment line1 + // line2 + bool ExperimentNumberSix( + chromeos::ErrorPtr* error, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override { + auto response = chromeos::dbus_utils::CallMethodAndBlockWithTimeout( + timeout_ms, + dbus_object_proxy_, + "org.chromium.TestInterface", + "ExperimentNumberSix", + error); + return response && chromeos::dbus_utils::ExtractMethodCallResults( + response.get(), error); + } + + // Comment line1 + // line2 + void ExperimentNumberSixAsync( + const base::Callback<void()>& success_callback, + const base::Callback<void(chromeos::Error*)>& error_callback, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override { + chromeos::dbus_utils::CallMethodWithTimeout( + timeout_ms, + dbus_object_proxy_, + "org.chromium.TestInterface", + "ExperimentNumberSix", + success_callback, + error_callback); + } + + private: + scoped_refptr<dbus::Bus> bus_; + std::string service_name_; + const dbus::ObjectPath object_path_{"/org/chromium/Test"}; + dbus::ObjectProxy* dbus_object_proxy_; + + DISALLOW_COPY_AND_ASSIGN(TestInterfaceProxy); +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Abstract interface proxy for org::chromium::TestInterface2. +class TestInterface2ProxyInterface { + public: + virtual ~TestInterface2ProxyInterface() = default; + + virtual bool GetPersonInfo( + std::string* out_name, + int32_t* out_age, + chromeos::ErrorPtr* error, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0; + + virtual void GetPersonInfoAsync( + const base::Callback<void(const std::string& /*name*/, int32_t /*age*/)>& success_callback, + const base::Callback<void(chromeos::Error*)>& error_callback, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) = 0; +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Interface proxy for org::chromium::TestInterface2. +class TestInterface2Proxy final : public TestInterface2ProxyInterface { + public: + TestInterface2Proxy( + const scoped_refptr<dbus::Bus>& bus, + const std::string& service_name, + const dbus::ObjectPath& object_path) : + bus_{bus}, + service_name_{service_name}, + object_path_{object_path}, + dbus_object_proxy_{ + bus_->GetObjectProxy(service_name_, object_path_)} { + } + + ~TestInterface2Proxy() override { + } + + void ReleaseObjectProxy(const base::Closure& callback) { + bus_->RemoveObjectProxy(service_name_, object_path_, callback); + } + + const dbus::ObjectPath& GetObjectPath() const { + return object_path_; + } + + dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; } + + bool GetPersonInfo( + std::string* out_name, + int32_t* out_age, + chromeos::ErrorPtr* error, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override { + auto response = chromeos::dbus_utils::CallMethodAndBlockWithTimeout( + timeout_ms, + dbus_object_proxy_, + "org.chromium.TestInterface2", + "GetPersonInfo", + error); + return response && chromeos::dbus_utils::ExtractMethodCallResults( + response.get(), error, out_name, out_age); + } + + void GetPersonInfoAsync( + const base::Callback<void(const std::string& /*name*/, int32_t /*age*/)>& success_callback, + const base::Callback<void(chromeos::Error*)>& error_callback, + int timeout_ms = dbus::ObjectProxy::TIMEOUT_USE_DEFAULT) override { + chromeos::dbus_utils::CallMethodWithTimeout( + timeout_ms, + dbus_object_proxy_, + "org.chromium.TestInterface2", + "GetPersonInfo", + success_callback, + error_callback); + } + + private: + scoped_refptr<dbus::Bus> bus_; + std::string service_name_; + dbus::ObjectPath object_path_; + dbus::ObjectProxy* dbus_object_proxy_; + + DISALLOW_COPY_AND_ASSIGN(TestInterface2Proxy); +}; + +} // namespace chromium +} // namespace org +)literal_string"; + +const char kExpectedContentWithService[] = R"literal_string( +#include <memory> +#include <string> +#include <vector> + +#include <base/bind.h> +#include <base/callback.h> +#include <base/logging.h> +#include <base/macros.h> +#include <base/memory/ref_counted.h> +#include <chromeos/any.h> +#include <chromeos/dbus/dbus_method_invoker.h> +#include <chromeos/dbus/dbus_property.h> +#include <chromeos/dbus/dbus_signal_handler.h> +#include <chromeos/errors/error.h> +#include <chromeos/variant_dictionary.h> +#include <dbus/bus.h> +#include <dbus/message.h> +#include <dbus/object_manager.h> +#include <dbus/object_path.h> +#include <dbus/object_proxy.h> + +namespace org { +namespace chromium { + +// Abstract interface proxy for org::chromium::TestInterface. +class TestInterfaceProxyInterface { + public: + virtual ~TestInterfaceProxyInterface() = default; + + virtual void RegisterCloserSignalHandler( + const base::Closure& signal_callback, + dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0; +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Interface proxy for org::chromium::TestInterface. +class TestInterfaceProxy final : public TestInterfaceProxyInterface { + public: + TestInterfaceProxy(const scoped_refptr<dbus::Bus>& bus) : + bus_{bus}, + dbus_object_proxy_{ + bus_->GetObjectProxy(service_name_, object_path_)} { + } + + ~TestInterfaceProxy() override { + } + + void RegisterCloserSignalHandler( + const base::Closure& signal_callback, + dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override { + chromeos::dbus_utils::ConnectToSignal( + dbus_object_proxy_, + "org.chromium.TestInterface", + "Closer", + signal_callback, + on_connected_callback); + } + + void ReleaseObjectProxy(const base::Closure& callback) { + bus_->RemoveObjectProxy(service_name_, object_path_, callback); + } + + const dbus::ObjectPath& GetObjectPath() const { + return object_path_; + } + + dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; } + + private: + scoped_refptr<dbus::Bus> bus_; + const std::string service_name_{"org.chromium.Test"}; + const dbus::ObjectPath object_path_{"/org/chromium/Test"}; + dbus::ObjectProxy* dbus_object_proxy_; + + DISALLOW_COPY_AND_ASSIGN(TestInterfaceProxy); +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Abstract interface proxy for org::chromium::TestInterface2. +class TestInterface2ProxyInterface { + public: + virtual ~TestInterface2ProxyInterface() = default; +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Interface proxy for org::chromium::TestInterface2. +class TestInterface2Proxy final : public TestInterface2ProxyInterface { + public: + TestInterface2Proxy( + const scoped_refptr<dbus::Bus>& bus, + const dbus::ObjectPath& object_path) : + bus_{bus}, + object_path_{object_path}, + dbus_object_proxy_{ + bus_->GetObjectProxy(service_name_, object_path_)} { + } + + ~TestInterface2Proxy() override { + } + + void ReleaseObjectProxy(const base::Closure& callback) { + bus_->RemoveObjectProxy(service_name_, object_path_, callback); + } + + const dbus::ObjectPath& GetObjectPath() const { + return object_path_; + } + + dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; } + + private: + scoped_refptr<dbus::Bus> bus_; + const std::string service_name_{"org.chromium.Test"}; + dbus::ObjectPath object_path_; + dbus::ObjectProxy* dbus_object_proxy_; + + DISALLOW_COPY_AND_ASSIGN(TestInterface2Proxy); +}; + +} // namespace chromium +} // namespace org +)literal_string"; + +const char kExpectedContentWithObjectManager[] = R"literal_string( +#include <memory> +#include <string> +#include <vector> + +#include <base/bind.h> +#include <base/callback.h> +#include <base/logging.h> +#include <base/macros.h> +#include <base/memory/ref_counted.h> +#include <chromeos/any.h> +#include <chromeos/dbus/dbus_method_invoker.h> +#include <chromeos/dbus/dbus_property.h> +#include <chromeos/dbus/dbus_signal_handler.h> +#include <chromeos/errors/error.h> +#include <chromeos/variant_dictionary.h> +#include <dbus/bus.h> +#include <dbus/message.h> +#include <dbus/object_manager.h> +#include <dbus/object_path.h> +#include <dbus/object_proxy.h> + +namespace org { +namespace chromium { +class ObjectManagerProxy; +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Abstract interface proxy for org::chromium::Itf1. +class Itf1ProxyInterface { + public: + virtual ~Itf1ProxyInterface() = default; + + virtual void RegisterCloserSignalHandler( + const base::Closure& signal_callback, + dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0; + + static const char* DataName() { return "Data"; } + virtual const std::string& data() const = 0; +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Interface proxy for org::chromium::Itf1. +class Itf1Proxy final : public Itf1ProxyInterface { + public: + class PropertySet : public dbus::PropertySet { + public: + PropertySet(dbus::ObjectProxy* object_proxy, + const PropertyChangedCallback& callback) + : dbus::PropertySet{object_proxy, + "org.chromium.Itf1", + callback} { + RegisterProperty(DataName(), &data); + } + + chromeos::dbus_utils::Property<std::string> data; + + private: + DISALLOW_COPY_AND_ASSIGN(PropertySet); + }; + + Itf1Proxy( + const scoped_refptr<dbus::Bus>& bus, + const std::string& service_name, + PropertySet* property_set) : + bus_{bus}, + service_name_{service_name}, + property_set_{property_set}, + dbus_object_proxy_{ + bus_->GetObjectProxy(service_name_, object_path_)} { + } + + ~Itf1Proxy() override { + } + + void RegisterCloserSignalHandler( + const base::Closure& signal_callback, + dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override { + chromeos::dbus_utils::ConnectToSignal( + dbus_object_proxy_, + "org.chromium.Itf1", + "Closer", + signal_callback, + on_connected_callback); + } + + void ReleaseObjectProxy(const base::Closure& callback) { + bus_->RemoveObjectProxy(service_name_, object_path_, callback); + } + + const dbus::ObjectPath& GetObjectPath() const { + return object_path_; + } + + dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; } + + void SetPropertyChangedCallback( + const base::Callback<void(Itf1Proxy*, const std::string&)>& callback) { + on_property_changed_ = callback; + } + + const PropertySet* GetProperties() const { return property_set_; } + PropertySet* GetProperties() { return property_set_; } + + const std::string& data() const override { + return property_set_->data.value(); + } + + private: + void OnPropertyChanged(const std::string& property_name) { + if (!on_property_changed_.is_null()) + on_property_changed_.Run(this, property_name); + } + + scoped_refptr<dbus::Bus> bus_; + std::string service_name_; + const dbus::ObjectPath object_path_{"/org/chromium/Test/Object"}; + PropertySet* property_set_; + base::Callback<void(Itf1Proxy*, const std::string&)> on_property_changed_; + dbus::ObjectProxy* dbus_object_proxy_; + + friend class org::chromium::ObjectManagerProxy; + DISALLOW_COPY_AND_ASSIGN(Itf1Proxy); +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Abstract interface proxy for org::chromium::Itf2. +class Itf2ProxyInterface { + public: + virtual ~Itf2ProxyInterface() = default; +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Interface proxy for org::chromium::Itf2. +class Itf2Proxy final : public Itf2ProxyInterface { + public: + class PropertySet : public dbus::PropertySet { + public: + PropertySet(dbus::ObjectProxy* object_proxy, + const PropertyChangedCallback& callback) + : dbus::PropertySet{object_proxy, + "org.chromium.Itf2", + callback} { + } + + + private: + DISALLOW_COPY_AND_ASSIGN(PropertySet); + }; + + Itf2Proxy( + const scoped_refptr<dbus::Bus>& bus, + const std::string& service_name, + const dbus::ObjectPath& object_path) : + bus_{bus}, + service_name_{service_name}, + object_path_{object_path}, + dbus_object_proxy_{ + bus_->GetObjectProxy(service_name_, object_path_)} { + } + + ~Itf2Proxy() override { + } + + void ReleaseObjectProxy(const base::Closure& callback) { + bus_->RemoveObjectProxy(service_name_, object_path_, callback); + } + + const dbus::ObjectPath& GetObjectPath() const { + return object_path_; + } + + dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; } + + private: + scoped_refptr<dbus::Bus> bus_; + std::string service_name_; + dbus::ObjectPath object_path_; + dbus::ObjectProxy* dbus_object_proxy_; + + DISALLOW_COPY_AND_ASSIGN(Itf2Proxy); +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +class ObjectManagerProxy : public dbus::ObjectManager::Interface { + public: + ObjectManagerProxy(const scoped_refptr<dbus::Bus>& bus, + const std::string& service_name) + : bus_{bus}, + service_name_{service_name}, + dbus_object_manager_{bus->GetObjectManager( + service_name, + dbus::ObjectPath{"/org/chromium/Test"})} { + dbus_object_manager_->RegisterInterface("org.chromium.Itf1", this); + dbus_object_manager_->RegisterInterface("org.chromium.Itf2", this); + } + + ~ObjectManagerProxy() override { + dbus_object_manager_->UnregisterInterface("org.chromium.Itf1"); + dbus_object_manager_->UnregisterInterface("org.chromium.Itf2"); + } + + dbus::ObjectManager* GetObjectManagerProxy() const { + return dbus_object_manager_; + } + + org::chromium::Itf1Proxy* GetItf1Proxy() { + if (itf1_instances_.empty()) + return nullptr; + return itf1_instances_.begin()->second.get(); + } + std::vector<org::chromium::Itf1Proxy*> GetItf1Instances() const { + std::vector<org::chromium::Itf1Proxy*> values; + values.reserve(itf1_instances_.size()); + for (const auto& pair : itf1_instances_) + values.push_back(pair.second.get()); + return values; + } + void SetItf1AddedCallback( + const base::Callback<void(org::chromium::Itf1Proxy*)>& callback) { + on_itf1_added_ = callback; + } + void SetItf1RemovedCallback( + const base::Callback<void(const dbus::ObjectPath&)>& callback) { + on_itf1_removed_ = callback; + } + + org::chromium::Itf2Proxy* GetItf2Proxy( + const dbus::ObjectPath& object_path) { + auto p = itf2_instances_.find(object_path); + if (p != itf2_instances_.end()) + return p->second.get(); + return nullptr; + } + std::vector<org::chromium::Itf2Proxy*> GetItf2Instances() const { + std::vector<org::chromium::Itf2Proxy*> values; + values.reserve(itf2_instances_.size()); + for (const auto& pair : itf2_instances_) + values.push_back(pair.second.get()); + return values; + } + void SetItf2AddedCallback( + const base::Callback<void(org::chromium::Itf2Proxy*)>& callback) { + on_itf2_added_ = callback; + } + void SetItf2RemovedCallback( + const base::Callback<void(const dbus::ObjectPath&)>& callback) { + on_itf2_removed_ = callback; + } + + private: + void OnPropertyChanged(const dbus::ObjectPath& object_path, + const std::string& interface_name, + const std::string& property_name) { + if (interface_name == "org.chromium.Itf1") { + auto p = itf1_instances_.find(object_path); + if (p == itf1_instances_.end()) + return; + p->second->OnPropertyChanged(property_name); + return; + } + } + + void ObjectAdded( + const dbus::ObjectPath& object_path, + const std::string& interface_name) override { + if (interface_name == "org.chromium.Itf1") { + auto property_set = + static_cast<org::chromium::Itf1Proxy::PropertySet*>( + dbus_object_manager_->GetProperties(object_path, interface_name)); + std::unique_ptr<org::chromium::Itf1Proxy> itf1_proxy{ + new org::chromium::Itf1Proxy{bus_, service_name_, property_set} + }; + auto p = itf1_instances_.emplace(object_path, std::move(itf1_proxy)); + if (!on_itf1_added_.is_null()) + on_itf1_added_.Run(p.first->second.get()); + return; + } + if (interface_name == "org.chromium.Itf2") { + std::unique_ptr<org::chromium::Itf2Proxy> itf2_proxy{ + new org::chromium::Itf2Proxy{bus_, service_name_, object_path} + }; + auto p = itf2_instances_.emplace(object_path, std::move(itf2_proxy)); + if (!on_itf2_added_.is_null()) + on_itf2_added_.Run(p.first->second.get()); + return; + } + } + + void ObjectRemoved( + const dbus::ObjectPath& object_path, + const std::string& interface_name) override { + if (interface_name == "org.chromium.Itf1") { + auto p = itf1_instances_.find(object_path); + if (p != itf1_instances_.end()) { + if (!on_itf1_removed_.is_null()) + on_itf1_removed_.Run(object_path); + itf1_instances_.erase(p); + } + return; + } + if (interface_name == "org.chromium.Itf2") { + auto p = itf2_instances_.find(object_path); + if (p != itf2_instances_.end()) { + if (!on_itf2_removed_.is_null()) + on_itf2_removed_.Run(object_path); + itf2_instances_.erase(p); + } + return; + } + } + + dbus::PropertySet* CreateProperties( + dbus::ObjectProxy* object_proxy, + const dbus::ObjectPath& object_path, + const std::string& interface_name) override { + if (interface_name == "org.chromium.Itf1") { + return new org::chromium::Itf1Proxy::PropertySet{ + object_proxy, + base::Bind(&ObjectManagerProxy::OnPropertyChanged, + weak_ptr_factory_.GetWeakPtr(), + object_path, + interface_name) + }; + } + if (interface_name == "org.chromium.Itf2") { + return new org::chromium::Itf2Proxy::PropertySet{ + object_proxy, + base::Bind(&ObjectManagerProxy::OnPropertyChanged, + weak_ptr_factory_.GetWeakPtr(), + object_path, + interface_name) + }; + } + LOG(FATAL) << "Creating properties for unsupported interface " + << interface_name; + return nullptr; + } + + scoped_refptr<dbus::Bus> bus_; + std::string service_name_; + dbus::ObjectManager* dbus_object_manager_; + std::map<dbus::ObjectPath, + std::unique_ptr<org::chromium::Itf1Proxy>> itf1_instances_; + base::Callback<void(org::chromium::Itf1Proxy*)> on_itf1_added_; + base::Callback<void(const dbus::ObjectPath&)> on_itf1_removed_; + std::map<dbus::ObjectPath, + std::unique_ptr<org::chromium::Itf2Proxy>> itf2_instances_; + base::Callback<void(org::chromium::Itf2Proxy*)> on_itf2_added_; + base::Callback<void(const dbus::ObjectPath&)> on_itf2_removed_; + base::WeakPtrFactory<ObjectManagerProxy> weak_ptr_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(ObjectManagerProxy); +}; + +} // namespace chromium +} // namespace org +)literal_string"; + +const char kExpectedContentWithObjectManagerAndServiceName[] = R"literal_string( +#include <memory> +#include <string> +#include <vector> + +#include <base/bind.h> +#include <base/callback.h> +#include <base/logging.h> +#include <base/macros.h> +#include <base/memory/ref_counted.h> +#include <chromeos/any.h> +#include <chromeos/dbus/dbus_method_invoker.h> +#include <chromeos/dbus/dbus_property.h> +#include <chromeos/dbus/dbus_signal_handler.h> +#include <chromeos/errors/error.h> +#include <chromeos/variant_dictionary.h> +#include <dbus/bus.h> +#include <dbus/message.h> +#include <dbus/object_manager.h> +#include <dbus/object_path.h> +#include <dbus/object_proxy.h> + +namespace org { +namespace chromium { +class ObjectManagerProxy; +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Abstract interface proxy for org::chromium::Itf1. +class Itf1ProxyInterface { + public: + virtual ~Itf1ProxyInterface() = default; + + virtual void RegisterCloserSignalHandler( + const base::Closure& signal_callback, + dbus::ObjectProxy::OnConnectedCallback on_connected_callback) = 0; +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Interface proxy for org::chromium::Itf1. +class Itf1Proxy final : public Itf1ProxyInterface { + public: + class PropertySet : public dbus::PropertySet { + public: + PropertySet(dbus::ObjectProxy* object_proxy, + const PropertyChangedCallback& callback) + : dbus::PropertySet{object_proxy, + "org.chromium.Itf1", + callback} { + } + + + private: + DISALLOW_COPY_AND_ASSIGN(PropertySet); + }; + + Itf1Proxy(const scoped_refptr<dbus::Bus>& bus) : + bus_{bus}, + dbus_object_proxy_{ + bus_->GetObjectProxy(service_name_, object_path_)} { + } + + ~Itf1Proxy() override { + } + + void RegisterCloserSignalHandler( + const base::Closure& signal_callback, + dbus::ObjectProxy::OnConnectedCallback on_connected_callback) override { + chromeos::dbus_utils::ConnectToSignal( + dbus_object_proxy_, + "org.chromium.Itf1", + "Closer", + signal_callback, + on_connected_callback); + } + + void ReleaseObjectProxy(const base::Closure& callback) { + bus_->RemoveObjectProxy(service_name_, object_path_, callback); + } + + const dbus::ObjectPath& GetObjectPath() const { + return object_path_; + } + + dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; } + + private: + scoped_refptr<dbus::Bus> bus_; + const std::string service_name_{"org.chromium.Test"}; + const dbus::ObjectPath object_path_{"/org/chromium/Test/Object"}; + dbus::ObjectProxy* dbus_object_proxy_; + + DISALLOW_COPY_AND_ASSIGN(Itf1Proxy); +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Abstract interface proxy for org::chromium::Itf2. +class Itf2ProxyInterface { + public: + virtual ~Itf2ProxyInterface() = default; +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +// Interface proxy for org::chromium::Itf2. +class Itf2Proxy final : public Itf2ProxyInterface { + public: + class PropertySet : public dbus::PropertySet { + public: + PropertySet(dbus::ObjectProxy* object_proxy, + const PropertyChangedCallback& callback) + : dbus::PropertySet{object_proxy, + "org.chromium.Itf2", + callback} { + } + + + private: + DISALLOW_COPY_AND_ASSIGN(PropertySet); + }; + + Itf2Proxy( + const scoped_refptr<dbus::Bus>& bus, + const dbus::ObjectPath& object_path) : + bus_{bus}, + object_path_{object_path}, + dbus_object_proxy_{ + bus_->GetObjectProxy(service_name_, object_path_)} { + } + + ~Itf2Proxy() override { + } + + void ReleaseObjectProxy(const base::Closure& callback) { + bus_->RemoveObjectProxy(service_name_, object_path_, callback); + } + + const dbus::ObjectPath& GetObjectPath() const { + return object_path_; + } + + dbus::ObjectProxy* GetObjectProxy() const { return dbus_object_proxy_; } + + private: + scoped_refptr<dbus::Bus> bus_; + const std::string service_name_{"org.chromium.Test"}; + dbus::ObjectPath object_path_; + dbus::ObjectProxy* dbus_object_proxy_; + + DISALLOW_COPY_AND_ASSIGN(Itf2Proxy); +}; + +} // namespace chromium +} // namespace org + +namespace org { +namespace chromium { + +class ObjectManagerProxy : public dbus::ObjectManager::Interface { + public: + ObjectManagerProxy(const scoped_refptr<dbus::Bus>& bus) + : bus_{bus}, + dbus_object_manager_{bus->GetObjectManager( + "org.chromium.Test", + dbus::ObjectPath{"/org/chromium/Test"})} { + dbus_object_manager_->RegisterInterface("org.chromium.Itf1", this); + dbus_object_manager_->RegisterInterface("org.chromium.Itf2", this); + } + + ~ObjectManagerProxy() override { + dbus_object_manager_->UnregisterInterface("org.chromium.Itf1"); + dbus_object_manager_->UnregisterInterface("org.chromium.Itf2"); + } + + dbus::ObjectManager* GetObjectManagerProxy() const { + return dbus_object_manager_; + } + + org::chromium::Itf1Proxy* GetItf1Proxy() { + if (itf1_instances_.empty()) + return nullptr; + return itf1_instances_.begin()->second.get(); + } + std::vector<org::chromium::Itf1Proxy*> GetItf1Instances() const { + std::vector<org::chromium::Itf1Proxy*> values; + values.reserve(itf1_instances_.size()); + for (const auto& pair : itf1_instances_) + values.push_back(pair.second.get()); + return values; + } + void SetItf1AddedCallback( + const base::Callback<void(org::chromium::Itf1Proxy*)>& callback) { + on_itf1_added_ = callback; + } + void SetItf1RemovedCallback( + const base::Callback<void(const dbus::ObjectPath&)>& callback) { + on_itf1_removed_ = callback; + } + + org::chromium::Itf2Proxy* GetItf2Proxy( + const dbus::ObjectPath& object_path) { + auto p = itf2_instances_.find(object_path); + if (p != itf2_instances_.end()) + return p->second.get(); + return nullptr; + } + std::vector<org::chromium::Itf2Proxy*> GetItf2Instances() const { + std::vector<org::chromium::Itf2Proxy*> values; + values.reserve(itf2_instances_.size()); + for (const auto& pair : itf2_instances_) + values.push_back(pair.second.get()); + return values; + } + void SetItf2AddedCallback( + const base::Callback<void(org::chromium::Itf2Proxy*)>& callback) { + on_itf2_added_ = callback; + } + void SetItf2RemovedCallback( + const base::Callback<void(const dbus::ObjectPath&)>& callback) { + on_itf2_removed_ = callback; + } + + private: + void OnPropertyChanged(const dbus::ObjectPath& object_path, + const std::string& interface_name, + const std::string& property_name) { + } + + void ObjectAdded( + const dbus::ObjectPath& object_path, + const std::string& interface_name) override { + if (interface_name == "org.chromium.Itf1") { + std::unique_ptr<org::chromium::Itf1Proxy> itf1_proxy{ + new org::chromium::Itf1Proxy{bus_} + }; + auto p = itf1_instances_.emplace(object_path, std::move(itf1_proxy)); + if (!on_itf1_added_.is_null()) + on_itf1_added_.Run(p.first->second.get()); + return; + } + if (interface_name == "org.chromium.Itf2") { + std::unique_ptr<org::chromium::Itf2Proxy> itf2_proxy{ + new org::chromium::Itf2Proxy{bus_, object_path} + }; + auto p = itf2_instances_.emplace(object_path, std::move(itf2_proxy)); + if (!on_itf2_added_.is_null()) + on_itf2_added_.Run(p.first->second.get()); + return; + } + } + + void ObjectRemoved( + const dbus::ObjectPath& object_path, + const std::string& interface_name) override { + if (interface_name == "org.chromium.Itf1") { + auto p = itf1_instances_.find(object_path); + if (p != itf1_instances_.end()) { + if (!on_itf1_removed_.is_null()) + on_itf1_removed_.Run(object_path); + itf1_instances_.erase(p); + } + return; + } + if (interface_name == "org.chromium.Itf2") { + auto p = itf2_instances_.find(object_path); + if (p != itf2_instances_.end()) { + if (!on_itf2_removed_.is_null()) + on_itf2_removed_.Run(object_path); + itf2_instances_.erase(p); + } + return; + } + } + + dbus::PropertySet* CreateProperties( + dbus::ObjectProxy* object_proxy, + const dbus::ObjectPath& object_path, + const std::string& interface_name) override { + if (interface_name == "org.chromium.Itf1") { + return new org::chromium::Itf1Proxy::PropertySet{ + object_proxy, + base::Bind(&ObjectManagerProxy::OnPropertyChanged, + weak_ptr_factory_.GetWeakPtr(), + object_path, + interface_name) + }; + } + if (interface_name == "org.chromium.Itf2") { + return new org::chromium::Itf2Proxy::PropertySet{ + object_proxy, + base::Bind(&ObjectManagerProxy::OnPropertyChanged, + weak_ptr_factory_.GetWeakPtr(), + object_path, + interface_name) + }; + } + LOG(FATAL) << "Creating properties for unsupported interface " + << interface_name; + return nullptr; + } + + scoped_refptr<dbus::Bus> bus_; + dbus::ObjectManager* dbus_object_manager_; + std::map<dbus::ObjectPath, + std::unique_ptr<org::chromium::Itf1Proxy>> itf1_instances_; + base::Callback<void(org::chromium::Itf1Proxy*)> on_itf1_added_; + base::Callback<void(const dbus::ObjectPath&)> on_itf1_removed_; + std::map<dbus::ObjectPath, + std::unique_ptr<org::chromium::Itf2Proxy>> itf2_instances_; + base::Callback<void(org::chromium::Itf2Proxy*)> on_itf2_added_; + base::Callback<void(const dbus::ObjectPath&)> on_itf2_removed_; + base::WeakPtrFactory<ObjectManagerProxy> weak_ptr_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(ObjectManagerProxy); +}; + +} // namespace chromium +} // namespace org +)literal_string"; +} // namespace + +class ProxyGeneratorTest : public Test { + public: + void SetUp() override { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + protected: + base::FilePath CreateInputFile(const string& contents) { + base::FilePath path; + EXPECT_TRUE(base::CreateTemporaryFileInDir(temp_dir_.path(), &path)); + int written = base::WriteFile(path, contents.c_str(), contents.size()); + EXPECT_EQ(contents.size(), static_cast<size_t>(written)); + return path; + } + + base::ScopedTempDir temp_dir_; +}; + +TEST_F(ProxyGeneratorTest, GenerateAdaptors) { + Interface interface; + interface.name = "org.chromium.TestInterface"; + interface.path = "/org/chromium/Test"; + interface.methods.emplace_back( + "Elements", + vector<Interface::Argument>{ + {"space_walk", kDBusTypeString}, + {"ramblin_man", kDBusTypeArryOfObjects}}, + vector<Interface::Argument>{{"", kDBusTypeString}}); + interface.methods.emplace_back( + "ReturnToPatagonia", + vector<Interface::Argument>{}, + vector<Interface::Argument>{{"", kDBusTypeInt64}}); + interface.methods.emplace_back( + "NiceWeatherForDucks", + vector<Interface::Argument>{{"", kDBusTypeBool}}, + vector<Interface::Argument>{}); + interface.methods.emplace_back("ExperimentNumberSix"); + interface.signals.emplace_back("Closer"); + interface.signals.emplace_back( + "TheCurseOfKaZar", + vector<Interface::Argument>{ + {"", kDBusTypeArryOfStrings}, + {"", kDBusTypeByte}}); + interface.methods.back().doc_string = "Comment line1\nline2"; + Interface interface2; + interface2.name = "org.chromium.TestInterface2"; + interface2.methods.emplace_back( + "GetPersonInfo", + vector<Interface::Argument>{}, + vector<Interface::Argument>{ + {"name", kDBusTypeString}, + {"age", kDBusTypeInt32}}); + vector<Interface> interfaces{interface, interface2}; + base::FilePath output_path = temp_dir_.path().Append("output.h"); + ServiceConfig config; + EXPECT_TRUE(ProxyGenerator::GenerateProxies(config, interfaces, output_path)); + string contents; + EXPECT_TRUE(base::ReadFileToString(output_path, &contents)); + // The header guards contain the (temporary) filename, so we search for + // the content we need within the string. + test_utils::EXPECT_TEXT_CONTAINED(kExpectedContent, contents); +} + +TEST_F(ProxyGeneratorTest, GenerateAdaptorsWithServiceName) { + Interface interface; + interface.name = "org.chromium.TestInterface"; + interface.path = "/org/chromium/Test"; + interface.signals.emplace_back("Closer"); + Interface interface2; + interface2.name = "org.chromium.TestInterface2"; + vector<Interface> interfaces{interface, interface2}; + base::FilePath output_path = temp_dir_.path().Append("output2.h"); + ServiceConfig config; + config.service_name = "org.chromium.Test"; + EXPECT_TRUE(ProxyGenerator::GenerateProxies(config, interfaces, output_path)); + string contents; + EXPECT_TRUE(base::ReadFileToString(output_path, &contents)); + // The header guards contain the (temporary) filename, so we search for + // the content we need within the string. + test_utils::EXPECT_TEXT_CONTAINED(kExpectedContentWithService, contents); +} + +TEST_F(ProxyGeneratorTest, GenerateAdaptorsWithObjectManager) { + Interface interface; + interface.name = "org.chromium.Itf1"; + interface.path = "/org/chromium/Test/Object"; + interface.signals.emplace_back("Closer"); + interface.properties.emplace_back("Data", "s", "read"); + Interface interface2; + interface2.name = "org.chromium.Itf2"; + vector<Interface> interfaces{interface, interface2}; + base::FilePath output_path = temp_dir_.path().Append("output3.h"); + ServiceConfig config; + config.object_manager.name = "org.chromium.ObjectManager"; + config.object_manager.object_path = "/org/chromium/Test"; + EXPECT_TRUE(ProxyGenerator::GenerateProxies(config, interfaces, output_path)); + string contents; + EXPECT_TRUE(base::ReadFileToString(output_path, &contents)); + // The header guards contain the (temporary) filename, so we search for + // the content we need within the string. + test_utils::EXPECT_TEXT_CONTAINED( + kExpectedContentWithObjectManager, contents); +} + +TEST_F(ProxyGeneratorTest, GenerateAdaptorsWithObjectManagerAndServiceName) { + Interface interface; + interface.name = "org.chromium.Itf1"; + interface.path = "/org/chromium/Test/Object"; + interface.signals.emplace_back("Closer"); + Interface interface2; + interface2.name = "org.chromium.Itf2"; + vector<Interface> interfaces{interface, interface2}; + base::FilePath output_path = temp_dir_.path().Append("output4.h"); + ServiceConfig config; + config.service_name = "org.chromium.Test"; + config.object_manager.name = "org.chromium.ObjectManager"; + config.object_manager.object_path = "/org/chromium/Test"; + EXPECT_TRUE(ProxyGenerator::GenerateProxies(config, interfaces, output_path)); + string contents; + EXPECT_TRUE(base::ReadFileToString(output_path, &contents)); + // The header guards contain the (temporary) filename, so we search for + // the content we need within the string. + test_utils::EXPECT_TEXT_CONTAINED( + kExpectedContentWithObjectManagerAndServiceName, contents); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/test_utils.cc b/chromeos-dbus-bindings/test_utils.cc new file mode 100644 index 0000000..5d019df --- /dev/null +++ b/chromeos-dbus-bindings/test_utils.cc @@ -0,0 +1,81 @@ +// Copyright 2015 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 "chromeos-dbus-bindings/test_utils.h" + +#include <string> +#include <vector> + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/logging.h> +#include <chromeos/process.h> +#include <gtest/gtest.h> + +using std::string; +using std::vector; + +namespace { + +// Return the diff between the texts |a| and |b|. +string GetUnifiedDiff(const string& a, const string& b) { + base::FilePath path_a, path_b; + if (!base::CreateTemporaryFile(&path_a) || + !base::CreateTemporaryFile(&path_b)) { + return "Error creating temporary file"; + } + WriteFile(path_a, a.data(), a.size()); + WriteFile(path_b, b.data(), b.size()); + + chromeos::ProcessImpl proc; + proc.AddArg("diff"); + proc.AddArg("-u"); + proc.AddArg(path_a.value()); + proc.AddArg(path_b.value()); + proc.SetSearchPath(true); + proc.RedirectUsingPipe(STDOUT_FILENO, false); + proc.Start(); + + int fd = proc.GetPipe(STDOUT_FILENO); + vector<char> buffer(32 * 1024); + string output; + while (true) { + int rc = read(fd, buffer.data(), buffer.size()); + if (rc < 0) { + PLOG(ERROR) << "Reading from diff."; + break; + } else if (rc == 0) { + break; + } else { + output.append(buffer.data(), rc); + } + } + proc.Wait(); + + base::DeleteFile(path_a, false); + base::DeleteFile(path_b, false); + return output; +} + +} // namespace + +namespace chromeos_dbus_bindings { +namespace test_utils { + +void ExpectTextContained(const tracked_objects::Location& from_here, + const string& expected_str, + const string& expected_expr, + const string& actual_str, + const string& actual_expr) { + if (string::npos != actual_str.find(expected_str)) + return; + + ADD_FAILURE_AT(from_here.file_name(), from_here.line_number()) + << "Expected to find " << expected_expr << " within " << actual_expr + << ".\nHere is the diff:\n" + << GetUnifiedDiff(expected_str, actual_str); +} + +} // namespace test_utils +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/test_utils.h b/chromeos-dbus-bindings/test_utils.h new file mode 100644 index 0000000..5619bc1 --- /dev/null +++ b/chromeos-dbus-bindings/test_utils.h @@ -0,0 +1,33 @@ +// Copyright 2015 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 CHROMEOS_DBUS_BINDINGS_TEST_UTILS_H_ +#define CHROMEOS_DBUS_BINDINGS_TEST_UTILS_H_ + +#include <string> + +#include <base/location.h> + +namespace chromeos_dbus_bindings { +namespace test_utils { + +// Helper macro to call ExpectTextContained(). +#define EXPECT_TEXT_CONTAINED(expected, actual) \ + ExpectTextContained(FROM_HERE, expected, #expected, actual, #actual) + +// Checks that the text |actual_str| is contained in the text |expected_str| and +// fails the current test if not. If the |actual_str| text is not contained, a +// meaningful line diff between |actual_str| and |expected_str| is displayed in +// stderr. Use this function instead of EXPECT_EQ() when the compared values are +// long texts. +void ExpectTextContained(const tracked_objects::Location& from_here, + const std::string& expected_str, + const std::string& expected_expr, + const std::string& actual_str, + const std::string& actual_expr); + +} // namespace test_utils +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_TEST_UTILS_H_ diff --git a/chromeos-dbus-bindings/testrunner.cc b/chromeos-dbus-bindings/testrunner.cc new file mode 100644 index 0000000..0777904 --- /dev/null +++ b/chromeos-dbus-bindings/testrunner.cc @@ -0,0 +1,16 @@ +// Copyright (c) 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 <base/at_exit.h> +#include <base/command_line.h> +#include <chromeos/syslog_logging.h> +#include <gtest/gtest.h> + +int main(int argc, char** argv) { + base::AtExitManager exit_manager; + base::CommandLine::Init(argc, argv); + chromeos::InitLog(chromeos::kLogToStderr); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/chromeos-dbus-bindings/xml_interface_parser.cc b/chromeos-dbus-bindings/xml_interface_parser.cc new file mode 100644 index 0000000..cb07e31 --- /dev/null +++ b/chromeos-dbus-bindings/xml_interface_parser.cc @@ -0,0 +1,351 @@ +// 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 "chromeos-dbus-bindings/xml_interface_parser.h" + +#include <utility> + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/logging.h> +#include <base/strings/string_util.h> +#include <chromeos/strings/string_utils.h> + +using std::string; +using std::vector; + +namespace chromeos_dbus_bindings { + +// static +const char XmlInterfaceParser::kArgumentTag[] = "arg"; +const char XmlInterfaceParser::kInterfaceTag[] = "interface"; +const char XmlInterfaceParser::kMethodTag[] = "method"; +const char XmlInterfaceParser::kNodeTag[] = "node"; +const char XmlInterfaceParser::kSignalTag[] = "signal"; +const char XmlInterfaceParser::kPropertyTag[] = "property"; +const char XmlInterfaceParser::kAnnotationTag[] = "annotation"; +const char XmlInterfaceParser::kDocStringTag[] = "tp:docstring"; +const char XmlInterfaceParser::kNameAttribute[] = "name"; +const char XmlInterfaceParser::kTypeAttribute[] = "type"; +const char XmlInterfaceParser::kValueAttribute[] = "value"; +const char XmlInterfaceParser::kDirectionAttribute[] = "direction"; +const char XmlInterfaceParser::kAccessAttribute[] = "access"; +const char XmlInterfaceParser::kArgumentDirectionIn[] = "in"; +const char XmlInterfaceParser::kArgumentDirectionOut[] = "out"; + +const char XmlInterfaceParser::kTrue[] = "true"; +const char XmlInterfaceParser::kFalse[] = "false"; + +const char XmlInterfaceParser::kMethodConst[] = + "org.chromium.DBus.Method.Const"; +const char XmlInterfaceParser::kMethodAsync[] = + "org.freedesktop.DBus.GLib.Async"; +const char XmlInterfaceParser::kMethodIncludeDBusMessage[] = + "org.chromium.DBus.Method.IncludeDBusMessage"; + +const char XmlInterfaceParser::kMethodKind[] = "org.chromium.DBus.Method.Kind"; +const char XmlInterfaceParser::kMethodKindSimple[] = "simple"; +const char XmlInterfaceParser::kMethodKindNormal[] = "normal"; +const char XmlInterfaceParser::kMethodKindAsync[] = "async"; +const char XmlInterfaceParser::kMethodKindRaw[] = "raw"; + +namespace { + +string GetElementPath(const vector<string>& path) { + return chromeos::string_utils::Join("/", path); +} + +} // anonymous namespace + +bool XmlInterfaceParser::ParseXmlInterfaceFile( + const std::string& contents, + const std::vector<std::string>& ignore_interfaces) { + auto parser = XML_ParserCreate(nullptr); + XML_SetUserData(parser, this); + XML_SetElementHandler(parser, + &XmlInterfaceParser::HandleElementStart, + &XmlInterfaceParser::HandleElementEnd); + XML_SetCharacterDataHandler(parser, &XmlInterfaceParser::HandleCharData); + const int kIsFinal = XML_TRUE; + + element_path_.clear(); + XML_Status res = XML_Parse(parser, + contents.c_str(), + contents.size(), + kIsFinal); + XML_ParserFree(parser); + + if (res != XML_STATUS_OK) { + LOG(ERROR) << "XML parse failure"; + return false; + } + + CHECK(element_path_.empty()); + + if (!ignore_interfaces.empty()) { + // Remove interfaces whose names are in |ignore_interfaces| list. + auto condition = [&ignore_interfaces](const Interface& itf) { + return std::find(ignore_interfaces.begin(), ignore_interfaces.end(), + itf.name) != ignore_interfaces.end(); + }; + auto p = std::remove_if(interfaces_.begin(), interfaces_.end(), condition); + interfaces_.erase(p, interfaces_.end()); + } + return true; +} + +void XmlInterfaceParser::OnOpenElement( + const string& element_name, const XmlAttributeMap& attributes) { + string prev_element; + if (!element_path_.empty()) + prev_element = element_path_.back(); + element_path_.push_back(element_name); + if (element_name == kNodeTag) { + CHECK(prev_element.empty() || prev_element == kNodeTag) + << "Unexpected tag " << element_name << " inside " << prev_element; + // 'name' attribute is optional for <node> element. + string name; + GetElementAttribute(attributes, element_path_, kNameAttribute, &name); + base::TrimWhitespaceASCII(name, base::TRIM_ALL, &name); + node_names_.push_back(name); + } else if (element_name == kInterfaceTag) { + CHECK_EQ(kNodeTag, prev_element) + << "Unexpected tag " << element_name << " inside " << prev_element; + string interface_name = GetValidatedElementName(attributes, element_path_); + Interface itf(interface_name, + std::vector<Interface::Method>{}, + std::vector<Interface::Signal>{}, + std::vector<Interface::Property>{}); + itf.path = node_names_.back(); + interfaces_.push_back(std::move(itf)); + } else if (element_name == kMethodTag) { + CHECK_EQ(kInterfaceTag, prev_element) + << "Unexpected tag " << element_name << " inside " << prev_element; + interfaces_.back().methods.push_back( + Interface::Method(GetValidatedElementName(attributes, element_path_))); + } else if (element_name == kSignalTag) { + CHECK_EQ(kInterfaceTag, prev_element) + << "Unexpected tag " << element_name << " inside " << prev_element; + interfaces_.back().signals.push_back( + Interface::Signal(GetValidatedElementName(attributes, element_path_))); + } else if (element_name == kPropertyTag) { + CHECK_EQ(kInterfaceTag, prev_element) + << "Unexpected tag " << element_name << " inside " << prev_element; + interfaces_.back().properties.push_back(ParseProperty(attributes, + element_path_)); + } else if (element_name == kArgumentTag) { + if (prev_element == kMethodTag) { + AddMethodArgument(attributes); + } else if (prev_element == kSignalTag) { + AddSignalArgument(attributes); + } else { + LOG(FATAL) << "Unexpected tag " << element_name + << " inside " << prev_element; + } + } else if (element_name == kAnnotationTag) { + string name = GetValidatedElementAttribute(attributes, element_path_, + kNameAttribute); + // Value is optional. Default to empty string if omitted. + string value; + GetElementAttribute(attributes, element_path_, kValueAttribute, &value); + if (prev_element == kInterfaceTag) { + // Parse interface annotations... + } else if (prev_element == kMethodTag) { + // Parse method annotations... + Interface::Method& method = interfaces_.back().methods.back(); + if (name == kMethodConst) { + CHECK(value == kTrue || value == kFalse); + method.is_const = (value == kTrue); + } else if (name == kMethodIncludeDBusMessage) { + CHECK(value == kTrue || value == kFalse); + method.include_dbus_message = (value == kTrue); + } else if (name == kMethodAsync) { + // Support GLib.Async annotation as well. + method.kind = Interface::Method::Kind::kAsync; + } else if (name == kMethodKind) { + if (value == kMethodKindSimple) { + method.kind = Interface::Method::Kind::kSimple; + } else if (value == kMethodKindNormal) { + method.kind = Interface::Method::Kind::kNormal; + } else if (value == kMethodKindAsync) { + method.kind = Interface::Method::Kind::kAsync; + } else if (value == kMethodKindRaw) { + method.kind = Interface::Method::Kind::kRaw; + } else { + LOG(FATAL) << "Invalid method kind: " << value; + } + } + } else if (prev_element == kSignalTag) { + // Parse signal annotations... + } else if (prev_element == kPropertyTag) { + // Parse property annotations... + } else { + LOG(FATAL) << "Unexpected tag " << element_name + << " inside " << prev_element; + } + } else if (element_name == kDocStringTag) { + CHECK(!prev_element.empty() && prev_element != kNodeTag) + << "Unexpected tag " << element_name << " inside " << prev_element; + } +} + +void XmlInterfaceParser::OnCharData(const std::string& content) { + // Handle the text data only for <tp:docstring> element. + if (element_path_.back() != kDocStringTag) + return; + + CHECK_GT(element_path_.size(), 1u); + string* doc_string_ptr = nullptr; + string target_element = element_path_[element_path_.size() - 2]; + if (target_element == kInterfaceTag) { + doc_string_ptr = &(interfaces_.back().doc_string); + } else if (target_element == kMethodTag) { + doc_string_ptr = &(interfaces_.back().methods.back().doc_string); + } else if (target_element == kSignalTag) { + doc_string_ptr = &(interfaces_.back().signals.back().doc_string); + } else if (target_element == kPropertyTag) { + doc_string_ptr = &(interfaces_.back().properties.back().doc_string); + } + + // If <tp:docstring> is attached to elements we don't care about, do nothing. + if (doc_string_ptr == nullptr) + return; + + (*doc_string_ptr) += content; +} + + +void XmlInterfaceParser::AddMethodArgument(const XmlAttributeMap& attributes) { + string argument_direction; + vector<string> path = element_path_; + path.push_back(kArgumentTag); + bool is_direction_paramter_present = GetElementAttribute( + attributes, path, kDirectionAttribute, &argument_direction); + vector<Interface::Argument>* argument_list = nullptr; + if (!is_direction_paramter_present || + argument_direction == kArgumentDirectionIn) { + argument_list = &interfaces_.back().methods.back().input_arguments; + } else if (argument_direction == kArgumentDirectionOut) { + argument_list = &interfaces_.back().methods.back().output_arguments; + } else { + LOG(FATAL) << "Unknown method argument direction " << argument_direction; + } + argument_list->push_back(ParseArgument(attributes, element_path_)); +} + +void XmlInterfaceParser::AddSignalArgument(const XmlAttributeMap& attributes) { + interfaces_.back().signals.back().arguments.push_back( + ParseArgument(attributes, element_path_)); +} + +void XmlInterfaceParser::OnCloseElement(const string& element_name) { + VLOG(1) << "Close Element " << element_name; + CHECK(!element_path_.empty()); + CHECK_EQ(element_path_.back(), element_name); + element_path_.pop_back(); + if (element_name == kNodeTag) { + CHECK(!node_names_.empty()); + node_names_.pop_back(); + } +} + +// static +bool XmlInterfaceParser::GetElementAttribute( + const XmlAttributeMap& attributes, + const vector<string>& element_path, + const string& element_key, + string* element_value) { + if (attributes.find(element_key) == attributes.end()) { + return false; + } + *element_value = attributes.find(element_key)->second; + VLOG(1) << "Got " << GetElementPath(element_path) << " element with " + << element_key << " = " << *element_value; + return true; +} + +// static +string XmlInterfaceParser::GetValidatedElementAttribute( + const XmlAttributeMap& attributes, + const vector<string>& element_path, + const string& element_key) { + string element_value; + CHECK(GetElementAttribute(attributes, + element_path, + element_key, + &element_value)) + << GetElementPath(element_path) << " does not contain a " << element_key + << " attribute"; + CHECK(!element_value.empty()) << GetElementPath(element_path) << " " + << element_key << " attribute is empty"; + return element_value; +} + +// static +string XmlInterfaceParser::GetValidatedElementName( + const XmlAttributeMap& attributes, + const vector<string>& element_path) { + return GetValidatedElementAttribute(attributes, element_path, kNameAttribute); +} + +// static +Interface::Argument XmlInterfaceParser::ParseArgument( + const XmlAttributeMap& attributes, const vector<string>& element_path) { + vector<string> path = element_path; + path.push_back(kArgumentTag); + string argument_name; + // Since the "name" field is optional, use the un-validated variant. + GetElementAttribute(attributes, path, kNameAttribute, &argument_name); + + string argument_type = GetValidatedElementAttribute( + attributes, path, kTypeAttribute); + return Interface::Argument(argument_name, argument_type); +} + +// static +Interface::Property XmlInterfaceParser::ParseProperty( + const XmlAttributeMap& attributes, + const std::vector<std::string>& element_path) { + vector<string> path = element_path; + path.push_back(kPropertyTag); + string property_name = GetValidatedElementName(attributes, path); + string property_type = GetValidatedElementAttribute(attributes, path, + kTypeAttribute); + string property_access = GetValidatedElementAttribute(attributes, path, + kAccessAttribute); + return Interface::Property(property_name, property_type, property_access); +} + +// static +void XmlInterfaceParser::HandleElementStart(void* user_data, + const XML_Char* element, + const XML_Char** attr) { + XmlAttributeMap attributes; + if (attr != nullptr) { + for (size_t n = 0; attr[n] != nullptr && attr[n+1] != nullptr; n += 2) { + auto key = attr[n]; + auto value = attr[n + 1]; + attributes.insert(std::make_pair(key, value)); + } + } + auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data); + parser->OnOpenElement(element, attributes); +} + +// static +void XmlInterfaceParser::HandleElementEnd(void* user_data, + const XML_Char* element) { + auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data); + parser->OnCloseElement(element); +} + +// static +void XmlInterfaceParser::HandleCharData(void* user_data, + const char *content, + int length) { + auto parser = reinterpret_cast<XmlInterfaceParser*>(user_data); + parser->OnCharData(string(content, length)); +} + +} // namespace chromeos_dbus_bindings diff --git a/chromeos-dbus-bindings/xml_interface_parser.h b/chromeos-dbus-bindings/xml_interface_parser.h new file mode 100644 index 0000000..15ad857 --- /dev/null +++ b/chromeos-dbus-bindings/xml_interface_parser.h @@ -0,0 +1,136 @@ +// 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 CHROMEOS_DBUS_BINDINGS_XML_INTERFACE_PARSER_H_ +#define CHROMEOS_DBUS_BINDINGS_XML_INTERFACE_PARSER_H_ + +#include <expat.h> + +#include <map> +#include <string> +#include <vector> + +#include <base/macros.h> + +#include "chromeos-dbus-bindings/interface.h" + +namespace base { + +class FilePath; + +} // namespace base + +namespace chromeos_dbus_bindings { + +class XmlInterfaceParser { + public: + using XmlAttributeMap = std::map<std::string, std::string>; + + XmlInterfaceParser() = default; + virtual ~XmlInterfaceParser() = default; + + bool ParseXmlInterfaceFile(const std::string& contents, + const std::vector<std::string>& ignore_interfaces); + const std::vector<Interface>& interfaces() const { return interfaces_; } + + private: + friend class XmlInterfaceParserTest; + + // XML tag names. + static const char kArgumentTag[]; + static const char kInterfaceTag[]; + static const char kMethodTag[]; + static const char kNodeTag[]; + static const char kSignalTag[]; + static const char kPropertyTag[]; + static const char kAnnotationTag[]; + static const char kDocStringTag[]; + + // XML attribute names. + static const char kNameAttribute[]; + static const char kTypeAttribute[]; + static const char kDirectionAttribute[]; + static const char kAccessAttribute[]; + static const char kValueAttribute[]; + + // XML argument directions. + static const char kArgumentDirectionIn[]; + static const char kArgumentDirectionOut[]; + + // XML annotations. + static const char kTrue[]; + static const char kFalse[]; + + static const char kMethodConst[]; + static const char kMethodAsync[]; + static const char kMethodIncludeDBusMessage[]; + + static const char kMethodKind[]; + static const char kMethodKindSimple[]; + static const char kMethodKindNormal[]; + static const char kMethodKindAsync[]; + static const char kMethodKindRaw[]; + + // Element callbacks on |this| called by HandleElementStart() and + // HandleElementEnd(), respectively. + void OnOpenElement(const std::string& element_name, + const XmlAttributeMap& attributes); + void OnCloseElement(const std::string& element_name); + void OnCharData(const std::string& content); + + // Methods for appending individual argument elements to the parser. + void AddMethodArgument(const XmlAttributeMap& attributes); + void AddSignalArgument(const XmlAttributeMap& attributes); + + // Finds the |element_key| element in |attributes|. Returns true and sets + // |element_value| on success. Returns false otherwise. + static bool GetElementAttribute(const XmlAttributeMap& attributes, + const std::vector<std::string>& element_path, + const std::string& element_key, + std::string* element_value); + + // Asserts that a non-empty |element_key| attribute appears in |attributes|. + // Returns the name on success, triggers a CHECK() otherwise. + static std::string GetValidatedElementAttribute( + const XmlAttributeMap& attributes, + const std::vector<std::string>& element_path, + const std::string& element_key); + + // Calls GetValidatedElementAttribute() for the "name" property. + static std::string GetValidatedElementName( + const XmlAttributeMap& attributes, + const std::vector<std::string>& element_path); + + // Method for extracting signal/method tag attributes to a struct. + static Interface::Argument ParseArgument( + const XmlAttributeMap& attributes, + const std::vector<std::string>& element_path); + + // Method for extracting property tag attributes to a struct. + static Interface::Property ParseProperty( + const XmlAttributeMap& attributes, + const std::vector<std::string>& element_path); + + // Expat element callback functions. + static void HandleElementStart(void* user_data, + const XML_Char* element, + const XML_Char** attr); + static void HandleElementEnd(void* user_data, const XML_Char* element); + static void HandleCharData(void* user_data, const char *content, int length); + + // The output of the parse. + std::vector<Interface> interfaces_; + + // A stack of <node> names used to track the object paths for interfaces. + std::vector<std::string> node_names_; + + // Tracks where in the element traversal our parse has taken us. + std::vector<std::string> element_path_; + + DISALLOW_COPY_AND_ASSIGN(XmlInterfaceParser); +}; + +} // namespace chromeos_dbus_bindings + +#endif // CHROMEOS_DBUS_BINDINGS_XML_INTERFACE_PARSER_H_ diff --git a/chromeos-dbus-bindings/xml_interface_parser_unittest.cc b/chromeos-dbus-bindings/xml_interface_parser_unittest.cc new file mode 100644 index 0000000..69173ec --- /dev/null +++ b/chromeos-dbus-bindings/xml_interface_parser_unittest.cc @@ -0,0 +1,129 @@ +// 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 "chromeos-dbus-bindings/xml_interface_parser.h" + +#include <base/files/file_path.h> +#include <base/files/file_util.h> +#include <base/files/scoped_temp_dir.h> +#include <gtest/gtest.h> + +#include "chromeos-dbus-bindings/interface.h" + +using std::string; +using std::vector; +using testing::Test; + +namespace chromeos_dbus_bindings { + +namespace { + +const char kBadInterfaceFileContents0[] = "This has no resemblance to XML"; +const char kBadInterfaceFileContents1[] = "<node>"; +const char kGoodInterfaceFileContents[] = R"literal_string( +<node name="/org/chromium/Test"> + <interface name="fi.w1.wpa_supplicant1.Interface"> + <method name="Scan"> + <arg name="args" type="a{sv}" direction="in"/> + <annotation name="org.chromium.DBus.Method.Kind" value="async"/> + <annotation name="org.chromium.DBus.Method.IncludeDBusMessage" + value="true"/> + </method> + <method name="GetBlob"> + <arg name="name" type="s"/> + <arg name="data" type="ay" direction="out"/> + <annotation name="org.chromium.DBus.Method.Const" value="true"/> + </method> + <property name="Capabilities" type="a{sv}" access="read"/> + <signal name="BSSRemoved"> + <arg name="BSS" type="o"/> + </signal> + </interface> + <interface name="DummyInterface" /> + <node name="/"/> + <node/> +</node> +)literal_string"; +const char kInterfaceName[] = "fi.w1.wpa_supplicant1.Interface"; +const char kScanMethod[] = "Scan"; +const char kArgsArgument[] = "args"; +const char kArrayStringVariantType[] = "a{sv}"; +const char kGetBlobMethod[] = "GetBlob"; +const char kNameArgument[] = "name"; +const char kDataArgument[] = "data"; +const char kStringType[] = "s"; +const char kArrayByteType[] = "ay"; +const char kBssRemovedSignal[] = "BSSRemoved"; +const char kBssArgument[] = "BSS"; +const char kObjectType[] = "o"; +const char kCapabilitiesProperty[] = "Capabilities"; +const char kReadAccess[] = "read"; +} // namespace + +class XmlInterfaceParserTest : public Test { + protected: + XmlInterfaceParser parser_; +}; + +TEST_F(XmlInterfaceParserTest, BadInputFile) { + EXPECT_FALSE(parser_.ParseXmlInterfaceFile(kBadInterfaceFileContents0, {})); + EXPECT_FALSE(parser_.ParseXmlInterfaceFile(kBadInterfaceFileContents1, {})); +} + +TEST_F(XmlInterfaceParserTest, GoodInputFile) { + EXPECT_TRUE(parser_.ParseXmlInterfaceFile(kGoodInterfaceFileContents, + {"DummyInterface"})); + const vector<Interface>& interfaces = parser_.interfaces(); + ASSERT_EQ(1u, interfaces.size()); + const Interface& interface = interfaces.back(); + + EXPECT_EQ(kInterfaceName, interface.name); + EXPECT_EQ("/org/chromium/Test", interface.path); + ASSERT_EQ(2u, interface.methods.size()); + ASSERT_EQ(1u, interface.signals.size()); + + // <method name="Scan"> + EXPECT_EQ(kScanMethod, interface.methods[0].name); + EXPECT_EQ(Interface::Method::Kind::kAsync, interface.methods[0].kind); + EXPECT_FALSE(interface.methods[0].is_const); + EXPECT_TRUE(interface.methods[0].include_dbus_message); + ASSERT_EQ(1u, interface.methods[0].input_arguments.size()); + + // <arg name="args" type="a{sv}" direction="in"/> + EXPECT_EQ(kArgsArgument, interface.methods[0].input_arguments[0].name); + EXPECT_EQ(kArrayStringVariantType, + interface.methods[0].input_arguments[0].type); + EXPECT_EQ(0u, interface.methods[0].output_arguments.size()); + + // <method name="GetBlob"> + EXPECT_EQ(kGetBlobMethod, interface.methods[1].name); + EXPECT_EQ(Interface::Method::Kind::kNormal, interface.methods[1].kind); + EXPECT_TRUE(interface.methods[1].is_const); + EXPECT_FALSE(interface.methods[1].include_dbus_message); + EXPECT_EQ(1u, interface.methods[1].input_arguments.size()); + EXPECT_EQ(1u, interface.methods[1].output_arguments.size()); + + // <arg name="name" type="s"/> (direction="in" is implicit) + EXPECT_EQ(kNameArgument, interface.methods[1].input_arguments[0].name); + EXPECT_EQ(kStringType, interface.methods[1].input_arguments[0].type); + + // <arg name="data" type="ay" direction="out"/> + EXPECT_EQ(kDataArgument, interface.methods[1].output_arguments[0].name); + EXPECT_EQ(kArrayByteType, interface.methods[1].output_arguments[0].type); + + // <signal name="BSSRemoved"> + EXPECT_EQ(kBssRemovedSignal, interface.signals[0].name); + EXPECT_EQ(1u, interface.signals[0].arguments.size()); + + // <arg name="BSS" type="o"/> + EXPECT_EQ(kBssArgument, interface.signals[0].arguments[0].name); + EXPECT_EQ(kObjectType, interface.signals[0].arguments[0].type); + + // <property name="Capabilities" type="s" access="read"/> + EXPECT_EQ(kCapabilitiesProperty, interface.properties[0].name); + EXPECT_EQ(kArrayStringVariantType, interface.properties[0].type); + EXPECT_EQ(kReadAccess, interface.properties[0].access); +} + +} // namespace chromeos_dbus_bindings |