| // Copyright 2023 The Chromium 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 "quiche/common/btree_scheduler.h" | |
| #include <optional> | |
| #include <ostream> | |
| #include <string> | |
| #include <tuple> | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/types/span.h" | |
| #include "quiche/common/platform/api/quiche_test.h" | |
| #include "quiche/common/test_tools/quiche_test_utils.h" | |
| namespace quiche::test { | |
| namespace { | |
| using ::testing::ElementsAre; | |
| using ::testing::Optional; | |
| template <typename Id, typename Priority> | |
| void ScheduleIds(BTreeScheduler<Id, Priority>& scheduler, | |
| absl::Span<const Id> ids) { | |
| for (Id id : ids) { | |
| QUICHE_EXPECT_OK(scheduler.Schedule(id)); | |
| } | |
| } | |
| template <typename Id, typename Priority> | |
| std::vector<Id> PopAll(BTreeScheduler<Id, Priority>& scheduler) { | |
| std::vector<Id> result; | |
| result.reserve(scheduler.NumScheduled()); | |
| for (;;) { | |
| absl::StatusOr<Id> id = scheduler.PopFront(); | |
| if (id.ok()) { | |
| result.push_back(*id); | |
| } else { | |
| EXPECT_THAT(id, StatusIs(absl::StatusCode::kNotFound)); | |
| break; | |
| } | |
| } | |
| return result; | |
| } | |
| TEST(BTreeSchedulerTest, SimplePop) { | |
| BTreeScheduler<int, int> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(1, 100)); | |
| QUICHE_EXPECT_OK(scheduler.Register(2, 101)); | |
| QUICHE_EXPECT_OK(scheduler.Register(3, 102)); | |
| EXPECT_THAT(scheduler.GetPriorityFor(1), Optional(100)); | |
| EXPECT_THAT(scheduler.GetPriorityFor(3), Optional(102)); | |
| EXPECT_EQ(scheduler.GetPriorityFor(5), std::nullopt); | |
| EXPECT_EQ(scheduler.NumScheduled(), 0u); | |
| EXPECT_FALSE(scheduler.HasScheduled()); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(1)); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(2)); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(3)); | |
| EXPECT_EQ(scheduler.NumScheduled(), 3u); | |
| EXPECT_TRUE(scheduler.HasScheduled()); | |
| EXPECT_THAT(scheduler.PopFront(), IsOkAndHolds(3)); | |
| EXPECT_THAT(scheduler.PopFront(), IsOkAndHolds(2)); | |
| EXPECT_THAT(scheduler.PopFront(), IsOkAndHolds(1)); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(2)); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(1)); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(3)); | |
| EXPECT_THAT(scheduler.PopFront(), IsOkAndHolds(3)); | |
| EXPECT_THAT(scheduler.PopFront(), IsOkAndHolds(2)); | |
| EXPECT_THAT(scheduler.PopFront(), IsOkAndHolds(1)); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(3)); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(1)); | |
| EXPECT_THAT(scheduler.PopFront(), IsOkAndHolds(3)); | |
| EXPECT_THAT(scheduler.PopFront(), IsOkAndHolds(1)); | |
| } | |
| TEST(BTreeSchedulerTest, FIFO) { | |
| BTreeScheduler<int, int> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(1, 100)); | |
| QUICHE_EXPECT_OK(scheduler.Register(2, 100)); | |
| QUICHE_EXPECT_OK(scheduler.Register(3, 100)); | |
| ScheduleIds(scheduler, {2, 1, 3}); | |
| EXPECT_THAT(PopAll(scheduler), ElementsAre(2, 1, 3)); | |
| QUICHE_EXPECT_OK(scheduler.Register(4, 101)); | |
| QUICHE_EXPECT_OK(scheduler.Register(5, 99)); | |
| ScheduleIds(scheduler, {5, 1, 2, 3, 4}); | |
| EXPECT_THAT(PopAll(scheduler), ElementsAre(4, 1, 2, 3, 5)); | |
| ScheduleIds(scheduler, {1, 5, 2, 4, 3}); | |
| EXPECT_THAT(PopAll(scheduler), ElementsAre(4, 1, 2, 3, 5)); | |
| ScheduleIds(scheduler, {3, 5, 2, 4, 1}); | |
| EXPECT_THAT(PopAll(scheduler), ElementsAre(4, 3, 2, 1, 5)); | |
| ScheduleIds(scheduler, {3, 2, 1, 2, 3}); | |
| EXPECT_THAT(PopAll(scheduler), ElementsAre(3, 2, 1)); | |
| } | |
| TEST(BTreeSchedulerTest, NumEntriesInRange) { | |
| BTreeScheduler<int, int> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(1, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Register(2, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Register(3, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Register(4, -2)); | |
| QUICHE_EXPECT_OK(scheduler.Register(5, -5)); | |
| QUICHE_EXPECT_OK(scheduler.Register(6, 10)); | |
| QUICHE_EXPECT_OK(scheduler.Register(7, 16)); | |
| QUICHE_EXPECT_OK(scheduler.Register(8, 32)); | |
| QUICHE_EXPECT_OK(scheduler.Register(9, 64)); | |
| EXPECT_EQ(scheduler.NumScheduled(), 0u); | |
| EXPECT_EQ(scheduler.NumScheduledInPriorityRange(std::nullopt, std::nullopt), | |
| 0u); | |
| EXPECT_EQ(scheduler.NumScheduledInPriorityRange(-1, 1), 0u); | |
| for (int stream = 1; stream <= 9; ++stream) { | |
| QUICHE_ASSERT_OK(scheduler.Schedule(stream)); | |
| } | |
| EXPECT_EQ(scheduler.NumScheduled(), 9u); | |
| EXPECT_EQ(scheduler.NumScheduledInPriorityRange(std::nullopt, std::nullopt), | |
| 9u); | |
| EXPECT_EQ(scheduler.NumScheduledInPriorityRange(0, 0), 3u); | |
| EXPECT_EQ(scheduler.NumScheduledInPriorityRange(std::nullopt, -1), 2u); | |
| EXPECT_EQ(scheduler.NumScheduledInPriorityRange(1, std::nullopt), 4u); | |
| } | |
| TEST(BTreeSchedulerTest, Registration) { | |
| BTreeScheduler<int, int> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(1, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Register(2, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(1)); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(2)); | |
| EXPECT_EQ(scheduler.NumScheduled(), 2u); | |
| EXPECT_TRUE(scheduler.IsScheduled(2)); | |
| EXPECT_THAT(scheduler.Register(2, 0), | |
| StatusIs(absl::StatusCode::kAlreadyExists)); | |
| QUICHE_EXPECT_OK(scheduler.Unregister(2)); | |
| EXPECT_EQ(scheduler.NumScheduled(), 1u); | |
| EXPECT_FALSE(scheduler.IsScheduled(2)); | |
| EXPECT_THAT(scheduler.UpdatePriority(2, 1234), | |
| StatusIs(absl::StatusCode::kNotFound)); | |
| EXPECT_THAT(scheduler.Unregister(2), StatusIs(absl::StatusCode::kNotFound)); | |
| EXPECT_THAT(scheduler.Schedule(2), StatusIs(absl::StatusCode::kNotFound)); | |
| QUICHE_EXPECT_OK(scheduler.Register(2, 0)); | |
| EXPECT_EQ(scheduler.NumScheduled(), 1u); | |
| EXPECT_TRUE(scheduler.IsScheduled(1)); | |
| EXPECT_FALSE(scheduler.IsScheduled(2)); | |
| } | |
| TEST(BTreeSchedulerTest, UpdatePriorityUp) { | |
| BTreeScheduler<int, int> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(1, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Register(2, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Register(3, 0)); | |
| ScheduleIds(scheduler, {1, 2, 3}); | |
| QUICHE_EXPECT_OK(scheduler.UpdatePriority(2, 1000)); | |
| EXPECT_THAT(PopAll(scheduler), ElementsAre(2, 1, 3)); | |
| } | |
| TEST(BTreeSchedulerTest, UpdatePriorityDown) { | |
| BTreeScheduler<int, int> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(1, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Register(2, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Register(3, 0)); | |
| ScheduleIds(scheduler, {1, 2, 3}); | |
| QUICHE_EXPECT_OK(scheduler.UpdatePriority(2, -1000)); | |
| EXPECT_THAT(PopAll(scheduler), ElementsAre(1, 3, 2)); | |
| } | |
| TEST(BTreeSchedulerTest, UpdatePriorityEqual) { | |
| BTreeScheduler<int, int> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(1, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Register(2, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Register(3, 0)); | |
| ScheduleIds(scheduler, {1, 2, 3}); | |
| QUICHE_EXPECT_OK(scheduler.UpdatePriority(2, 0)); | |
| EXPECT_THAT(PopAll(scheduler), ElementsAre(1, 2, 3)); | |
| } | |
| TEST(BTreeSchedulerTest, UpdatePriorityIntoSameBucket) { | |
| BTreeScheduler<int, int> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(1, 0)); | |
| QUICHE_EXPECT_OK(scheduler.Register(2, -100)); | |
| QUICHE_EXPECT_OK(scheduler.Register(3, 0)); | |
| ScheduleIds(scheduler, {1, 2, 3}); | |
| QUICHE_EXPECT_OK(scheduler.UpdatePriority(2, 0)); | |
| EXPECT_THAT(PopAll(scheduler), ElementsAre(1, 2, 3)); | |
| } | |
| TEST(BTreeSchedulerTest, ShouldYield) { | |
| BTreeScheduler<int, int> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(10, 100)); | |
| QUICHE_EXPECT_OK(scheduler.Register(20, 101)); | |
| QUICHE_EXPECT_OK(scheduler.Register(21, 101)); | |
| QUICHE_EXPECT_OK(scheduler.Register(30, 102)); | |
| EXPECT_THAT(scheduler.ShouldYield(10), IsOkAndHolds(false)); | |
| EXPECT_THAT(scheduler.ShouldYield(20), IsOkAndHolds(false)); | |
| EXPECT_THAT(scheduler.ShouldYield(21), IsOkAndHolds(false)); | |
| EXPECT_THAT(scheduler.ShouldYield(30), IsOkAndHolds(false)); | |
| EXPECT_THAT(scheduler.ShouldYield(40), StatusIs(absl::StatusCode::kNotFound)); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(20)); | |
| EXPECT_THAT(scheduler.ShouldYield(10), IsOkAndHolds(true)); | |
| EXPECT_THAT(scheduler.ShouldYield(20), IsOkAndHolds(false)); | |
| EXPECT_THAT(scheduler.ShouldYield(21), IsOkAndHolds(true)); | |
| EXPECT_THAT(scheduler.ShouldYield(30), IsOkAndHolds(false)); | |
| } | |
| TEST(BTreeSchedulerTest, Deschedule) { | |
| BTreeScheduler<int, int> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(10, 100)); | |
| QUICHE_EXPECT_OK(scheduler.Register(20, 101)); | |
| EXPECT_THAT(scheduler.Deschedule(10), | |
| StatusIs(absl::StatusCode::kFailedPrecondition)); | |
| EXPECT_THAT(scheduler.Deschedule(11), StatusIs(absl::StatusCode::kNotFound)); | |
| EXPECT_FALSE(scheduler.IsScheduled(10)); | |
| QUICHE_EXPECT_OK(scheduler.Schedule(10)); | |
| EXPECT_TRUE(scheduler.IsScheduled(10)); | |
| QUICHE_EXPECT_OK(scheduler.Deschedule(10)); | |
| EXPECT_FALSE(scheduler.IsScheduled(10)); | |
| QUICHE_EXPECT_OK(scheduler.Unregister(10)); | |
| } | |
| struct CustomPriority { | |
| int a; | |
| int b; | |
| bool operator<(const CustomPriority& other) const { | |
| return std::make_tuple(a, b) < std::make_tuple(other.a, other.b); | |
| } | |
| }; | |
| TEST(BTreeSchedulerTest, CustomPriority) { | |
| BTreeScheduler<int, CustomPriority> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(10, CustomPriority{0, 1})); | |
| QUICHE_EXPECT_OK(scheduler.Register(11, CustomPriority{0, 0})); | |
| QUICHE_EXPECT_OK(scheduler.Register(12, CustomPriority{0, 0})); | |
| QUICHE_EXPECT_OK(scheduler.Register(13, CustomPriority{10, 0})); | |
| QUICHE_EXPECT_OK(scheduler.Register(14, CustomPriority{-10, 0})); | |
| ScheduleIds(scheduler, {10, 11, 12, 13, 14}); | |
| EXPECT_THAT(PopAll(scheduler), ElementsAre(13, 10, 11, 12, 14)); | |
| } | |
| struct CustomId { | |
| int a; | |
| std::string b; | |
| bool operator==(const CustomId& other) const { | |
| return a == other.a && b == other.b; | |
| } | |
| template <typename H> | |
| friend H AbslHashValue(H h, const CustomId& c) { | |
| return H::combine(std::move(h), c.a, c.b); | |
| } | |
| }; | |
| std::ostream& operator<<(std::ostream& os, const CustomId& id) { | |
| os << id.a << ":" << id.b; | |
| return os; | |
| } | |
| TEST(BTreeSchedulerTest, CustomIds) { | |
| BTreeScheduler<CustomId, int> scheduler; | |
| QUICHE_EXPECT_OK(scheduler.Register(CustomId{1, "foo"}, 10)); | |
| QUICHE_EXPECT_OK(scheduler.Register(CustomId{1, "bar"}, 12)); | |
| QUICHE_EXPECT_OK(scheduler.Register(CustomId{2, "foo"}, 11)); | |
| EXPECT_THAT(scheduler.Register(CustomId{1, "foo"}, 10), | |
| StatusIs(absl::StatusCode::kAlreadyExists)); | |
| ScheduleIds(scheduler, | |
| {CustomId{1, "foo"}, CustomId{1, "bar"}, CustomId{2, "foo"}}); | |
| EXPECT_THAT(scheduler.ShouldYield(CustomId{1, "foo"}), IsOkAndHolds(true)); | |
| EXPECT_THAT(scheduler.ShouldYield(CustomId{1, "bar"}), IsOkAndHolds(false)); | |
| EXPECT_THAT( | |
| PopAll(scheduler), | |
| ElementsAre(CustomId{1, "bar"}, CustomId{2, "foo"}, CustomId{1, "foo"})); | |
| } | |
| } // namespace | |
| } // namespace quiche::test |