|  | // Copyright 2014 The Chromium OS Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "buffet/commands/dbus_command_proxy.h" | 
|  |  | 
|  | #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 <chromeos/dbus/dbus_object_test_helpers.h> | 
|  | #include <gtest/gtest.h> | 
|  |  | 
|  | #include "buffet/commands/command_dictionary.h" | 
|  | #include "buffet/commands/command_instance.h" | 
|  | #include "buffet/commands/unittest_utils.h" | 
|  | #include "buffet/dbus_constants.h" | 
|  |  | 
|  | namespace buffet { | 
|  |  | 
|  | using ::testing::AnyNumber; | 
|  | using ::testing::Invoke; | 
|  | using ::testing::Return; | 
|  | using ::testing::_; | 
|  |  | 
|  | using chromeos::VariantDictionary; | 
|  | using chromeos::dbus_utils::AsyncEventSequencer; | 
|  | using unittests::CreateDictionaryValue; | 
|  |  | 
|  | 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. | 
|  | // TODO(antonm): Test results. | 
|  | auto json = CreateDictionaryValue(R"({ | 
|  | 'robot': { | 
|  | 'jump': { | 
|  | 'parameters': { | 
|  | 'height': { | 
|  | 'type': 'integer', | 
|  | 'minimum': 0, | 
|  | 'maximum': 100 | 
|  | }, | 
|  | '_jumpType': { | 
|  | 'type': 'string', | 
|  | 'enum': ['_withAirFlip', '_withSpin', '_withKick'] | 
|  | } | 
|  | }, | 
|  | 'results': { | 
|  | 'foo': { | 
|  | 'type': 'integer' | 
|  | }, | 
|  | 'bar': { | 
|  | 'type': 'string' | 
|  | } | 
|  | }, | 
|  | 'progress': { | 
|  | 'progress': { | 
|  | 'type': 'integer', | 
|  | 'minimum': 0, | 
|  | 'maximum': 100 | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | })"); | 
|  | 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(), "local", dict_, nullptr, 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()); | 
|  |  | 
|  | std::unique_ptr<CommandProxyInterface> command_proxy( | 
|  | new DBusCommandProxy(nullptr, bus_, command_instance_.get(), cmd_path)); | 
|  | command_instance_->AddProxy(std::move(command_proxy)); | 
|  | GetCommandProxy()->RegisterAsync( | 
|  | AsyncEventSequencer::GetDefaultCompletionAction()); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | EXPECT_CALL(*mock_exported_object_command_, Unregister()).Times(1); | 
|  | command_instance_.reset(); | 
|  | dict_.Clear(); | 
|  | bus_ = nullptr; | 
|  | } | 
|  |  | 
|  | DBusCommandProxy* GetCommandProxy() const { | 
|  | CHECK_EQ(command_instance_->proxies_.size(), 1U); | 
|  | return static_cast<DBusCommandProxy*>(command_instance_->proxies_[0].get()); | 
|  | } | 
|  |  | 
|  | org::chromium::Buffet::CommandAdaptor* GetCommandAdaptor() const { | 
|  | return &GetCommandProxy()->dbus_adaptor_; | 
|  | } | 
|  |  | 
|  | org::chromium::Buffet::CommandInterface* GetCommandInterface() const { | 
|  | // DBusCommandProxy also implements CommandInterface. | 
|  | return GetCommandProxy(); | 
|  | } | 
|  |  | 
|  | 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, GetCommandAdaptor()->GetStatus()); | 
|  | EXPECT_EQ(params, GetCommandAdaptor()->GetParameters()); | 
|  | EXPECT_EQ(VariantDictionary{}, GetCommandAdaptor()->GetProgress()); | 
|  | EXPECT_EQ(VariantDictionary{}, GetCommandAdaptor()->GetResults()); | 
|  | EXPECT_EQ("robot.jump", GetCommandAdaptor()->GetName()); | 
|  | EXPECT_EQ(kTestCommandCategoty, GetCommandAdaptor()->GetCategory()); | 
|  | EXPECT_EQ(kTestCommandId, GetCommandAdaptor()->GetId()); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, SetProgress) { | 
|  | EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(2); | 
|  | EXPECT_TRUE( | 
|  | GetCommandInterface()->SetProgress(nullptr, {{"progress", int32_t{10}}})); | 
|  | EXPECT_EQ(CommandInstance::kStatusInProgress, | 
|  | GetCommandAdaptor()->GetStatus()); | 
|  |  | 
|  | VariantDictionary progress{{"progress", int32_t{10}}}; | 
|  | EXPECT_EQ(progress, GetCommandAdaptor()->GetProgress()); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, SetProgress_OutOfRange) { | 
|  | EXPECT_FALSE(GetCommandInterface()->SetProgress( | 
|  | nullptr, {{"progress", int32_t{110}}})); | 
|  | EXPECT_EQ(CommandInstance::kStatusQueued, GetCommandAdaptor()->GetStatus()); | 
|  | EXPECT_EQ(VariantDictionary{}, GetCommandAdaptor()->GetProgress()); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, SetResults) { | 
|  | EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1); | 
|  | const VariantDictionary results = { | 
|  | {"foo", int32_t{42}}, | 
|  | {"bar", std::string{"foobar"}}, | 
|  | }; | 
|  | EXPECT_TRUE(GetCommandInterface()->SetResults(nullptr, results)); | 
|  | EXPECT_EQ(results, GetCommandAdaptor()->GetResults()); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, SetResults_UnknownProperty) { | 
|  | EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(0); | 
|  | const VariantDictionary results = { | 
|  | {"quux", int32_t{42}}, | 
|  | }; | 
|  | EXPECT_FALSE(GetCommandInterface()->SetResults(nullptr, results)); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, Abort) { | 
|  | EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1); | 
|  | GetCommandInterface()->Abort(); | 
|  | EXPECT_EQ(CommandInstance::kStatusAborted, | 
|  | GetCommandAdaptor()->GetStatus()); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, Cancel) { | 
|  | EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1); | 
|  | GetCommandInterface()->Cancel(); | 
|  | EXPECT_EQ(CommandInstance::kStatusCancelled, | 
|  | GetCommandAdaptor()->GetStatus()); | 
|  | } | 
|  |  | 
|  | TEST_F(DBusCommandProxyTest, Done) { | 
|  | // 1 property update: | 
|  | // status: queued -> done | 
|  | EXPECT_CALL(*mock_exported_object_command_, SendSignal(_)).Times(1); | 
|  | GetCommandInterface()->Done(); | 
|  | EXPECT_EQ(CommandInstance::kStatusDone, GetCommandAdaptor()->GetStatus()); | 
|  | } | 
|  |  | 
|  | }  // namespace buffet |