// Copyright 2022 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/task/sequence_manager/hierarchical_timing_wheel.h" #include #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace base::sequence_manager { namespace { // Custom comparator for testing. template struct CustomCompare { bool operator()(const T& lhs, const T& rhs) const { return std::tie(lhs.delayed_run_time, lhs.name) > std::tie(rhs.delayed_run_time, rhs.name); } }; class Task { public: enum : size_t { kInvalidIndex = std::numeric_limits::max() }; explicit Task(TimeTicks delayed_run_time, std::string name = std::string()) : delayed_run_time(delayed_run_time), name(name), handle_(std::make_unique()) {} HierarchicalTimingWheelHandle* handle() const { return handle_.get(); } TimeTicks delayed_run_time; // Used as a second comparator key to test the custom comparator // functionality. std::string name; private: std::unique_ptr handle_; }; } // namespace // Tests the construction of the object. TEST(HierarchicalTimingWheelTest, SimpleTest) { const TimeTicks baseline = TimeTicks::Now(); HierarchicalTimingWheel hierarchical_timing_wheel{ baseline}; EXPECT_EQ(hierarchical_timing_wheel.Size(), 0u); auto* handle = hierarchical_timing_wheel.Insert(Task{baseline + Microseconds(100)}) ->handle(); EXPECT_EQ(hierarchical_timing_wheel.Size(), 1u); hierarchical_timing_wheel.Remove(*handle); EXPECT_EQ(hierarchical_timing_wheel.Size(), 0u); } // Tests whether an element can be added in all the places in the hierarchy. TEST(HierarchicalTimingWheelTest, InsertAllDistinctElements) { const size_t kHierarchyCount = 6; const TimeTicks baseline = TimeTicks::Now(); HierarchicalTimingWheel hierarchical_timing_wheel{ baseline}; // Create time delta to insert a task in each of the hierarchy. The element's // index represents the hierarchy index in which it will be inserted. The // delays are chosen as per the example given in the class's header file. const TimeDelta kDelay[kHierarchyCount] = { Microseconds(100), Microseconds(500), Milliseconds(50), Seconds(5), Seconds(500), Seconds(50000)}; HierarchicalTimingWheelHandle* handles[kHierarchyCount]; for (size_t i = 0; i < kHierarchyCount; i++) { handles[i] = hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle(); } for (size_t i = 0; i < kHierarchyCount; i++) { EXPECT_EQ(handles[i]->GetHierarchyIndex(), i); const bool is_heap_handle = i == 0 || i == kHierarchyCount - 1; EXPECT_EQ(handles[i]->GetHeapHandle().IsValid(), is_heap_handle); EXPECT_EQ(handles[i]->GetTimingWheelHandle().IsValid(), !is_heap_handle); } } // Tests whether multiple elements can be added in the same place in the // hierarchy. TEST(HierarchicalTimingWheelTest, InsertSimilarElements) { const size_t kTotalElements = 3; const size_t kExpectedHierarchyIndex = 1; const TimeTicks baseline = TimeTicks::Now(); HierarchicalTimingWheel hierarchical_timing_wheel{ baseline}; // Create time delta to insert a task in the second hierarchy. const TimeDelta kDelay[kTotalElements] = {Microseconds(500), Milliseconds(21), Milliseconds(49)}; HierarchicalTimingWheelHandle* handles[kTotalElements]; for (size_t i = 0; i < kTotalElements; i++) { handles[i] = hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle(); } for (auto* handle : handles) { EXPECT_EQ(handle->GetHierarchyIndex(), kExpectedHierarchyIndex); EXPECT_EQ(handle->GetHeapHandle().IsValid(), false); EXPECT_EQ(handle->GetTimingWheelHandle().IsValid(), true); } } // Tests whether the hierarchy can be updated and cascading take place from one // hierarchy to another for an element. TEST(HierarchicalTimingWheelTest, UpdateOneElement) { const size_t kHierarchyCount = 6; TimeTicks baseline = TimeTicks::Now(); HierarchicalTimingWheel hierarchical_timing_wheel{ baseline}; // An array of deltas which cascades an element from the biggest // hierarchy to the smallest sequentially, and then finally expiring the // element. const TimeDelta kTimeDelta[] = {Seconds(50000) - Seconds(500), Seconds(500) - Seconds(5), Seconds(5) - Milliseconds(50), Milliseconds(50) - Microseconds(500), Microseconds(500) - Microseconds(100), Microseconds(100)}; // Create time delta to insert a task at the end of the hierarchy. const TimeTicks delayed_run_time = baseline + Seconds(50000); HierarchicalTimingWheelHandle* handle = hierarchical_timing_wheel.Insert(Task{delayed_run_time})->handle(); std::vector expired_tasks; for (size_t i = 0; i < kHierarchyCount; i++) { const size_t expected_hierarchy_index = kHierarchyCount - i - 1; EXPECT_EQ(handle->GetHierarchyIndex(), expected_hierarchy_index); baseline += kTimeDelta[i]; expired_tasks = hierarchical_timing_wheel.Update(baseline); // An element will be returned as expired on the last Update. const bool expired = i == kHierarchyCount - 1; // We expect one element to be returned, once. EXPECT_EQ(expired_tasks.size() == 0, !expired); EXPECT_EQ(expired_tasks.size() == 1, expired); } } // Tests whether the hierarchy can be updated and cascading take place of // multiple existing elements in the hierarchy. TEST(HierarchicalTimingWheelTest, UpdateMultipleElements) { const size_t kHierarchyCount = 6; TimeTicks baseline = TimeTicks::Now(); HierarchicalTimingWheel hierarchical_timing_wheel{ baseline}; // Create time delta to insert a task in each of the hierarchy. The element's // index represents the hierarchy index in which it will be inserted. const TimeDelta kDelay[kHierarchyCount] = { Microseconds(100), Microseconds(500), Milliseconds(50), Seconds(5), Seconds(500), Seconds(50000)}; HierarchicalTimingWheelHandle* handles[kHierarchyCount]; for (size_t i = 0; i < kHierarchyCount; i++) { handles[i] = hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle(); } // This update expires all the inserted elements except the last two. baseline += Seconds(499); std::vector expired_tasks = hierarchical_timing_wheel.Update(baseline); EXPECT_EQ(expired_tasks.size(), 4u); EXPECT_EQ(handles[kHierarchyCount - 2]->GetHierarchyIndex(), 2u); EXPECT_EQ(handles[kHierarchyCount - 1]->GetHierarchyIndex(), 4u); // Expires the last two elements by updating much more than latest delay // element. baseline += Seconds(100000); expired_tasks = hierarchical_timing_wheel.Update(baseline); EXPECT_EQ(expired_tasks.size(), 2u); } // Tests whether an element can be removed from each hierarchy. TEST(HierarchicalTimingWheelTest, RemoveElements) { const size_t kHierarchyCount = 6; const TimeTicks baseline = TimeTicks::Now(); HierarchicalTimingWheel hierarchical_timing_wheel{ baseline}; // Create time delta to insert a task in each of the hierarchy. The element's // index represents the hierarchy index in which it will be inserted. const TimeDelta kDelay[kHierarchyCount] = { Microseconds(100), Microseconds(500), Milliseconds(50), Seconds(5), Seconds(500), Seconds(50000)}; HierarchicalTimingWheelHandle* handles[kHierarchyCount]; for (size_t i = 0; i < kHierarchyCount; i++) { handles[i] = hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle(); } for (auto* handle : handles) { hierarchical_timing_wheel.Remove(*handle); } // The biggest delay was Seconds(50000). Hence, this would remove any leftover // element, which there aren't supposed to be. std::vector expired_tasks = hierarchical_timing_wheel.Update(baseline + Seconds(50000)); EXPECT_EQ(expired_tasks.empty(), true); } // Tests whether the top element of the hierarchy returned is correct when all // distinct elements exist in the hierarchy. TEST(HierarchicalTimingWheelTest, TopDifferentElements) { const size_t kHierarchyCount = 6; const TimeTicks baseline = TimeTicks::Now(); HierarchicalTimingWheel hierarchical_timing_wheel{ baseline}; // Create time delta to insert a task in each of the hierarchy. The element's // index represents the hierarchy index in which it will be inserted. const TimeDelta kDelay[kHierarchyCount] = { Microseconds(100), Microseconds(500), Milliseconds(50), Seconds(5), Seconds(500), Seconds(50000)}; HierarchicalTimingWheelHandle* handles[kHierarchyCount]; for (size_t i = 0; i < kHierarchyCount; i++) { handles[i] = hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle(); const Task& task = hierarchical_timing_wheel.Top(); EXPECT_EQ(task.delayed_run_time, baseline + kDelay[0]); } for (size_t i = 0; i < kHierarchyCount; i++) { const Task& task = hierarchical_timing_wheel.Top(); EXPECT_EQ(task.delayed_run_time, baseline + kDelay[i]); hierarchical_timing_wheel.Remove(*handles[i]); } } // Tests whether the top element of the hierarchy returned is correct when // multiple similar elements are in the hierarchy. TEST(HierarchicalTimingWheelTest, TopSimilarElements) { const size_t kTotalElements = 3; const TimeTicks baseline = TimeTicks::Now(); HierarchicalTimingWheel hierarchical_timing_wheel{ baseline}; // Create time delta to insert a task in the first hierarchy. const TimeDelta kDelay[kTotalElements] = { Microseconds(100), Microseconds(200), Microseconds(300)}; HierarchicalTimingWheelHandle* handles[kTotalElements]; for (size_t i = 0; i < kTotalElements; i++) { handles[i] = hierarchical_timing_wheel.Insert(Task{baseline + kDelay[i]})->handle(); const Task& task = hierarchical_timing_wheel.Top(); EXPECT_EQ(task.delayed_run_time, baseline + kDelay[0]); } for (size_t i = 0; i < kTotalElements; i++) { const Task& task = hierarchical_timing_wheel.Top(); EXPECT_EQ(task.delayed_run_time, baseline + kDelay[i]); hierarchical_timing_wheel.Remove(*handles[i]); } } // Tests whether the |Compare| functor is correctly used. TEST(HierarchicalTimingWheelTest, CustomComparator) { const std::string expectedTopTaskName = "a"; const TimeTicks baseline = TimeTicks::Now(); HierarchicalTimingWheel, GetDelayedRunTime, CustomCompare> hierarchical_timing_wheel{baseline}; // Create time delta to insert a task in the first hierarchy. const TimeDelta kDelay = Microseconds(100); // Inserts two elements in the same bucket. hierarchical_timing_wheel.Insert(Task{baseline + kDelay, "z"}); hierarchical_timing_wheel.Insert(Task{baseline + kDelay, "a"}); const Task& task = hierarchical_timing_wheel.Top(); // The custom comparator orders by the name's lexicographical order, // since both the elements have the same delayed run time. EXPECT_EQ(task.name, expectedTopTaskName); } } // namespace base::sequence_manager