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