blob: a590a36ab193aa17d25e2f1d0c50f26d3b63f6d4 [file] [log] [blame]
// 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 <set>
#include <string>
#include <vector>
#include <base/bind.h>
#include <base/memory/weak_ptr.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <weave/provider/test/fake_task_runner.h>
#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<CommandInstance> CreateDummyCommandInstance(
const std::string& name,
const std::string& id) {
std::unique_ptr<CommandInstance> 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<provider::test::FakeTaskRunner> 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<std::string>(ids_.begin(), ids_.end()));
}
private:
std::set<std::string> ids_;
std::set<Command*> commands_;
base::WeakPtrFactory<FakeDispatcher> 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<std::string> ids{id1, id2}; // Make sure they are sorted properly.
std::string expected_set =
Join(",", std::vector<std::string>(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