diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding_unittest.cc | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding_unittest.cc')
-rw-r--r-- | third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding_unittest.cc | 693 |
1 files changed, 693 insertions, 0 deletions
diff --git a/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding_unittest.cc b/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding_unittest.cc new file mode 100644 index 0000000000..d0f7fb93db --- /dev/null +++ b/third_party/libwebrtc/logging/rtc_event_log/encoder/delta_encoding_unittest.cc @@ -0,0 +1,693 @@ +/* + * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "logging/rtc_event_log/encoder/delta_encoding.h" + +#include <algorithm> +#include <limits> +#include <numeric> +#include <string> +#include <tuple> +#include <vector> + +#include "absl/types/optional.h" +#include "rtc_base/arraysize.h" +#include "rtc_base/checks.h" +#include "rtc_base/random.h" +#include "test/gtest.h" + +namespace webrtc { + +void SetFixedLengthEncoderDeltaSignednessForTesting(bool signedness); +void UnsetFixedLengthEncoderDeltaSignednessForTesting(); + +namespace { + +enum class DeltaSignedness { kNoOverride, kForceUnsigned, kForceSigned }; + +void MaybeSetSignedness(DeltaSignedness signedness) { + switch (signedness) { + case DeltaSignedness::kNoOverride: + UnsetFixedLengthEncoderDeltaSignednessForTesting(); + return; + case DeltaSignedness::kForceUnsigned: + SetFixedLengthEncoderDeltaSignednessForTesting(false); + return; + case DeltaSignedness::kForceSigned: + SetFixedLengthEncoderDeltaSignednessForTesting(true); + return; + } + RTC_DCHECK_NOTREACHED(); +} + +uint64_t RandomWithMaxBitWidth(Random* prng, uint64_t max_width) { + RTC_DCHECK_GE(max_width, 1u); + RTC_DCHECK_LE(max_width, 64u); + + const uint64_t low = prng->Rand(std::numeric_limits<uint32_t>::max()); + const uint64_t high = + max_width > 32u ? prng->Rand(std::numeric_limits<uint32_t>::max()) : 0u; + + const uint64_t random_before_mask = (high << 32) | low; + + if (max_width < 64) { + return random_before_mask & ((static_cast<uint64_t>(1) << max_width) - 1); + } else { + return random_before_mask; + } +} + +// Encodes `values` based on `base`, then decodes the result and makes sure +// that it is equal to the original input. +// If `encoded_string` is non-null, the encoded result will also be written +// into it. +void TestEncodingAndDecoding( + absl::optional<uint64_t> base, + const std::vector<absl::optional<uint64_t>>& values, + std::string* encoded_string = nullptr) { + const std::string encoded = EncodeDeltas(base, values); + if (encoded_string) { + *encoded_string = encoded; + } + + const std::vector<absl::optional<uint64_t>> decoded = + DecodeDeltas(encoded, base, values.size()); + + EXPECT_EQ(decoded, values); +} + +std::vector<absl::optional<uint64_t>> CreateSequenceByFirstValue( + uint64_t first, + size_t sequence_length) { + std::vector<absl::optional<uint64_t>> sequence(sequence_length); + std::iota(sequence.begin(), sequence.end(), first); + return sequence; +} + +std::vector<absl::optional<uint64_t>> CreateSequenceByLastValue( + uint64_t last, + size_t num_values) { + const uint64_t first = last - num_values + 1; + std::vector<absl::optional<uint64_t>> result(num_values); + std::iota(result.begin(), result.end(), first); + return result; +} + +// If `sequence_length` is greater than the number of deltas, the sequence of +// deltas will wrap around. +std::vector<absl::optional<uint64_t>> CreateSequenceByOptionalDeltas( + uint64_t first, + const std::vector<absl::optional<uint64_t>>& deltas, + size_t sequence_length) { + RTC_DCHECK_GE(sequence_length, 1); + + std::vector<absl::optional<uint64_t>> sequence(sequence_length); + + uint64_t previous = first; + for (size_t i = 0, next_delta_index = 0; i < sequence.size(); ++i) { + if (deltas[next_delta_index].has_value()) { + sequence[i] = + absl::optional<uint64_t>(previous + deltas[next_delta_index].value()); + previous = sequence[i].value(); + } + next_delta_index = (next_delta_index + 1) % deltas.size(); + } + + return sequence; +} + +size_t EncodingLengthUpperBound(size_t delta_max_bit_width, + size_t num_of_deltas, + DeltaSignedness signedness_override) { + absl::optional<size_t> smallest_header_size_bytes; + switch (signedness_override) { + case DeltaSignedness::kNoOverride: + case DeltaSignedness::kForceUnsigned: + smallest_header_size_bytes = 1; + break; + case DeltaSignedness::kForceSigned: + smallest_header_size_bytes = 2; + break; + } + RTC_DCHECK(smallest_header_size_bytes); + + return delta_max_bit_width * num_of_deltas + *smallest_header_size_bytes; +} + +// If `sequence_length` is greater than the number of deltas, the sequence of +// deltas will wrap around. +std::vector<absl::optional<uint64_t>> CreateSequenceByDeltas( + uint64_t first, + const std::vector<uint64_t>& deltas, + size_t sequence_length) { + RTC_DCHECK(!deltas.empty()); + std::vector<absl::optional<uint64_t>> optional_deltas(deltas.size()); + for (size_t i = 0; i < deltas.size(); ++i) { + optional_deltas[i] = absl::optional<uint64_t>(deltas[i]); + } + return CreateSequenceByOptionalDeltas(first, optional_deltas, + sequence_length); +} + +// Tests of the delta encoding, parameterized by the number of values +// in the sequence created by the test. +class DeltaEncodingTest + : public ::testing::TestWithParam< + std::tuple<DeltaSignedness, size_t, bool, uint64_t>> { + public: + DeltaEncodingTest() + : signedness_(std::get<0>(GetParam())), + num_of_values_(std::get<1>(GetParam())), + optional_values_(std::get<2>(GetParam())), + partial_random_seed_(std::get<3>(GetParam())) { + MaybeSetSignedness(signedness_); + } + + ~DeltaEncodingTest() override = default; + + // Running with the same seed for all variants would make all tests start + // with the same sequence; avoid this by making the seed different. + uint64_t Seed() const { + // Multiply everything but by different primes to produce unique results. + return 2 * static_cast<uint64_t>(signedness_) + 3 * num_of_values_ + + 5 * optional_values_ + 7 * partial_random_seed_; + } + + const DeltaSignedness signedness_; + const uint64_t num_of_values_; + const bool optional_values_; + const uint64_t partial_random_seed_; // Explained where it's used. +}; + +TEST_P(DeltaEncodingTest, AllValuesEqualToExistentBaseValue) { + const absl::optional<uint64_t> base(3432); + std::vector<absl::optional<uint64_t>> values(num_of_values_); + std::fill(values.begin(), values.end(), base); + std::string encoded; + TestEncodingAndDecoding(base, values, &encoded); + + // Additional requirement - the encoding should be efficient in this + // case - the empty string will be used. + EXPECT_TRUE(encoded.empty()); +} + +TEST_P(DeltaEncodingTest, AllValuesEqualToNonExistentBaseValue) { + if (!optional_values_) { + return; // Test irrelevant for this case. + } + + const absl::optional<uint64_t> base; + std::vector<absl::optional<uint64_t>> values(num_of_values_); + std::fill(values.begin(), values.end(), base); + std::string encoded; + TestEncodingAndDecoding(base, values, &encoded); + + // Additional requirement - the encoding should be efficient in this + // case - the empty string will be used. + EXPECT_TRUE(encoded.empty()); +} + +TEST_P(DeltaEncodingTest, BaseNonExistentButSomeOtherValuesExist) { + if (!optional_values_) { + return; // Test irrelevant for this case. + } + + const absl::optional<uint64_t> base; + std::vector<absl::optional<uint64_t>> values(num_of_values_); + + Random prng(Seed()); + + const uint64_t max_bit_width = 1 + prng.Rand(63); // [1, 64] + + for (size_t i = 0; i < values.size();) { + // Leave a random number of values as non-existent. + const size_t non_existent_count = prng.Rand(values.size() - i - 1); + i += non_existent_count; + + // Assign random values to a random number of values. (At least one, to + // prevent this iteration of the outer loop from being a no-op.) + const size_t existent_count = + std::max<size_t>(prng.Rand(values.size() - i - 1), 1); + for (size_t j = 0; j < existent_count; ++j) { + values[i + j] = RandomWithMaxBitWidth(&prng, max_bit_width); + } + i += existent_count; + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, MinDeltaNoWrapAround) { + const absl::optional<uint64_t> base(3432); + + auto values = CreateSequenceByFirstValue(base.value() + 1, num_of_values_); + ASSERT_GT(values[values.size() - 1], base) << "Sanity; must not wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[0] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, BigDeltaNoWrapAround) { + const uint64_t kBigDelta = 132828; + const absl::optional<uint64_t> base(3432); + + auto values = + CreateSequenceByFirstValue(base.value() + kBigDelta, num_of_values_); + ASSERT_GT(values[values.size() - 1], base) << "Sanity; must not wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[0] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, MaxDeltaNoWrapAround) { + const absl::optional<uint64_t> base(3432); + + auto values = CreateSequenceByLastValue(std::numeric_limits<uint64_t>::max(), + num_of_values_); + ASSERT_GT(values[values.size() - 1], base) << "Sanity; must not wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[0] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, SmallDeltaWithWrapAroundComparedToBase) { + if (optional_values_ && num_of_values_ == 1) { + return; // Inapplicable + } + + const absl::optional<uint64_t> base(std::numeric_limits<uint64_t>::max()); + + auto values = CreateSequenceByDeltas(*base, {1, 10, 3}, num_of_values_); + ASSERT_LT(values[0], base) << "Sanity; must wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, SmallDeltaWithWrapAroundInValueSequence) { + if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { + return; // Inapplicable. + } + + const absl::optional<uint64_t> base(std::numeric_limits<uint64_t>::max() - 2); + + auto values = CreateSequenceByDeltas(*base, {1, 10, 3}, num_of_values_); + ASSERT_LT(values[values.size() - 1], values[0]) << "Sanity; must wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + RTC_DCHECK_GT(values.size() - 1, 1u); // Wrap around not cancelled. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +// Suppress "integral constant overflow" warning; this is the test's focus. +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4307) +#endif +TEST_P(DeltaEncodingTest, BigDeltaWithWrapAroundComparedToBase) { + if (optional_values_ && num_of_values_ == 1) { + return; // Inapplicable + } + + const uint64_t kBigDelta = 132828; + const absl::optional<uint64_t> base(std::numeric_limits<uint64_t>::max() - + kBigDelta + 3); + + auto values = + CreateSequenceByFirstValue(base.value() + kBigDelta, num_of_values_); + ASSERT_LT(values[0], base.value()) << "Sanity; must wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, BigDeltaWithWrapAroundInValueSequence) { + if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { + return; // Inapplicable. + } + + const uint64_t kBigDelta = 132828; + const absl::optional<uint64_t> base(std::numeric_limits<uint64_t>::max() - + kBigDelta + 3); + + auto values = CreateSequenceByFirstValue(std::numeric_limits<uint64_t>::max(), + num_of_values_); + ASSERT_LT(values[values.size() - 1], values[0]) << "Sanity; must wrap around"; + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + RTC_DCHECK_GT(values.size() - 1, 1u); // Wrap around not cancelled. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +TEST_P(DeltaEncodingTest, MaxDeltaWithWrapAroundComparedToBase) { + if (optional_values_ && num_of_values_ == 1) { + return; // Inapplicable + } + + const absl::optional<uint64_t> base(3432); + auto values = CreateSequenceByFirstValue(*base - 1, num_of_values_); + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +TEST_P(DeltaEncodingTest, MaxDeltaWithWrapAroundInValueSequence) { + if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { + return; // Inapplicable. + } + + const absl::optional<uint64_t> base(3432); + + auto values = CreateSequenceByDeltas( + *base, {0, std::numeric_limits<uint64_t>::max(), 3}, num_of_values_); + // Wraps around continuously by virtue of being max(); will not ASSERT. + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + RTC_DCHECK_GT(values.size() - 1, 1u); // Wrap around not cancelled. + values[1] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +// If num_of_values_ == 1, a zero delta will yield an empty string; that's +// already covered by AllValuesEqualToExistentBaseValue, but it doesn't hurt to +// test again. For all other cases, we have a new test. +TEST_P(DeltaEncodingTest, ZeroDelta) { + const absl::optional<uint64_t> base(3432); + + // Arbitrary sequence of deltas with intentional zero deltas, as well as + // consecutive zeros. + const std::vector<uint64_t> deltas = {0, 312, 11, 1, 1, 0, 0, 12, + 400321, 3, 3, 12, 5, 0, 6}; + auto values = CreateSequenceByDeltas(base.value(), deltas, num_of_values_); + + if (optional_values_) { + // Arbitrarily make one of the values non-existent, to force + // optional-supporting encoding. + values[0] = absl::optional<uint64_t>(); + } + + TestEncodingAndDecoding(base, values); +} + +INSTANTIATE_TEST_SUITE_P( + SignednessOverrideAndNumberOfValuesInSequence, + DeltaEncodingTest, + ::testing::Combine(::testing::Values(DeltaSignedness::kNoOverride, + DeltaSignedness::kForceUnsigned, + DeltaSignedness::kForceSigned), + ::testing::Values(1, 2, 100, 10000), + ::testing::Bool(), + ::testing::Values(10, 20, 30))); + +// Tests over the quality of the compression (as opposed to its correctness). +// Not to be confused with tests of runtime efficiency. +class DeltaEncodingCompressionQualityTest + : public ::testing::TestWithParam< + std::tuple<DeltaSignedness, uint64_t, uint64_t, uint64_t>> { + public: + DeltaEncodingCompressionQualityTest() + : signedness_(std::get<0>(GetParam())), + delta_max_bit_width_(std::get<1>(GetParam())), + num_of_values_(std::get<2>(GetParam())), + partial_random_seed_(std::get<3>(GetParam())) { + MaybeSetSignedness(signedness_); + } + + ~DeltaEncodingCompressionQualityTest() override = default; + + // Running with the same seed for all variants would make all tests start + // with the same sequence; avoid this by making the seed different. + uint64_t Seed() const { + // Multiply everything but by different primes to produce unique results. + return 2 * static_cast<uint64_t>(signedness_) + 3 * delta_max_bit_width_ + + 5 * delta_max_bit_width_ + 7 * num_of_values_ + + 11 * partial_random_seed_; + } + + const DeltaSignedness signedness_; + const uint64_t delta_max_bit_width_; + const uint64_t num_of_values_; + const uint64_t partial_random_seed_; // Explained where it's used. +}; + +// If no wrap-around occurs in the stream, the width of the values does not +// matter to compression performance; only the deltas matter. +TEST_P(DeltaEncodingCompressionQualityTest, + BaseDoesNotAffectEfficiencyIfNoWrapAround) { + // 1. Bases which will not produce a wrap-around. + // 2. The last base - 0xffffffffffffffff - does cause a wrap-around, but + // that still works, because the width is 64 anyway, and does not + // need to be conveyed explicitly in the encoding header. + const uint64_t bases[] = {0, 0x55, 0xffffffff, + std::numeric_limits<uint64_t>::max()}; + const size_t kIntendedWrapAroundBaseIndex = arraysize(bases); + + std::vector<uint64_t> deltas(num_of_values_); + + // Allows us to make sure that the deltas do not produce a wrap-around. + uint64_t last_element[arraysize(bases)]; + memcpy(last_element, bases, sizeof(bases)); + + // Avoid empty `deltas` due to first element causing wrap-around. + deltas[0] = 1; + for (size_t i = 0; i < arraysize(last_element); ++i) { + last_element[i] += 1; + } + + Random prng(Seed()); + + for (size_t i = 1; i < deltas.size(); ++i) { + const uint64_t delta = RandomWithMaxBitWidth(&prng, delta_max_bit_width_); + + bool wrap_around = false; + for (size_t j = 0; j < arraysize(last_element); ++j) { + if (j == kIntendedWrapAroundBaseIndex) { + continue; + } + + last_element[j] += delta; + if (last_element[j] < bases[j]) { + wrap_around = true; + break; + } + } + + if (wrap_around) { + deltas.resize(i); + break; + } + + deltas[i] = delta; + } + + std::string encodings[arraysize(bases)]; + + for (size_t i = 0; i < arraysize(bases); ++i) { + const auto values = + CreateSequenceByDeltas(bases[i], deltas, num_of_values_); + // Produce the encoding and write it to encodings[i]. + // By using TestEncodingAndDecoding() to do this, we also sanity-test + // the encoding/decoding, though that is not the test's focus. + TestEncodingAndDecoding(bases[i], values, &encodings[i]); + EXPECT_LE(encodings[i].length(), + EncodingLengthUpperBound(delta_max_bit_width_, num_of_values_, + signedness_)); + } + + // Test focus - all of the encodings should be the same, as they are based + // on the same delta sequence, and do not contain a wrap-around. + for (size_t i = 1; i < arraysize(encodings); ++i) { + EXPECT_EQ(encodings[i], encodings[0]); + } +} + +INSTANTIATE_TEST_SUITE_P( + SignednessOverrideAndDeltaMaxBitWidthAndNumberOfValuesInSequence, + DeltaEncodingCompressionQualityTest, + ::testing::Combine( + ::testing::Values(DeltaSignedness::kNoOverride, + DeltaSignedness::kForceUnsigned, + DeltaSignedness::kForceSigned), + ::testing::Values(1, 4, 8, 15, 16, 17, 31, 32, 33, 63, 64), + ::testing::Values(1, 2, 100, 10000), + ::testing::Values(11, 12, 13))); + +// Similar to DeltaEncodingTest, but instead of semi-surgically producing +// specific cases, produce large amount of semi-realistic inputs. +class DeltaEncodingFuzzerLikeTest + : public ::testing::TestWithParam< + std::tuple<DeltaSignedness, uint64_t, uint64_t, bool, uint64_t>> { + public: + DeltaEncodingFuzzerLikeTest() + : signedness_(std::get<0>(GetParam())), + delta_max_bit_width_(std::get<1>(GetParam())), + num_of_values_(std::get<2>(GetParam())), + optional_values_(std::get<3>(GetParam())), + partial_random_seed_(std::get<4>(GetParam())) { + MaybeSetSignedness(signedness_); + } + + ~DeltaEncodingFuzzerLikeTest() override = default; + + // Running with the same seed for all variants would make all tests start + // with the same sequence; avoid this by making the seed different. + uint64_t Seed() const { + // Multiply everything but by different primes to produce unique results. + return 2 * static_cast<uint64_t>(signedness_) + 3 * delta_max_bit_width_ + + 5 * delta_max_bit_width_ + 7 * num_of_values_ + + 11 * static_cast<uint64_t>(optional_values_) + + 13 * partial_random_seed_; + } + + const DeltaSignedness signedness_; + const uint64_t delta_max_bit_width_; + const uint64_t num_of_values_; + const bool optional_values_; + const uint64_t partial_random_seed_; // Explained where it's used. +}; + +TEST_P(DeltaEncodingFuzzerLikeTest, Test) { + const absl::optional<uint64_t> base(3432); + + Random prng(Seed()); + std::vector<absl::optional<uint64_t>> deltas(num_of_values_); + for (size_t i = 0; i < deltas.size(); ++i) { + if (!optional_values_ || prng.Rand<bool>()) { + deltas[i] = RandomWithMaxBitWidth(&prng, delta_max_bit_width_); + } + } + const auto values = + CreateSequenceByOptionalDeltas(base.value(), deltas, num_of_values_); + + TestEncodingAndDecoding(base, values); +} + +INSTANTIATE_TEST_SUITE_P( + SignednessOverrideAndDeltaMaxBitWidthAndNumberOfValuesInSequence, + DeltaEncodingFuzzerLikeTest, + ::testing::Combine( + ::testing::Values(DeltaSignedness::kNoOverride, + DeltaSignedness::kForceUnsigned, + DeltaSignedness::kForceSigned), + ::testing::Values(1, 4, 8, 15, 16, 17, 31, 32, 33, 63, 64), + ::testing::Values(1, 2, 100, 10000), + ::testing::Bool(), + ::testing::Values(21, 22, 23))); + +class DeltaEncodingSpecificEdgeCasesTest + : public ::testing::TestWithParam< + std::tuple<DeltaSignedness, uint64_t, bool>> { + public: + DeltaEncodingSpecificEdgeCasesTest() { + UnsetFixedLengthEncoderDeltaSignednessForTesting(); + } + + ~DeltaEncodingSpecificEdgeCasesTest() override = default; +}; + +// This case is special because it produces identical forward/backward deltas. +TEST_F(DeltaEncodingSpecificEdgeCasesTest, SignedDeltaWithOnlyTopBitOn) { + MaybeSetSignedness(DeltaSignedness::kForceSigned); + + const absl::optional<uint64_t> base(3432); + + const uint64_t delta = static_cast<uint64_t>(1) << 63; + const std::vector<absl::optional<uint64_t>> values = {base.value() + delta}; + + TestEncodingAndDecoding(base, values); +} + +TEST_F(DeltaEncodingSpecificEdgeCasesTest, MaximumUnsignedDelta) { + MaybeSetSignedness(DeltaSignedness::kForceUnsigned); + + const absl::optional<uint64_t> base((static_cast<uint64_t>(1) << 63) + 0x123); + + const std::vector<absl::optional<uint64_t>> values = {base.value() - 1}; + + TestEncodingAndDecoding(base, values); +} + +// Check that, if all deltas are set to -1, things still work. +TEST_P(DeltaEncodingSpecificEdgeCasesTest, ReverseSequence) { + MaybeSetSignedness(std::get<0>(GetParam())); + const uint64_t width = std::get<1>(GetParam()); + const bool wrap_around = std::get<2>(GetParam()); + + const uint64_t value_mask = (width == 64) + ? std::numeric_limits<uint64_t>::max() + : ((static_cast<uint64_t>(1) << width) - 1); + + const uint64_t base = wrap_around ? 1u : (0xf82d3 & value_mask); + const std::vector<absl::optional<uint64_t>> values = { + (base - 1u) & value_mask, (base - 2u) & value_mask, + (base - 3u) & value_mask}; + + TestEncodingAndDecoding(base, values); +} + +INSTANTIATE_TEST_SUITE_P( + _, + DeltaEncodingSpecificEdgeCasesTest, + ::testing::Combine( + ::testing::Values(DeltaSignedness::kNoOverride, + DeltaSignedness::kForceUnsigned, + DeltaSignedness::kForceSigned), + ::testing::Values(1, 4, 8, 15, 16, 17, 31, 32, 33, 63, 64), + ::testing::Bool())); + +} // namespace +} // namespace webrtc |