|  | // 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 <functional> | 
|  | #include <memory> | 
|  |  | 
|  | #include <dbus/mock_bus.h> | 
|  | #include <dbus/mock_exported_object.h> | 
|  | #include <dbus/property.h> | 
|  | #include <chromeos/dbus/dbus_object.h> | 
|  | #include <gtest/gtest.h> | 
|  |  | 
|  | #include "buffet/commands/command_dictionary.h" | 
|  | #include "buffet/commands/command_instance.h" | 
|  | #include "buffet/commands/dbus_command_proxy.h" | 
|  | #include "buffet/commands/unittest_utils.h" | 
|  | #include "buffet/libbuffet/dbus_constants.h" | 
|  |  | 
|  | using ::testing::AnyNumber; | 
|  | using ::testing::Return; | 
|  | using ::testing::Invoke; | 
|  | using ::testing::_; | 
|  |  | 
|  | using buffet::unittests::CreateDictionaryValue; | 
|  | using chromeos::dbus_utils::AsyncEventSequencer; | 
|  | using chromeos::dbus_utils::ExportedObjectManager; | 
|  | using chromeos::VariantDictionary; | 
|  |  | 
|  | namespace buffet { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | const char kTestCommandCategoty[] = "test_command_category"; | 
|  | const char kTestCommandId[] = "cmd_1"; | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class DBusCommandProxyTest : public ::testing::Test { | 
|  | public: | 
|  | void SetUp() override { | 
|  | // Set up a mock DBus bus object. | 
|  | dbus::Bus::Options options; | 
|  | options.bus_type = dbus::Bus::SYSTEM; | 
|  | bus_ = new dbus::MockBus(options); | 
|  | // By default, don't worry about threading assertions. | 
|  | EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber()); | 
|  | EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber()); | 
|  |  | 
|  | // Command instance. | 
|  | auto json = CreateDictionaryValue(R"({ | 
|  | 'robot': { | 
|  | 'jump': { | 
|  | 'parameters': { | 
|  | 'height': { | 
|  | 'type': 'integer', | 
|  | 'minimum': 0, | 
|  | 'maximum': 100 | 
|  | }, | 
|  | '_jumpType': { | 
|  | 'type': 'string', | 
|  | 'enum': ['_withAirFlip', '_withSpin', '_withKick'] | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | })"); | 
|  | CHECK(dict_.LoadCommands(*json, kTestCommandCategoty, nullptr, nullptr)) | 
|  | << "Failed to parse test command dictionary"; | 
|  |  | 
|  | json = CreateDictionaryValue(R"({ | 
|  | 'name': 'robot.jump', | 
|  | 'parameters': { | 
|  | 'height': 53, | 
|  | '_jumpType': '_withKick' | 
|  | } | 
|  | })"); | 
|  | command_instance_ = CommandInstance::FromJson(json.get(), dict_, nullptr); | 
|  | command_instance_->SetID(kTestCommandId); | 
|  |  | 
|  | // Set up a mock ExportedObject to be used with the DBus command proxy. | 
|  | std::string cmd_path = dbus_constants::kCommandServicePathPrefix; | 
|  | cmd_path += kTestCommandId; | 
|  | const dbus::ObjectPath kCmdObjPath(cmd_path); | 
|  | // Use a mock exported object for the exported object manager. | 
|  | mock_exported_object_command_ = | 
|  | new dbus::MockExportedObject(bus_.get(), kCmdObjPath); | 
|  | EXPECT_CALL(*bus_, GetExportedObject(kCmdObjPath)).Times(AnyNumber()) | 
|  | .WillRepeatedly(Return(mock_exported_object_command_.get())); | 
|  | EXPECT_CALL(*mock_exported_object_command_, | 
|  | ExportMethod(_, _, _, _)).Times(AnyNumber()); | 
|  |  | 
|  | command_proxy_.reset(new DBusCommandProxy(nullptr, bus_, | 
|  | command_instance_.get())); | 
|  | command_instance_->SetProxy(command_proxy_.get()); | 
|  | command_proxy_->RegisterAsync( | 
|  | AsyncEventSequencer::GetDefaultCompletionAction()); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | EXPECT_CALL(*mock_exported_object_command_, Unregister()).Times(1); | 
|  | command_instance_->SetProxy(nullptr); | 
|  | command_proxy_.reset(); | 
|  | command_instance_.reset(); | 
|  | dict_.Clear(); | 
|  | bus_ = nullptr; | 
|  | } | 
|  |  | 
|  | chromeos::dbus_utils::DBusObject* GetProxyDBusObject() { | 
|  | return &command_proxy_->dbus_object_; | 
|  | } | 
|  |  | 
|  | std::string GetStatus() const { | 
|  | return command_proxy_->status_.value(); | 
|  | } | 
|  |  | 
|  | int32_t GetProgress() const { | 
|  | return command_proxy_->progress_.value(); | 
|  | } | 
|  |  | 
|  | VariantDictionary GetParameters() const { | 
|  | return command_proxy_->parameters_.value(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<dbus::Response> CallMethod( | 
|  | const std::string& method_name, | 
|  | const std::function<void(dbus::MessageWriter*)>& param_callback) { | 
|  | dbus::MethodCall method_call(dbus_constants::kCommandInterface, | 
|  | method_name); | 
|  | method_call.SetSerial(1234); | 
|  | dbus::MessageWriter writer(&method_call); | 
|  | if (param_callback) | 
|  | param_callback(&writer); | 
|  | return chromeos::dbus_utils::CallMethod(*GetProxyDBusObject(), | 
|  | &method_call); | 
|  | } | 
|  |  | 
|  | static bool IsResponseError(const std::unique_ptr<dbus::Response>& response) { | 
|  | return (response->GetMessageType() == dbus::Message::MESSAGE_ERROR); | 
|  | } | 
|  |  | 
|  | static void VerifyResponse( | 
|  | const std::unique_ptr<dbus::Response>& response, | 
|  | const std::function<void(dbus::MessageReader*)>& result_callback) { | 
|  | EXPECT_FALSE(IsResponseError(response)); | 
|  | dbus::MessageReader reader(response.get()); | 
|  | if (result_callback) | 
|  | result_callback(&reader); | 
|  | EXPECT_FALSE(reader.HasMoreData()); | 
|  | } | 
|  |  | 
|  | template<typename T> | 
|  | T GetPropertyValue(const std::string& property_name) { | 
|  | dbus::MethodCall method_call(dbus::kPropertiesInterface, | 
|  | dbus::kPropertiesGet); | 
|  | method_call.SetSerial(1234); | 
|  | dbus::MessageWriter writer(&method_call); | 
|  | writer.AppendString(dbus_constants::kCommandInterface); | 
|  | writer.AppendString(property_name); | 
|  | auto response = chromeos::dbus_utils::CallMethod(*GetProxyDBusObject(), | 
|  | &method_call); | 
|  | T value{}; | 
|  | VerifyResponse(response, [&value](dbus::MessageReader* reader) { | 
|  | EXPECT_TRUE(chromeos::dbus_utils::PopValueFromReader(reader, &value)); | 
|  | }); | 
|  | return value; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<DBusCommandProxy> command_proxy_; | 
|  | std::unique_ptr<CommandInstance> command_instance_; | 
|  | CommandDictionary dict_; | 
|  |  | 
|  | scoped_refptr<dbus::MockExportedObject> mock_exported_object_command_; | 
|  | scoped_refptr<dbus::MockBus> bus_; | 
|  | }; | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, Init) { | 
|  | VariantDictionary params = { | 
|  | {"height", int32_t{53}}, | 
|  | {"_jumpType", std::string{"_withKick"}}, | 
|  | }; | 
|  | EXPECT_EQ(CommandInstance::kStatusQueued, GetStatus()); | 
|  | EXPECT_EQ(0, GetProgress()); | 
|  | EXPECT_EQ(params, GetParameters()); | 
|  | EXPECT_EQ("robot.jump", | 
|  | GetPropertyValue<std::string>(dbus_constants::kCommandName)); | 
|  | EXPECT_EQ(kTestCommandCategoty, | 
|  | GetPropertyValue<std::string>(dbus_constants::kCommandCategory)); | 
|  | EXPECT_EQ(kTestCommandId, | 
|  | GetPropertyValue<std::string>(dbus_constants::kCommandId)); | 
|  | EXPECT_EQ(CommandInstance::kStatusQueued, | 
|  | GetPropertyValue<std::string>(dbus_constants::kCommandStatus)); | 
|  | EXPECT_EQ(0, GetPropertyValue<int32_t>(dbus_constants::kCommandProgress)); | 
|  | EXPECT_EQ(params, | 
|  | GetPropertyValue<VariantDictionary>( | 
|  | dbus_constants::kCommandParameters)); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, SetProgress) { | 
|  | EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(2); | 
|  | auto response = CallMethod(dbus_constants::kCommandSetProgress, | 
|  | [](dbus::MessageWriter* writer) { | 
|  | writer->AppendInt32(10); | 
|  | }); | 
|  | VerifyResponse(response, {}); | 
|  | EXPECT_EQ(CommandInstance::kStatusInProgress, GetStatus()); | 
|  | EXPECT_EQ(10, GetProgress()); | 
|  | EXPECT_EQ(CommandInstance::kStatusInProgress, | 
|  | GetPropertyValue<std::string>(dbus_constants::kCommandStatus)); | 
|  | EXPECT_EQ(10, GetPropertyValue<int32_t>(dbus_constants::kCommandProgress)); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, SetProgress_OutOfRange) { | 
|  | auto response = CallMethod(dbus_constants::kCommandSetProgress, | 
|  | [](dbus::MessageWriter* writer) { | 
|  | writer->AppendInt32(110); | 
|  | }); | 
|  | EXPECT_TRUE(IsResponseError(response)); | 
|  | EXPECT_EQ(CommandInstance::kStatusQueued, GetStatus()); | 
|  | EXPECT_EQ(0, GetProgress()); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, Abort) { | 
|  | EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1); | 
|  | auto response = CallMethod(dbus_constants::kCommandAbort, {}); | 
|  | VerifyResponse(response, {}); | 
|  | EXPECT_EQ(CommandInstance::kStatusAborted, GetStatus()); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, Cancel) { | 
|  | EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1); | 
|  | auto response = CallMethod(dbus_constants::kCommandCancel, {}); | 
|  | VerifyResponse(response, {}); | 
|  | EXPECT_EQ(CommandInstance::kStatusCanceled, GetStatus()); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, Done) { | 
|  | // 3 property updates: | 
|  | // status: queued -> inProgress | 
|  | // progress: 0 -> 100 | 
|  | // status: inProgress -> done | 
|  | EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(3); | 
|  | auto response = CallMethod(dbus_constants::kCommandDone, {}); | 
|  | VerifyResponse(response, {}); | 
|  | EXPECT_EQ(CommandInstance::kStatusDone, GetStatus()); | 
|  | EXPECT_EQ(100, GetProgress()); | 
|  | } | 
|  |  | 
|  | }  // namespace buffet |