blob: a9d1a2fe8f0ce3548224e33c5c4dc013aa8bd6cd [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
#pragma allow_unsafe_buffers
#endif
#include "base/task/sequence_manager/task_queue_selector.h"
#include <stddef.h>
#include <map>
#include <memory>
#include <set>
#include <utility>
#include <vector>
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/pending_task.h"
#include "base/task/sequence_manager/enqueue_order_generator.h"
#include "base/task/sequence_manager/task_queue_impl.h"
#include "base/task/sequence_manager/test/mock_time_domain.h"
#include "base/task/sequence_manager/work_queue.h"
#include "base/task/sequence_manager/work_queue_sets.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using testing::_;
using testing::ElementsAre;
using testing::NotNull;
namespace base {
namespace sequence_manager {
namespace internal {
// To avoid symbol collisions in jumbo builds.
namespace task_queue_selector_unittest {
namespace {
const TaskQueue::QueuePriority kHighestPriority = 0;
const TaskQueue::QueuePriority kHighPriority = 4;
const TaskQueue::QueuePriority kDefaultPriority = 5;
const TaskQueue::QueuePriority kPriorityCount = 10;
const size_t kTaskQueueCount = kPriorityCount;
// Tests assume high priority is higher than default priority, but nothing about
// their values.
static_assert(kHighPriority < kDefaultPriority);
} // namespace
class MockObserver : public TaskQueueSelector::Observer {
public:
MockObserver() = default;
MockObserver(const MockObserver&) = delete;
MockObserver& operator=(const MockObserver&) = delete;
~MockObserver() override = default;
MOCK_METHOD1(OnTaskQueueEnabled, void(internal::TaskQueueImpl*));
MOCK_METHOD0(OnWorkAvailable, void());
};
class TaskQueueSelectorForTest : public TaskQueueSelector {
public:
using TaskQueueSelector::ActivePriorityTracker;
using TaskQueueSelector::ChooseWithPriority;
using TaskQueueSelector::delayed_work_queue_sets;
using TaskQueueSelector::immediate_work_queue_sets;
using TaskQueueSelector::SetImmediateStarvationCountForTest;
using TaskQueueSelector::SetOperationOldest;
explicit TaskQueueSelectorForTest(
scoped_refptr<AssociatedThreadId> associated_thread)
: TaskQueueSelector(
associated_thread,
SequenceManager::Settings::Builder()
.SetPrioritySettings(
SequenceManager::PrioritySettings(kPriorityCount,
kDefaultPriority))
.Build()) {}
};
class TaskQueueSelectorTest : public testing::Test {
public:
TaskQueueSelectorTest()
: test_closure_(BindRepeating(&TaskQueueSelectorTest::TestFunction)),
associated_thread_(AssociatedThreadId::CreateBound()),
selector_(associated_thread_) {}
~TaskQueueSelectorTest() override = default;
void PushTasks(const size_t queue_indices[], size_t num_tasks) {
EnqueueOrderGenerator enqueue_order_generator;
for (size_t i = 0; i < num_tasks; i++) {
task_queues_[queue_indices[i]]->immediate_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
enqueue_order_generator.GenerateNext()));
}
}
void PushTasksWithEnqueueOrder(const size_t queue_indices[],
const size_t enqueue_orders[],
size_t num_tasks) {
for (size_t i = 0; i < num_tasks; i++) {
task_queues_[queue_indices[i]]->immediate_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(enqueue_orders[i])));
}
}
void PushTask(const size_t queue_index, const size_t enqueue_order) {
task_queues_[queue_index]->immediate_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(enqueue_order)));
}
std::vector<size_t> PopTasksAndReturnQueueIndices() {
std::vector<size_t> order;
while (WorkQueue* chosen_work_queue =
selector_.SelectWorkQueueToService()) {
size_t chosen_queue_index =
queue_to_index_map_.find(chosen_work_queue->task_queue())->second;
order.push_back(chosen_queue_index);
chosen_work_queue->PopTaskForTesting();
chosen_work_queue->work_queue_sets()->OnPopMinQueueInSet(
chosen_work_queue);
}
return order;
}
static void TestFunction() {}
protected:
void SetUp() final {
for (size_t i = 0; i < kTaskQueueCount; i++) {
std::unique_ptr<TaskQueueImpl> task_queue =
std::make_unique<TaskQueueImpl>(nullptr, nullptr,
TaskQueue::Spec(QueueName::TEST_TQ));
selector_.AddQueue(task_queue.get(), kDefaultPriority);
task_queues_.push_back(std::move(task_queue));
}
for (size_t i = 0; i < kTaskQueueCount; i++) {
EXPECT_EQ(kDefaultPriority, task_queues_[i]->GetQueuePriority()) << i;
queue_to_index_map_.insert(std::make_pair(task_queues_[i].get(), i));
}
}
void TearDown() final {
for (std::unique_ptr<TaskQueueImpl>& task_queue : task_queues_) {
// Note since this test doesn't have a SequenceManager we need to
// manually remove |task_queue| from the |selector_|. Normally
// UnregisterTaskQueue would do that.
selector_.RemoveQueue(task_queue.get());
task_queue->UnregisterTaskQueue();
}
}
std::unique_ptr<TaskQueueImpl> NewTaskQueueWithBlockReporting() {
return std::make_unique<TaskQueueImpl>(nullptr, nullptr,
TaskQueue::Spec(QueueName::TEST_TQ));
}
RepeatingClosure test_closure_;
scoped_refptr<AssociatedThreadId> associated_thread_;
TaskQueueSelectorForTest selector_;
std::vector<std::unique_ptr<TaskQueueImpl>> task_queues_;
std::map<TaskQueueImpl*, size_t> queue_to_index_map_;
};
TEST_F(TaskQueueSelectorTest, TestDefaultPriority) {
size_t queue_order[] = {4, 3, 2, 1, 0};
PushTasks(queue_order, 5);
EXPECT_THAT(PopTasksAndReturnQueueIndices(), ElementsAre(4, 3, 2, 1, 0));
}
TEST_F(TaskQueueSelectorTest, TestPriorities) {
for (size_t priority = 0; priority < kDefaultPriority; ++priority) {
size_t queue_order[] = {0, 1, 2, 3, 4};
PushTasks(queue_order, 5);
if (priority != kDefaultPriority) {
selector_.SetQueuePriority(task_queues_[2].get(), priority);
}
EXPECT_THAT(PopTasksAndReturnQueueIndices(), ElementsAre(2, 0, 1, 3, 4));
}
}
TEST_F(TaskQueueSelectorTest, TestMultiplePriorities) {
size_t reverse_priority_order[kPriorityCount];
for (size_t priority = 0; priority < kPriorityCount; ++priority) {
reverse_priority_order[(kPriorityCount - 1) - priority] = priority;
}
PushTasks(reverse_priority_order, kPriorityCount);
for (size_t i = 0; i < kPriorityCount; ++i) {
TaskQueue::QueuePriority priority = reverse_priority_order[i];
if (priority != kDefaultPriority) {
selector_.SetQueuePriority(task_queues_[i].get(), priority);
}
EXPECT_EQ(task_queues_[i]->GetQueuePriority(), priority);
}
EXPECT_THAT(PopTasksAndReturnQueueIndices(),
ElementsAre(9, 8, 7, 6, 5, 4, 3, 2, 1, 0));
}
TEST_F(TaskQueueSelectorTest, TestObserverWorkAvailableOnPushTask) {
testing::StrictMock<MockObserver> mock_observer;
selector_.SetTaskQueueSelectorObserver(&mock_observer);
EXPECT_CALL(mock_observer, OnWorkAvailable());
PushTask(/* queue_index=*/0, /* enqueue_order=*/4);
}
TEST_F(TaskQueueSelectorTest, TestObserverWithEnabledQueue) {
task_queues_[1]->SetQueueEnabled(false);
selector_.DisableQueue(task_queues_[1].get());
{
testing::StrictMock<MockObserver> mock_observer;
selector_.SetTaskQueueSelectorObserver(&mock_observer);
EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_)).Times(1);
task_queues_[1]->SetQueueEnabled(true);
selector_.EnableQueue(task_queues_[1].get());
// Clear observer before it goes out of scope.
selector_.SetTaskQueueSelectorObserver(nullptr);
}
}
TEST_F(TaskQueueSelectorTest,
TestObserverWithSetQueuePriorityAndQueueAlreadyEnabled) {
selector_.SetQueuePriority(task_queues_[1].get(), kHighPriority);
{
testing::StrictMock<MockObserver> mock_observer;
selector_.SetTaskQueueSelectorObserver(&mock_observer);
EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_)).Times(0);
selector_.SetQueuePriority(task_queues_[1].get(), kDefaultPriority);
// Clear observer before it goes out of scope.
selector_.SetTaskQueueSelectorObserver(nullptr);
}
}
TEST_F(TaskQueueSelectorTest, TestDisableEnable) {
testing::StrictMock<MockObserver> mock_observer;
selector_.SetTaskQueueSelectorObserver(&mock_observer);
size_t queue_order[] = {0, 1, 2, 3, 4};
{
// Work is available when a task is pushed to an enabled queue (all queues
// were previously empty).
EXPECT_CALL(mock_observer, OnWorkAvailable());
PushTasks(queue_order, 5);
testing::Mock::VerifyAndClearExpectations(&mock_observer);
}
task_queues_[2]->SetQueueEnabled(false);
selector_.DisableQueue(task_queues_[2].get());
task_queues_[4]->SetQueueEnabled(false);
selector_.DisableQueue(task_queues_[4].get());
// Disabling a queue should not affect its priority.
EXPECT_EQ(kDefaultPriority, task_queues_[2]->GetQueuePriority());
EXPECT_EQ(kDefaultPriority, task_queues_[4]->GetQueuePriority());
EXPECT_THAT(PopTasksAndReturnQueueIndices(), ElementsAre(0, 1, 3));
task_queues_[2]->SetQueueEnabled(true);
{
EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_));
// Work is available when a non-empty queue is enabled (all queues were
// previously disabled).
EXPECT_CALL(mock_observer, OnWorkAvailable());
selector_.EnableQueue(task_queues_[2].get());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
}
selector_.SetQueuePriority(task_queues_[2].get(), kPriorityCount - 1);
EXPECT_THAT(PopTasksAndReturnQueueIndices(), ElementsAre(2));
task_queues_[4]->SetQueueEnabled(true);
{
EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_));
// Work is available when a non-empty queue is enabled (all queues were
// previously disabled).
EXPECT_CALL(mock_observer, OnWorkAvailable());
selector_.EnableQueue(task_queues_[4].get());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
}
EXPECT_THAT(PopTasksAndReturnQueueIndices(), ElementsAre(4));
// Clear observer before it goes out of scope.
selector_.SetTaskQueueSelectorObserver(nullptr);
}
TEST_F(TaskQueueSelectorTest, TestDisableChangePriorityThenEnable) {
EXPECT_TRUE(task_queues_[2]->delayed_work_queue()->Empty());
EXPECT_TRUE(task_queues_[2]->immediate_work_queue()->Empty());
task_queues_[2]->SetQueueEnabled(false);
selector_.SetQueuePriority(task_queues_[2].get(), kHighPriority);
size_t queue_order[] = {0, 1, 2, 3, 4};
PushTasks(queue_order, 5);
EXPECT_TRUE(task_queues_[2]->delayed_work_queue()->Empty());
EXPECT_FALSE(task_queues_[2]->immediate_work_queue()->Empty());
task_queues_[2]->SetQueueEnabled(true);
EXPECT_EQ(kHighPriority, task_queues_[2]->GetQueuePriority());
EXPECT_THAT(PopTasksAndReturnQueueIndices(), ElementsAre(2, 0, 1, 3, 4));
}
TEST_F(TaskQueueSelectorTest, TestEmptyQueues) {
EXPECT_EQ(nullptr, selector_.SelectWorkQueueToService());
// Test only disabled queues.
size_t queue_order[] = {0};
PushTasks(queue_order, 1);
task_queues_[0]->SetQueueEnabled(false);
selector_.DisableQueue(task_queues_[0].get());
EXPECT_EQ(nullptr, selector_.SelectWorkQueueToService());
// These tests are unusual since there's no TQM. To avoid a later DCHECK when
// deleting the task queue, we re-enable the queue here so the selector
// doesn't get out of sync.
task_queues_[0]->SetQueueEnabled(true);
selector_.EnableQueue(task_queues_[0].get());
}
TEST_F(TaskQueueSelectorTest, TestAge) {
size_t enqueue_order[] = {10, 1, 2, 9, 4};
size_t queue_order[] = {0, 1, 2, 3, 4};
PushTasksWithEnqueueOrder(queue_order, enqueue_order, 5);
EXPECT_THAT(PopTasksAndReturnQueueIndices(), ElementsAre(1, 2, 4, 3, 0));
}
class TaskQueueSelectorStarvationTest : public TaskQueueSelectorTest {
public:
TaskQueueSelectorStarvationTest() = default;
protected:
void TestPriorityOrder(const size_t queue_order[], size_t num_tasks) {
for (size_t i = 0; i < kTaskQueueCount; i++) {
// Setting the queue priority to its current value causes a check to fail.
if (task_queues_[i]->GetQueuePriority() !=
static_cast<TaskQueue::QueuePriority>(i)) {
selector_.SetQueuePriority(task_queues_[i].get(),
static_cast<TaskQueue::QueuePriority>(i));
}
}
ASSERT_EQ(num_tasks, kTaskQueueCount);
PushTasks(queue_order, kTaskQueueCount);
for (size_t priority = 0; priority < kTaskQueueCount; priority++) {
for (int i = 0; i < 100; i++) {
WorkQueue* chosen_work_queue = selector_.SelectWorkQueueToService();
ASSERT_THAT(chosen_work_queue, NotNull());
EXPECT_EQ(task_queues_[priority].get(),
chosen_work_queue->task_queue());
// Don't remove task from queue to simulate all queues still being full.
}
// Simulate the highest priority queue becoming empty.
WorkQueue* chosen_work_queue = selector_.SelectWorkQueueToService();
chosen_work_queue->PopTaskForTesting();
chosen_work_queue->work_queue_sets()->OnPopMinQueueInSet(
chosen_work_queue);
}
}
};
TEST_F(TaskQueueSelectorStarvationTest,
HigherPriorityWorkStarvesLowerPriorityWork) {
size_t queue_order[kTaskQueueCount];
for (size_t i = 0; i < kTaskQueueCount; i++)
queue_order[i] = i;
TestPriorityOrder(queue_order, kTaskQueueCount);
}
TEST_F(TaskQueueSelectorStarvationTest,
NewHigherPriorityTasksStarveOldLowerPriorityTasks) {
// Enqueue tasks in order from lowest to highest priority, and check that they
// still run in order from highest to lowest priority.
size_t queue_order[kTaskQueueCount];
for (size_t i = 0; i < kTaskQueueCount; i++)
queue_order[i] = (kTaskQueueCount - i) - 1;
TestPriorityOrder(queue_order, kTaskQueueCount);
}
TEST_F(TaskQueueSelectorTest, GetHighestPendingPriority) {
EXPECT_FALSE(selector_.GetHighestPendingPriority().has_value());
size_t queue_order[] = {0, 1};
PushTasks(queue_order, 2);
selector_.SetQueuePriority(task_queues_[1].get(), kHighPriority);
EXPECT_EQ(kHighPriority, *selector_.GetHighestPendingPriority());
PopTasksAndReturnQueueIndices();
EXPECT_FALSE(selector_.GetHighestPendingPriority().has_value());
PushTasks(queue_order, 1);
EXPECT_EQ(kDefaultPriority, *selector_.GetHighestPendingPriority());
PopTasksAndReturnQueueIndices();
EXPECT_FALSE(selector_.GetHighestPendingPriority().has_value());
}
TEST_F(TaskQueueSelectorTest, ChooseWithPriority_Empty) {
EXPECT_EQ(
nullptr,
selector_
.ChooseWithPriority<TaskQueueSelectorForTest::SetOperationOldest>(
kDefaultPriority));
}
TEST_F(TaskQueueSelectorTest, ChooseWithPriority_OnlyDelayed) {
task_queues_[0]->delayed_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(2)));
EXPECT_EQ(
task_queues_[0]->delayed_work_queue(),
selector_
.ChooseWithPriority<TaskQueueSelectorForTest::SetOperationOldest>(
kDefaultPriority));
}
TEST_F(TaskQueueSelectorTest, ChooseWithPriority_OnlyImmediate) {
task_queues_[0]->immediate_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(2)));
EXPECT_EQ(
task_queues_[0]->immediate_work_queue(),
selector_
.ChooseWithPriority<TaskQueueSelectorForTest::SetOperationOldest>(
kDefaultPriority));
}
TEST_F(TaskQueueSelectorTest,
SelectWorkQueueToServiceImmediateOnlyWithoutImmediateTask) {
task_queues_[0]->delayed_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(2)));
EXPECT_EQ(nullptr,
selector_.SelectWorkQueueToService(
TaskQueueSelector::SelectTaskOption::kSkipDelayedTask));
EXPECT_EQ(task_queues_[0]->delayed_work_queue(),
selector_.SelectWorkQueueToService());
}
TEST_F(TaskQueueSelectorTest,
SelectWorkQueueToServiceImmediateOnlyWithDelayedTasks) {
task_queues_[0]->delayed_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(1)));
task_queues_[0]->immediate_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(2)));
EXPECT_EQ(task_queues_[0]->immediate_work_queue(),
selector_.SelectWorkQueueToService(
TaskQueueSelector::SelectTaskOption::kSkipDelayedTask));
EXPECT_EQ(task_queues_[0]->delayed_work_queue(),
selector_.SelectWorkQueueToService());
}
TEST_F(TaskQueueSelectorTest,
SelectWorkQueueToServiceImmediateOnlyWithDisabledQueues) {
task_queues_[0]->delayed_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(1)));
task_queues_[0]->immediate_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(2)));
task_queues_[1]->delayed_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(3)));
task_queues_[2]->immediate_work_queue()->Push(
Task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(4)));
EXPECT_EQ(task_queues_[0]->delayed_work_queue(),
selector_.SelectWorkQueueToService());
EXPECT_EQ(task_queues_[0]->immediate_work_queue(),
selector_.SelectWorkQueueToService(
TaskQueueSelector::SelectTaskOption::kSkipDelayedTask));
task_queues_[0]->SetQueueEnabled(false);
selector_.DisableQueue(task_queues_[0].get());
EXPECT_EQ(task_queues_[1]->delayed_work_queue(),
selector_.SelectWorkQueueToService());
EXPECT_EQ(task_queues_[2]->immediate_work_queue(),
selector_.SelectWorkQueueToService(
TaskQueueSelector::SelectTaskOption::kSkipDelayedTask));
task_queues_[1]->SetQueueEnabled(false);
selector_.DisableQueue(task_queues_[1].get());
EXPECT_EQ(task_queues_[2]->immediate_work_queue(),
selector_.SelectWorkQueueToService(
TaskQueueSelector::SelectTaskOption::kSkipDelayedTask));
EXPECT_EQ(task_queues_[2]->immediate_work_queue(),
selector_.SelectWorkQueueToService());
}
TEST_F(TaskQueueSelectorTest, TestObserverWithOneBlockedQueue) {
testing::StrictMock<MockObserver> mock_observer; // Must outlive `selector`
TaskQueueSelectorForTest selector(associated_thread_);
selector.SetTaskQueueSelectorObserver(&mock_observer);
std::unique_ptr<TaskQueueImpl> task_queue(NewTaskQueueWithBlockReporting());
selector.AddQueue(task_queue.get(), kDefaultPriority);
task_queue->SetQueueEnabled(false);
selector.DisableQueue(task_queue.get());
Task task(PostedTask(nullptr, test_closure_, FROM_HERE), EnqueueOrder(),
EnqueueOrder::FromIntForTesting(2));
task_queue->immediate_work_queue()->Push(std::move(task));
EXPECT_EQ(nullptr, selector.SelectWorkQueueToService());
task_queue->SetQueueEnabled(true);
{
EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_));
EXPECT_CALL(mock_observer, OnWorkAvailable());
selector.EnableQueue(task_queue.get());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
}
selector.RemoveQueue(task_queue.get());
task_queue->UnregisterTaskQueue();
}
TEST_F(TaskQueueSelectorTest, TestObserverWithTwoBlockedQueues) {
testing::StrictMock<MockObserver> mock_observer; // Must outlive `selector`
TaskQueueSelectorForTest selector(associated_thread_);
selector.SetTaskQueueSelectorObserver(&mock_observer);
std::unique_ptr<TaskQueueImpl> task_queue(NewTaskQueueWithBlockReporting());
std::unique_ptr<TaskQueueImpl> task_queue2(NewTaskQueueWithBlockReporting());
selector.AddQueue(task_queue.get(), kDefaultPriority);
selector.AddQueue(task_queue2.get(), kDefaultPriority);
task_queue->SetQueueEnabled(false);
task_queue2->SetQueueEnabled(false);
selector.DisableQueue(task_queue.get());
selector.DisableQueue(task_queue2.get());
selector.SetQueuePriority(task_queue2.get(), kHighestPriority);
Task task1(PostedTask(nullptr, test_closure_, FROM_HERE),
EnqueueOrder::FromIntForTesting(2),
EnqueueOrder::FromIntForTesting(2));
Task task2(PostedTask(nullptr, test_closure_, FROM_HERE),
EnqueueOrder::FromIntForTesting(3),
EnqueueOrder::FromIntForTesting(3));
task_queue->immediate_work_queue()->Push(std::move(task1));
task_queue2->immediate_work_queue()->Push(std::move(task2));
EXPECT_EQ(nullptr, selector.SelectWorkQueueToService());
{
EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_));
EXPECT_CALL(mock_observer, OnWorkAvailable());
task_queue->SetQueueEnabled(true);
selector.EnableQueue(task_queue.get());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
}
selector.RemoveQueue(task_queue.get());
task_queue->UnregisterTaskQueue();
EXPECT_EQ(nullptr, selector.SelectWorkQueueToService());
{
EXPECT_CALL(mock_observer, OnTaskQueueEnabled(_));
EXPECT_CALL(mock_observer, OnWorkAvailable());
task_queue2->SetQueueEnabled(true);
selector.EnableQueue(task_queue2.get());
testing::Mock::VerifyAndClearExpectations(&mock_observer);
}
selector.RemoveQueue(task_queue2.get());
task_queue2->UnregisterTaskQueue();
}
TEST_F(TaskQueueSelectorTest, CollectSkippedOverLowerPriorityTasks) {
size_t queue_order[] = {0, 1, 2, 3, 2, 1, 0};
PushTasks(queue_order, 7);
selector_.SetQueuePriority(task_queues_[3].get(), kHighPriority);
std::vector<const Task*> result;
selector_.CollectSkippedOverLowerPriorityTasks(
task_queues_[3]->immediate_work_queue(), &result);
ASSERT_EQ(3u, result.size());
EXPECT_EQ(2u, result[0]->enqueue_order()); // The order here isn't important.
EXPECT_EQ(3u, result[1]->enqueue_order());
EXPECT_EQ(4u, result[2]->enqueue_order());
}
struct ChooseWithPriorityTestParam {
int delayed_task_enqueue_order;
int immediate_task_enqueue_order;
int immediate_starvation_count;
const char* expected_work_queue_name;
};
static const ChooseWithPriorityTestParam kChooseWithPriorityTestCases[] = {
{1, 2, 0, "delayed"}, {1, 2, 1, "delayed"}, {1, 2, 2, "delayed"},
{1, 2, 3, "immediate"}, {1, 2, 4, "immediate"}, {2, 1, 4, "immediate"},
{2, 1, 4, "immediate"},
};
class ChooseWithPriorityTest
: public TaskQueueSelectorTest,
public testing::WithParamInterface<ChooseWithPriorityTestParam> {};
TEST_P(ChooseWithPriorityTest, RoundRobinTest) {
task_queues_[0]->immediate_work_queue()->Push(Task(
PostedTask(nullptr, test_closure_, FROM_HERE),
EnqueueOrder::FromIntForTesting(GetParam().immediate_task_enqueue_order),
EnqueueOrder::FromIntForTesting(
GetParam().immediate_task_enqueue_order)));
task_queues_[0]->delayed_work_queue()->Push(Task(
PostedTask(nullptr, test_closure_, FROM_HERE),
EnqueueOrder::FromIntForTesting(GetParam().delayed_task_enqueue_order),
EnqueueOrder::FromIntForTesting(GetParam().delayed_task_enqueue_order)));
selector_.SetImmediateStarvationCountForTest(
GetParam().immediate_starvation_count);
WorkQueue* chosen_work_queue =
selector_
.ChooseWithPriority<TaskQueueSelectorForTest::SetOperationOldest>(
kDefaultPriority);
EXPECT_EQ(chosen_work_queue->task_queue(), task_queues_[0].get());
EXPECT_STREQ(chosen_work_queue->name(), GetParam().expected_work_queue_name);
}
INSTANTIATE_TEST_SUITE_P(ChooseWithPriorityTest,
ChooseWithPriorityTest,
testing::ValuesIn(kChooseWithPriorityTestCases));
class ActivePriorityTrackerTest : public testing::Test {
protected:
TaskQueueSelectorForTest::ActivePriorityTracker active_priority_tracker_;
};
TEST_F(ActivePriorityTrackerTest, SetPriorityActiveAndInactive) {
EXPECT_FALSE(active_priority_tracker_.HasActivePriority());
EXPECT_FALSE(active_priority_tracker_.IsActive(kDefaultPriority));
active_priority_tracker_.SetActive(kDefaultPriority, true);
EXPECT_TRUE(active_priority_tracker_.HasActivePriority());
EXPECT_TRUE(active_priority_tracker_.IsActive(kDefaultPriority));
active_priority_tracker_.SetActive(kDefaultPriority, false);
EXPECT_FALSE(active_priority_tracker_.HasActivePriority());
EXPECT_FALSE(active_priority_tracker_.IsActive(kDefaultPriority));
}
TEST_F(ActivePriorityTrackerTest, HighestActivePriority) {
EXPECT_FALSE(active_priority_tracker_.HasActivePriority());
for (size_t i = 0; i < SequenceManager::PrioritySettings::kMaxPriorities;
i++) {
TaskQueue::QueuePriority priority =
static_cast<TaskQueue::QueuePriority>(i);
EXPECT_FALSE(active_priority_tracker_.IsActive(priority));
active_priority_tracker_.SetActive(priority, true);
EXPECT_TRUE(active_priority_tracker_.IsActive(priority));
}
for (size_t i = 0; i < SequenceManager::PrioritySettings::kMaxPriorities;
i++) {
EXPECT_TRUE(active_priority_tracker_.HasActivePriority());
TaskQueue::QueuePriority priority =
static_cast<TaskQueue::QueuePriority>(i);
EXPECT_EQ(active_priority_tracker_.HighestActivePriority(), priority);
active_priority_tracker_.SetActive(priority, false);
}
EXPECT_FALSE(active_priority_tracker_.HasActivePriority());
}
} // namespace task_queue_selector_unittest
} // namespace internal
} // namespace sequence_manager
} // namespace base