buffet: Add an abstract command dispatch interface Added simple framework for implementing command dispatch from buffet to command-handling daemons which concrete delivery layers, such as D-Bus, can use to actually implement the command delivery. Also changed some CommandQueue unit tests to remove the implementation detail knowledge of how the command IDs are generated. BUG=chromium:374864 TEST=USE=buffet P2_TEST_FILTER="buffet::*" FEATURES=test emerge-link platform2 Change-Id: Ic7a719a09e924fefedc72cc0bb675adf1acd3713 Reviewed-on: https://chromium-review.googlesource.com/211485 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/command_dispatch_interface.h b/buffet/commands/command_dispatch_interface.h new file mode 100644 index 0000000..b09c6ec --- /dev/null +++ b/buffet/commands/command_dispatch_interface.h
@@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium OS Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BUFFET_COMMANDS_COMMAND_DISPATCH_INTERFACE_H_ +#define BUFFET_COMMANDS_COMMAND_DISPATCH_INTERFACE_H_ + +#include <string> + +namespace buffet { + +class CommandInstance; + +// This is an abstract base interface that a command dispatcher will implement. +// It allows to abstract out the actual transport layer, such as D-Bus, from +// the rest of command registration and delivery subsystems. +class CommandDispachInterface { + public: + virtual ~CommandDispachInterface() = default; + // Callback invoked by CommandQueue when a new command is added to the queue. + virtual void OnCommandAdded(const std::string& command_id, + const CommandInstance* command_instance) = 0; + // Callback invoked by CommandQueue when a new command is removed from + // the queue. + virtual void OnCommandRemoved(const std::string& command_id, + const CommandInstance* command_instance) = 0; +}; + +} // namespace buffet + +#endif // BUFFET_COMMANDS_COMMAND_DISPATCH_INTERFACE_H_
diff --git a/buffet/commands/command_queue.cc b/buffet/commands/command_queue.cc index ca1e7f9..671db6d 100644 --- a/buffet/commands/command_queue.cc +++ b/buffet/commands/command_queue.cc
@@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "buffet/commands/command_dispatch_interface.h" #include "buffet/commands/command_queue.h" namespace buffet { @@ -11,6 +12,9 @@ auto pair = map_.insert(std::make_pair(id, std::move(instance))); LOG_IF(FATAL, !pair.second) << "Command with ID '" << id << "' is already in the queue"; + if (dispatch_interface_) + dispatch_interface_->OnCommandAdded(id, pair.first->second.get()); + return id; } @@ -21,6 +25,8 @@ if (p != map_.end()) { instance = std::move(p->second); map_.erase(p); + if (dispatch_interface_) + dispatch_interface_->OnCommandRemoved(id, instance.get()); } return instance; }
diff --git a/buffet/commands/command_queue.h b/buffet/commands/command_queue.h index 3f269f1..e70fb8d 100644 --- a/buffet/commands/command_queue.h +++ b/buffet/commands/command_queue.h
@@ -15,10 +15,19 @@ namespace buffet { +class CommandDispachInterface; + class CommandQueue final { public: CommandQueue() = default; + // Sets a command dispatch notifications for changes in command queue. + // |dispatch_interface| must outlive the CommandQueue object instance + // or be nullptr. + void SetCommandDispachInterface(CommandDispachInterface* dispatch_interface) { + dispatch_interface_ = dispatch_interface; + } + // Checks if the command queue is empty. bool IsEmpty() const { return map_.empty(); } @@ -47,6 +56,8 @@ std::map<std::string, std::unique_ptr<const CommandInstance>> map_; // Counter for generating unique command IDs. int next_id_ = 0; + // Callback interface for command dispatch, if provided. + CommandDispachInterface* dispatch_interface_ = nullptr; DISALLOW_COPY_AND_ASSIGN(CommandQueue); };
diff --git a/buffet/commands/command_queue_unittest.cc b/buffet/commands/command_queue_unittest.cc index d116e96..117d7bc 100644 --- a/buffet/commands/command_queue_unittest.cc +++ b/buffet/commands/command_queue_unittest.cc
@@ -2,11 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include <set> #include <string> +#include <vector> #include <gtest/gtest.h> +#include "buffet/commands/command_dispatch_interface.h" #include "buffet/commands/command_queue.h" +#include "buffet/string_utils.h" namespace { @@ -16,6 +20,41 @@ new buffet::CommandInstance(name, "powerd", {})); } +// Fake implementation of CommandDispachInterface. +// Just keeps track of commands being added to and removed from the queue. +// Aborts if duplicate commands are added or non-existent commands are removed. +class FakeDispatchInterface : public buffet::CommandDispachInterface { + public: + virtual void OnCommandAdded( + const std::string& command_id, + const buffet::CommandInstance* command_instance) override { + CHECK(ids_.insert(command_id).second) + << "Command ID already exists: " << command_id; + CHECK(commands_.insert(command_instance).second) + << "Command instance already exists"; + } + + virtual void OnCommandRemoved( + const std::string& command_id, + const buffet::CommandInstance* command_instance) override { + CHECK_EQ(1, ids_.erase(command_id)) + << "Command ID not found: " << command_id; + CHECK_EQ(1, commands_.erase(command_instance)) + << "Command instance not found"; + } + + // Get the comma-separated list of command IDs currently accumulated in the + // command queue. + std::string GetIDs() const { + using buffet::string_utils::Join; + return Join(',', std::vector<std::string>(ids_.begin(), ids_.end())); + } + + private: + std::set<std::string> ids_; + std::set<const buffet::CommandInstance*> commands_; +}; + } // anonymous namespace TEST(CommandQueue, Empty) { @@ -26,10 +65,12 @@ TEST(CommandQueue, Add) { buffet::CommandQueue queue; - EXPECT_EQ("1", queue.Add(CreateDummyCommandInstance())); - EXPECT_EQ("2", queue.Add(CreateDummyCommandInstance())); - EXPECT_EQ("3", queue.Add(CreateDummyCommandInstance())); + std::set<std::string> ids; // Using set<> to check that IDs are unique. + ids.insert(queue.Add(CreateDummyCommandInstance())); + ids.insert(queue.Add(CreateDummyCommandInstance())); + ids.insert(queue.Add(CreateDummyCommandInstance())); EXPECT_EQ(3, queue.GetCount()); + EXPECT_EQ(3, ids.size()); EXPECT_FALSE(queue.IsEmpty()); } @@ -51,6 +92,22 @@ EXPECT_TRUE(queue.IsEmpty()); } +TEST(CommandQueue, Dispatch) { + FakeDispatchInterface dispatch; + buffet::CommandQueue queue; + queue.SetCommandDispachInterface(&dispatch); + std::string id1 = queue.Add(CreateDummyCommandInstance()); + std::string id2 = queue.Add(CreateDummyCommandInstance()); + std::set<std::string> ids{id1, id2}; // Make sure they are sorted properly. + std::string expected_set = buffet::string_utils::Join( + ',', std::vector<std::string>(ids.begin(), ids.end())); + EXPECT_EQ(expected_set, dispatch.GetIDs()); + queue.Remove(id1); + EXPECT_EQ(id2, dispatch.GetIDs()); + queue.Remove(id2); + EXPECT_EQ("", dispatch.GetIDs()); +} + TEST(CommandQueue, Find) { buffet::CommandQueue queue; std::string id1 = queue.Add(CreateDummyCommandInstance("base.reboot"));