1 // Copyright 2015 The Weave Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "src/commands/command_queue.h" 6 7 #include <set> 8 #include <string> 9 #include <vector> 10 11 #include <base/bind.h> 12 #include <base/memory/weak_ptr.h> 13 #include <gmock/gmock.h> 14 #include <gtest/gtest.h> 15 #include <weave/provider/test/fake_task_runner.h> 16 17 #include "src/bind_lambda.h" 18 #include "src/string_utils.h" 19 20 namespace weave { 21 22 using testing::Return; 23 using testing::StrictMock; 24 25 class CommandQueueTest : public testing::Test { 26 public: 27 std::unique_ptr<CommandInstance> CreateDummyCommandInstance( 28 const std::string& name, 29 const std::string& id) { 30 std::unique_ptr<CommandInstance> cmd{ 31 new CommandInstance{name, Command::Origin::kLocal, {}}}; 32 cmd->SetID(id); 33 return cmd; 34 } 35 36 bool Remove(const std::string& id) { return queue_.Remove(id); } 37 38 void Cleanup(const base::TimeDelta& interval) { 39 return queue_.Cleanup(task_runner_.GetClock()->Now() + interval); 40 } 41 42 std::string GetFirstCommandToBeRemoved() const { 43 return queue_.remove_queue_.top().second; 44 } 45 46 StrictMock<provider::test::FakeTaskRunner> task_runner_; 47 CommandQueue queue_{&task_runner_, task_runner_.GetClock()}; 48 }; 49 50 // Keeps track of commands being added to and removed from the queue_. 51 // Aborts if duplicate commands are added or non-existent commands are removed. 52 class FakeDispatcher { 53 public: 54 explicit FakeDispatcher(CommandQueue* queue) { 55 queue->AddCommandAddedCallback(base::Bind(&FakeDispatcher::OnCommandAdded, 56 weak_ptr_factory_.GetWeakPtr())); 57 queue->AddCommandRemovedCallback(base::Bind( 58 &FakeDispatcher::OnCommandRemoved, weak_ptr_factory_.GetWeakPtr())); 59 } 60 61 void OnCommandAdded(Command* command) { 62 CHECK(ids_.insert(command->GetID()).second) << "Command ID already exists: " 63 << command->GetID(); 64 CHECK(commands_.insert(command).second) 65 << "Command instance already exists"; 66 } 67 68 void OnCommandRemoved(Command* command) { 69 CHECK_EQ(1u, ids_.erase(command->GetID())) << "Command ID not found: " 70 << command->GetID(); 71 CHECK_EQ(1u, commands_.erase(command)) << "Command instance not found"; 72 } 73 74 // Get the comma-separated list of command IDs currently accumulated in the 75 // command queue_. 76 std::string GetIDs() const { 77 return Join(",", std::vector<std::string>(ids_.begin(), ids_.end())); 78 } 79 80 private: 81 std::set<std::string> ids_; 82 std::set<Command*> commands_; 83 base::WeakPtrFactory<FakeDispatcher> weak_ptr_factory_{this}; 84 }; 85 86 TEST_F(CommandQueueTest, Empty) { 87 EXPECT_TRUE(queue_.IsEmpty()); 88 EXPECT_EQ(0u, queue_.GetCount()); 89 } 90 91 TEST_F(CommandQueueTest, Add) { 92 queue_.Add(CreateDummyCommandInstance("base.reboot", "id1")); 93 queue_.Add(CreateDummyCommandInstance("base.reboot", "id2")); 94 queue_.Add(CreateDummyCommandInstance("base.reboot", "id3")); 95 EXPECT_EQ(3u, queue_.GetCount()); 96 EXPECT_FALSE(queue_.IsEmpty()); 97 } 98 99 TEST_F(CommandQueueTest, Remove) { 100 const std::string id1 = "id1"; 101 const std::string id2 = "id2"; 102 queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); 103 queue_.Add(CreateDummyCommandInstance("base.reboot", id2)); 104 EXPECT_FALSE(queue_.IsEmpty()); 105 EXPECT_FALSE(Remove("dummy")); 106 EXPECT_EQ(2u, queue_.GetCount()); 107 EXPECT_TRUE(Remove(id1)); 108 EXPECT_EQ(1u, queue_.GetCount()); 109 EXPECT_FALSE(Remove(id1)); 110 EXPECT_EQ(1u, queue_.GetCount()); 111 EXPECT_TRUE(Remove(id2)); 112 EXPECT_EQ(0u, queue_.GetCount()); 113 EXPECT_FALSE(Remove(id2)); 114 EXPECT_EQ(0u, queue_.GetCount()); 115 EXPECT_TRUE(queue_.IsEmpty()); 116 } 117 118 TEST_F(CommandQueueTest, RemoveLater) { 119 const std::string id1 = "id1"; 120 queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); 121 EXPECT_EQ(1u, queue_.GetCount()); 122 123 queue_.RemoveLater(id1); 124 EXPECT_EQ(1u, queue_.GetCount()); 125 126 Cleanup(base::TimeDelta::FromMinutes(1)); 127 EXPECT_EQ(1u, queue_.GetCount()); 128 129 Cleanup(base::TimeDelta::FromMinutes(15)); 130 EXPECT_EQ(0u, queue_.GetCount()); 131 } 132 133 TEST_F(CommandQueueTest, RemoveLaterOnCleanupTask) { 134 const std::string id1 = "id1"; 135 queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); 136 EXPECT_EQ(1u, queue_.GetCount()); 137 138 queue_.RemoveLater(id1); 139 EXPECT_EQ(1u, queue_.GetCount()); 140 ASSERT_EQ(1u, task_runner_.GetTaskQueueSize()); 141 142 task_runner_.RunOnce(); 143 144 EXPECT_EQ(0u, queue_.GetCount()); 145 EXPECT_EQ(0u, task_runner_.GetTaskQueueSize()); 146 } 147 148 TEST_F(CommandQueueTest, CleanupMultipleCommands) { 149 const std::string id1 = "id1"; 150 const std::string id2 = "id2"; 151 152 queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); 153 queue_.Add(CreateDummyCommandInstance("base.reboot", id2)); 154 auto remove_task = [this](const std::string& id) { queue_.RemoveLater(id); }; 155 remove_task(id1); 156 task_runner_.PostDelayedTask(FROM_HERE, base::Bind(remove_task, id2), 157 base::TimeDelta::FromSeconds(10)); 158 EXPECT_EQ(2u, queue_.GetCount()); 159 ASSERT_EQ(2u, task_runner_.GetTaskQueueSize()); 160 task_runner_.RunOnce(); // Executes "remove_task(id2) @ T+10s". 161 ASSERT_EQ(2u, queue_.GetCount()); 162 ASSERT_EQ(1u, task_runner_.GetTaskQueueSize()); 163 EXPECT_EQ(id1, GetFirstCommandToBeRemoved()); 164 task_runner_.RunOnce(); // Should remove task "id1" from queue. 165 ASSERT_EQ(1u, queue_.GetCount()); 166 ASSERT_EQ(1u, task_runner_.GetTaskQueueSize()); 167 EXPECT_EQ(id2, GetFirstCommandToBeRemoved()); 168 task_runner_.RunOnce(); // Should remove task "id2" from queue. 169 EXPECT_EQ(0u, queue_.GetCount()); 170 EXPECT_EQ(0u, task_runner_.GetTaskQueueSize()); 171 } 172 173 TEST_F(CommandQueueTest, Dispatch) { 174 FakeDispatcher dispatch(&queue_); 175 const std::string id1 = "id1"; 176 const std::string id2 = "id2"; 177 queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); 178 queue_.Add(CreateDummyCommandInstance("base.reboot", id2)); 179 std::set<std::string> ids{id1, id2}; // Make sure they are sorted properly. 180 std::string expected_set = 181 Join(",", std::vector<std::string>(ids.begin(), ids.end())); 182 EXPECT_EQ(expected_set, dispatch.GetIDs()); 183 Remove(id1); 184 EXPECT_EQ(id2, dispatch.GetIDs()); 185 Remove(id2); 186 EXPECT_EQ("", dispatch.GetIDs()); 187 } 188 189 TEST_F(CommandQueueTest, Find) { 190 const std::string id1 = "id1"; 191 const std::string id2 = "id2"; 192 queue_.Add(CreateDummyCommandInstance("base.reboot", id1)); 193 queue_.Add(CreateDummyCommandInstance("base.shutdown", id2)); 194 EXPECT_EQ(nullptr, queue_.Find("dummy")); 195 auto cmd1 = queue_.Find(id1); 196 EXPECT_NE(nullptr, cmd1); 197 EXPECT_EQ("base.reboot", cmd1->GetName()); 198 EXPECT_EQ(id1, cmd1->GetID()); 199 auto cmd2 = queue_.Find(id2); 200 EXPECT_NE(nullptr, cmd2); 201 EXPECT_EQ("base.shutdown", cmd2->GetName()); 202 EXPECT_EQ(id2, cmd2->GetID()); 203 } 204 205 } // namespace weave 206