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:
CreateDummyCommandInstance(const std::string & name,const std::string & id)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
Remove(const std::string & id)36 bool Remove(const std::string& id) { return queue_.Remove(id); }
37
Cleanup(const base::TimeDelta & interval)38 void Cleanup(const base::TimeDelta& interval) {
39 return queue_.Cleanup(task_runner_.GetClock()->Now() + interval);
40 }
41
GetFirstCommandToBeRemoved() const42 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:
FakeDispatcher(CommandQueue * queue)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
OnCommandAdded(Command * command)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
OnCommandRemoved(Command * command)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_.
GetIDs() const76 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
TEST_F(CommandQueueTest,Empty)86 TEST_F(CommandQueueTest, Empty) {
87 EXPECT_TRUE(queue_.IsEmpty());
88 EXPECT_EQ(0u, queue_.GetCount());
89 }
90
TEST_F(CommandQueueTest,Add)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
TEST_F(CommandQueueTest,Remove)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
TEST_F(CommandQueueTest,RemoveLater)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
TEST_F(CommandQueueTest,RemoveLaterOnCleanupTask)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
TEST_F(CommandQueueTest,CleanupMultipleCommands)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
TEST_F(CommandQueueTest,Dispatch)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
TEST_F(CommandQueueTest,Find)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