// Copyright 2015 The Weave 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 "src/commands/command_queue.h" #include #include #include #include #include #include #include #include #include "src/bind_lambda.h" #include "src/string_utils.h" namespace weave { using testing::Return; using testing::StrictMock; class CommandQueueTest : public testing::Test { public: std::unique_ptr CreateDummyCommandInstance( const std::string& name, const std::string& id) { std::unique_ptr cmd{ new CommandInstance{name, Command::Origin::kLocal, {}}}; cmd->SetID(id); return cmd; } bool Remove(const std::string& id) { return queue_.Remove(id); } void Cleanup(const base::TimeDelta& interval) { return queue_.Cleanup(task_runner_.GetClock()->Now() + interval); } std::string GetFirstCommandToBeRemoved() const { return queue_.remove_queue_.top().second; } StrictMock task_runner_; CommandQueue queue_{&task_runner_, task_runner_.GetClock()}; }; // 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 FakeDispatcher { public: explicit FakeDispatcher(CommandQueue* queue) { queue->AddCommandAddedCallback(base::Bind(&FakeDispatcher::OnCommandAdded, weak_ptr_factory_.GetWeakPtr())); queue->AddCommandRemovedCallback(base::Bind( &FakeDispatcher::OnCommandRemoved, weak_ptr_factory_.GetWeakPtr())); } void OnCommandAdded(Command* command) { CHECK(ids_.insert(command->GetID()).second) << "Command ID already exists: " << command->GetID(); CHECK(commands_.insert(command).second) << "Command instance already exists"; } void OnCommandRemoved(Command* command) { CHECK_EQ(1u, ids_.erase(command->GetID())) << "Command ID not found: " << command->GetID(); CHECK_EQ(1u, commands_.erase(command)) << "Command instance not found"; } // Get the comma-separated list of command IDs currently accumulated in the // command queue_. std::string GetIDs() const { return Join(",", std::vector(ids_.begin(), ids_.end())); } private: std::set ids_; std::set commands_; base::WeakPtrFactory weak_ptr_factory_{this}; }; TEST_F(CommandQueueTest, Empty) { EXPECT_TRUE(queue_.IsEmpty()); EXPECT_EQ(0u, queue_.GetCount()); } TEST_F(CommandQueueTest, Add) { queue_.Add(CreateDummyCommandInstance("base.reboot", "id1")); queue_.Add(CreateDummyCommandInstance("base.reboot", "id2")); queue_.Add(CreateDummyCommandInstance("base.reboot", "id3")); EXPECT_EQ(3u, queue_.GetCount()); EXPECT_FALSE(queue_.IsEmpty()); } TEST_F(CommandQueueTest, Remove) { const std::string id1 = "id1"; const std::string id2 = "id2"; queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); queue_.Add(CreateDummyCommandInstance("base.reboot", id2)); EXPECT_FALSE(queue_.IsEmpty()); EXPECT_FALSE(Remove("dummy")); EXPECT_EQ(2u, queue_.GetCount()); EXPECT_TRUE(Remove(id1)); EXPECT_EQ(1u, queue_.GetCount()); EXPECT_FALSE(Remove(id1)); EXPECT_EQ(1u, queue_.GetCount()); EXPECT_TRUE(Remove(id2)); EXPECT_EQ(0u, queue_.GetCount()); EXPECT_FALSE(Remove(id2)); EXPECT_EQ(0u, queue_.GetCount()); EXPECT_TRUE(queue_.IsEmpty()); } TEST_F(CommandQueueTest, RemoveLater) { const std::string id1 = "id1"; queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); EXPECT_EQ(1u, queue_.GetCount()); queue_.RemoveLater(id1); EXPECT_EQ(1u, queue_.GetCount()); Cleanup(base::TimeDelta::FromMinutes(1)); EXPECT_EQ(1u, queue_.GetCount()); Cleanup(base::TimeDelta::FromMinutes(15)); EXPECT_EQ(0u, queue_.GetCount()); } TEST_F(CommandQueueTest, RemoveLaterOnCleanupTask) { const std::string id1 = "id1"; queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); EXPECT_EQ(1u, queue_.GetCount()); queue_.RemoveLater(id1); EXPECT_EQ(1u, queue_.GetCount()); ASSERT_EQ(1u, task_runner_.GetTaskQueueSize()); task_runner_.RunOnce(); EXPECT_EQ(0u, queue_.GetCount()); EXPECT_EQ(0u, task_runner_.GetTaskQueueSize()); } TEST_F(CommandQueueTest, CleanupMultipleCommands) { const std::string id1 = "id1"; const std::string id2 = "id2"; queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); queue_.Add(CreateDummyCommandInstance("base.reboot", id2)); auto remove_task = [](CommandQueue* queue, const std::string& id) { queue->RemoveLater(id); }; remove_task(&queue_, id1); task_runner_.PostDelayedTask( FROM_HERE, base::Bind(remove_task, base::Unretained(&queue_), id2), base::TimeDelta::FromSeconds(10)); EXPECT_EQ(2u, queue_.GetCount()); ASSERT_EQ(2u, task_runner_.GetTaskQueueSize()); task_runner_.RunOnce(); // Executes "remove_task(id2) @ T+10s". ASSERT_EQ(2u, queue_.GetCount()); ASSERT_EQ(1u, task_runner_.GetTaskQueueSize()); EXPECT_EQ(id1, GetFirstCommandToBeRemoved()); task_runner_.RunOnce(); // Should remove task "id1" from queue. ASSERT_EQ(1u, queue_.GetCount()); ASSERT_EQ(1u, task_runner_.GetTaskQueueSize()); EXPECT_EQ(id2, GetFirstCommandToBeRemoved()); task_runner_.RunOnce(); // Should remove task "id2" from queue. EXPECT_EQ(0u, queue_.GetCount()); EXPECT_EQ(0u, task_runner_.GetTaskQueueSize()); } TEST_F(CommandQueueTest, Dispatch) { FakeDispatcher dispatch(&queue_); const std::string id1 = "id1"; const std::string id2 = "id2"; queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); queue_.Add(CreateDummyCommandInstance("base.reboot", id2)); std::set ids{id1, id2}; // Make sure they are sorted properly. std::string expected_set = Join(",", std::vector(ids.begin(), ids.end())); EXPECT_EQ(expected_set, dispatch.GetIDs()); Remove(id1); EXPECT_EQ(id2, dispatch.GetIDs()); Remove(id2); EXPECT_EQ("", dispatch.GetIDs()); } TEST_F(CommandQueueTest, Find) { const std::string id1 = "id1"; const std::string id2 = "id2"; queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); queue_.Add(CreateDummyCommandInstance("base.shutdown", id2)); EXPECT_EQ(nullptr, queue_.Find("dummy")); auto cmd1 = queue_.Find(id1); EXPECT_NE(nullptr, cmd1); EXPECT_EQ("base.reboot", cmd1->GetName()); EXPECT_EQ(id1, cmd1->GetID()); auto cmd2 = queue_.Find(id2); EXPECT_NE(nullptr, cmd2); EXPECT_EQ("base.shutdown", cmd2->GetName()); EXPECT_EQ(id2, cmd2->GetID()); } } // namespace weave