// Copyright 2023 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_bluetooth_sapphire/internal/host/common/retire_log.h" #include #include #include #include #include namespace bt::internal { namespace { // Retire 101 entries where the fields in the range [starting_value, // starting_value + 100] void Retire101Elements(RetireLog& retire_log, int starting_value = 0) { std::vector values(101); std::iota(values.begin(), values.end(), starting_value); constexpr int kSeed = 4; std::shuffle(values.begin(), values.end(), std::default_random_engine(kSeed)); for (auto value : values) { retire_log.Retire(value, std::chrono::milliseconds(value)); } } TEST(RetireLogDeathTest, InitializationLimits) { ASSERT_DEATH_IF_SUPPORTED({ RetireLog retire_log(0, 100); }, "min_depth"); ASSERT_DEATH_IF_SUPPORTED( { RetireLog retire_log(101, 100); }, "min_depth.*max_depth"); } TEST(RetireLogDeathTest, ComputeQuantileLimits) { RetireLog retire_log(1, 100); retire_log.Retire(1, std::chrono::seconds(1)); ASSERT_DEATH_IF_SUPPORTED( { [[maybe_unused]] auto _ = retire_log.ComputeByteCountQuantiles(std::array{-1.}); }, "0"); ASSERT_DEATH_IF_SUPPORTED( { [[maybe_unused]] auto _ = retire_log.ComputeByteCountQuantiles(std::array{2.}); }, "1"); } TEST(RetireLogTest, QuantileCallBeforeRetiringYieldsNothing) { RetireLog retire_log(1, 100); EXPECT_EQ(0U, retire_log.depth()); auto result = retire_log.ComputeAgeQuantiles(std::array{.5}); EXPECT_FALSE(result.has_value()); } TEST(RetireLogTest, QuantileCallsAfterDepthOneYieldsTheResult) { RetireLog retire_log(1, 100); constexpr pw::chrono::SystemClock::duration kTestDuration = std::chrono::milliseconds(10); retire_log.Retire(0, kTestDuration); auto result = retire_log.ComputeAgeQuantiles(std::array{.5}); ASSERT_TRUE(result.has_value()); EXPECT_THAT(*result, testing::Each(testing::Eq(kTestDuration))); } TEST(RetireLogTest, ComputeQuantiles) { RetireLog retire_log(1, 101); Retire101Elements(retire_log); auto result = retire_log.ComputeByteCountQuantiles( std::array{0., .001, .5, .754, .99, 1.}); ASSERT_TRUE(result.has_value()); // Cutting at extremes yields the min and max entries while cutting in the // middle yields the median. Cutting between entry values yields the nearest // (by distribution) logged value. EXPECT_THAT(*result, testing::ElementsAre(0, 0, 50, 76, 99, 100)); } TEST(RetireLogTest, ComputeQuantilesExactBoundaryIsHighBiased) { RetireLog retire_log(2, 2); retire_log.Retire(0, {}); retire_log.Retire(1, {}); auto result = retire_log.ComputeByteCountQuantiles(std::array{.5}); ASSERT_TRUE(result.has_value()); // Cutting at exactly between two entries yields the higher sample EXPECT_THAT(*result, testing::ElementsAre(1)); } TEST(RetireLogTest, ComputeQuantilesOutOfOrderPartitions) { RetireLog retire_log(1, 101); Retire101Elements(retire_log); auto result = retire_log.ComputeByteCountQuantiles(std::array{.75, .25, .5}); ASSERT_TRUE(result.has_value()); EXPECT_THAT(*result, testing::ElementsAre(75, 25, 50)); } // Check that cutting at the same point more than once works TEST(RetireLogTest, ComputeSameQuantileTwice) { RetireLog retire_log(1, 101); Retire101Elements(retire_log); auto result = retire_log.ComputeByteCountQuantiles(std::array{0., 0., 1., 1.}); ASSERT_TRUE(result.has_value()); EXPECT_THAT(*result, testing::ElementsAre(0, 0, 100, 100)); } TEST(RetireLogTest, RetiringPastMaxDepthReplacesPreviousEntries) { RetireLog retire_log(1, 101); Retire101Elements(retire_log, /*starting_value=*/0); Retire101Elements(retire_log, /*starting_value=*/10); EXPECT_EQ(101U, retire_log.depth()); auto result = retire_log.ComputeByteCountQuantiles(std::array{0., .5, 1.}); ASSERT_TRUE(result.has_value()); EXPECT_THAT(*result, testing::ElementsAre(10, 60, 110)); } } // namespace } // namespace bt::internal