buffet: Add DBusCommandDispacher and hook it up to CommandQueue
Added DBusCommandDispacher class that maintains DBusCommandProxy object
for each instance of CommandInstance class added to the CommandQueue.
As soon as a command instance is removed from the queue, D-Bus command
dispatcher removes the corresponding command proxy object from D-Bus.
BUG=chromium:374864
TEST=FEATURES=test emerge-link buffet
Change-Id: Ib7ce7370bd3ee471e22f02b8546675021ff063d7
Reviewed-on: https://chromium-review.googlesource.com/211642
Tested-by: Alex Vakulenko <avakulenko@chromium.org>
Reviewed-by: Christopher Wiley <wiley@chromium.org>
Commit-Queue: Alex Vakulenko <avakulenko@chromium.org>
diff --git a/buffet/commands/dbus_command_dispatcher_unittest.cc b/buffet/commands/dbus_command_dispatcher_unittest.cc
new file mode 100644
index 0000000..dd52871
--- /dev/null
+++ b/buffet/commands/dbus_command_dispatcher_unittest.cc
@@ -0,0 +1,196 @@
+// 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 <chromeos/dbus/exported_object_manager.h>
+#include <dbus/mock_bus.h>
+#include <dbus/mock_exported_object.h>
+#include <dbus/object_manager.h>
+#include <dbus/object_path.h>
+#include <gtest/gtest.h>
+
+#include "buffet/commands/command_dictionary.h"
+#include "buffet/commands/command_queue.h"
+#include "buffet/commands/dbus_command_dispatcher.h"
+#include "buffet/commands/unittest_utils.h"
+#include "buffet/dbus_constants.h"
+
+using buffet::unittests::CreateDictionaryValue;
+using chromeos::dbus_utils::AsyncEventSequencer;
+using testing::AnyNumber;
+using testing::InSequence;
+using testing::Invoke;
+using testing::Return;
+using testing::_;
+
+namespace buffet {
+
+namespace {
+
+const char kCommandCategory[] = "test_category";
+
+} // anonymous namespace
+
+class DBusCommandDispacherTest : public testing::Test {
+ public:
+ void SetUp() override {
+ const dbus::ObjectPath kExportedObjectManagerPath("/test/om_path");
+ std::string cmd_path = dbus_constants::kCommandServicePathPrefix;
+ cmd_path += "1";
+ const dbus::ObjectPath kCmdObjPath(cmd_path);
+
+ dbus::Bus::Options options;
+ options.bus_type = dbus::Bus::SYSTEM;
+ bus_ = new dbus::MockBus(options);
+ // By default, don't worry about threading assertions.
+ EXPECT_CALL(*bus_, AssertOnOriginThread()).Times(AnyNumber());
+ EXPECT_CALL(*bus_, AssertOnDBusThread()).Times(AnyNumber());
+ // Use a mock exported object manager.
+ mock_exported_object_manager_ = new dbus::MockExportedObject(
+ bus_.get(), kExportedObjectManagerPath);
+ EXPECT_CALL(*bus_, GetExportedObject(kExportedObjectManagerPath))
+ .WillRepeatedly(Return(mock_exported_object_manager_.get()));
+ EXPECT_CALL(*mock_exported_object_manager_,
+ ExportMethod(_, _, _, _)).Times(AnyNumber());
+ om_.reset(new chromeos::dbus_utils::ExportedObjectManager(
+ bus_.get(), kExportedObjectManagerPath));
+ om_->RegisterAsync(AsyncEventSequencer::GetDefaultCompletionAction());
+ command_dispatcher_.reset(
+ new DBusCommandDispacher(om_->GetBus(), om_.get()));
+ command_queue_.SetCommandDispachInterface(command_dispatcher_.get());
+ // Use a mock exported object for command proxy.
+ mock_exported_command_proxy_ = new dbus::MockExportedObject(
+ bus_.get(), kCmdObjPath);
+ EXPECT_CALL(*bus_, GetExportedObject(kCmdObjPath))
+ .WillRepeatedly(Return(mock_exported_command_proxy_.get()));
+ EXPECT_CALL(*mock_exported_command_proxy_, ExportMethod(_, _, _, _))
+ .WillRepeatedly(Invoke(MockExportMethod));
+
+ auto json = CreateDictionaryValue(R"({
+ 'base': {
+ 'reboot': {
+ 'parameters': {'delay': 'integer'}
+ },
+ 'shutdown': {
+ 'parameters': {}
+ }
+ }
+ })");
+ CHECK(dictionary_.LoadCommands(*json, kCommandCategory, nullptr, nullptr))
+ << "Failed to load command dictionary";
+ }
+
+ void TearDown() override {
+ EXPECT_CALL(*mock_exported_object_manager_, Unregister()).Times(1);
+ om_.reset();
+ bus_ = nullptr;
+ }
+
+ static void MockExportMethod(
+ const std::string& interface_name,
+ const std::string& method_name,
+ dbus::ExportedObject::MethodCallCallback method_call_callback,
+ dbus::ExportedObject::OnExportedCallback on_exported_callback) {
+ on_exported_callback.Run(interface_name, method_name, true);
+ }
+
+
+ std::string AddNewCommand(const std::string& json) {
+ auto command_instance = CommandInstance::FromJson(
+ CreateDictionaryValue(json.c_str()).get(), dictionary_, nullptr);
+ // Two interfaces are added - Command and Properties.
+ EXPECT_CALL(*mock_exported_object_manager_, SendSignal(_)).Times(2);
+ return command_instance ?
+ command_queue_.Add(std::move(command_instance)) : std::string();
+ }
+
+ void FinishCommand(DBusCommandProxy* proxy) {
+ proxy->HandleDone(nullptr);
+ }
+
+ void SetProgress(DBusCommandProxy* proxy, int progress) {
+ proxy->HandleSetProgress(nullptr, progress);
+ }
+
+
+ scoped_refptr<dbus::MockBus> bus_;
+ scoped_refptr<dbus::MockExportedObject> mock_exported_object_manager_;
+ scoped_refptr<dbus::MockExportedObject> mock_exported_command_proxy_;
+ std::unique_ptr<chromeos::dbus_utils::ExportedObjectManager> om_;
+ CommandDictionary dictionary_;
+ CommandQueue command_queue_;
+ std::unique_ptr<DBusCommandDispacher> command_dispatcher_;
+};
+
+TEST_F(DBusCommandDispacherTest, Test_Command_Base_Shutdown) {
+ std::string id = AddNewCommand("{'name':'base.shutdown'}");
+ EXPECT_EQ("1", id);
+ CommandInstance* command_instance = command_queue_.Find(id);
+ ASSERT_NE(nullptr, command_instance);
+ DBusCommandProxy* command_proxy =
+ command_dispatcher_->FindProxy(command_instance);
+ ASSERT_NE(nullptr, command_proxy);
+ EXPECT_EQ(CommandInstance::kStatusQueued, command_instance->GetStatus());
+
+ // Two properties are set, Progress = 50%, Status = "inProgress"
+ EXPECT_CALL(*mock_exported_command_proxy_, SendSignal(_)).Times(2);
+ SetProgress(command_proxy, 50);
+ EXPECT_EQ(CommandInstance::kStatusInProgress, command_instance->GetStatus());
+ EXPECT_EQ(50, command_instance->GetProgress());
+
+ // Command must be removed from the queue and proxy destroyed after calling
+ // FinishCommand().
+ // Two properties are set, Progress = 100%, Status = "done"
+ EXPECT_CALL(*mock_exported_command_proxy_, SendSignal(_)).Times(2);
+ // D-Bus command proxy is going away.
+ EXPECT_CALL(*mock_exported_command_proxy_, Unregister()).Times(1);
+ // Two interfaces are being removed on the D-Bus command object.
+ EXPECT_CALL(*mock_exported_object_manager_, SendSignal(_)).Times(2);
+ FinishCommand(command_proxy);
+
+ EXPECT_EQ(nullptr,
+ command_dispatcher_->FindProxy(command_instance));
+ EXPECT_EQ(nullptr, command_queue_.Find(id));
+}
+
+TEST_F(DBusCommandDispacherTest, Test_Command_Base_Reboot) {
+ std::string id = AddNewCommand(R"({
+ 'name': 'base.reboot',
+ 'parameters': {
+ 'delay': 20
+ }
+ })");
+ EXPECT_EQ("1", id);
+ CommandInstance* command_instance = command_queue_.Find(id);
+ ASSERT_NE(nullptr, command_instance);
+ DBusCommandProxy* command_proxy =
+ command_dispatcher_->FindProxy(command_instance);
+ ASSERT_NE(nullptr, command_proxy);
+ EXPECT_EQ(CommandInstance::kStatusQueued, command_instance->GetStatus());
+
+ // Two properties are set, Progress = 50%, Status = "inProgress"
+ EXPECT_CALL(*mock_exported_command_proxy_, SendSignal(_)).Times(2);
+ SetProgress(command_proxy, 50);
+ EXPECT_EQ(CommandInstance::kStatusInProgress, command_instance->GetStatus());
+ EXPECT_EQ(50, command_instance->GetProgress());
+
+ // Command must be removed from the queue and proxy destroyed after calling
+ // FinishCommand().
+ // Two properties are set, Progress = 100%, Status = "done"
+ EXPECT_CALL(*mock_exported_command_proxy_, SendSignal(_)).Times(2);
+ // D-Bus command proxy is going away.
+ EXPECT_CALL(*mock_exported_command_proxy_, Unregister()).Times(1);
+ // Two interfaces are being removed on the D-Bus command object.
+ EXPECT_CALL(*mock_exported_object_manager_, SendSignal(_)).Times(2);
+ FinishCommand(command_proxy);
+
+ EXPECT_EQ(nullptr,
+ command_dispatcher_->FindProxy(command_instance));
+ EXPECT_EQ(nullptr, command_queue_.Find(id));
+}
+
+
+} // namespace buffet