delta_encoding_unittest.cc (23942B)
1 /* 2 * Copyright (c) 2018 The WebRTC project authors. All Rights Reserved. 3 * 4 * Use of this source code is governed by a BSD-style license 5 * that can be found in the LICENSE file in the root of the source 6 * tree. An additional intellectual property rights grant can be found 7 * in the file PATENTS. All contributing project authors may 8 * be found in the AUTHORS file in the root of the source tree. 9 */ 10 11 #include "logging/rtc_event_log/encoder/delta_encoding.h" 12 13 #include <algorithm> 14 #include <cstddef> 15 #include <cstdint> 16 #include <cstring> 17 #include <limits> 18 #include <numeric> 19 #include <optional> 20 #include <string> 21 #include <tuple> 22 #include <vector> 23 24 #include "rtc_base/checks.h" 25 #include "rtc_base/random.h" 26 #include "test/gmock.h" 27 #include "test/gtest.h" 28 29 namespace webrtc { 30 31 void SetFixedLengthEncoderDeltaSignednessForTesting(bool signedness); 32 void UnsetFixedLengthEncoderDeltaSignednessForTesting(); 33 34 namespace { 35 36 using ::testing::Each; 37 using ::testing::Eq; 38 39 enum class DeltaSignedness { kNoOverride, kForceUnsigned, kForceSigned }; 40 41 void MaybeSetSignedness(DeltaSignedness signedness) { 42 switch (signedness) { 43 case DeltaSignedness::kNoOverride: 44 UnsetFixedLengthEncoderDeltaSignednessForTesting(); 45 return; 46 case DeltaSignedness::kForceUnsigned: 47 SetFixedLengthEncoderDeltaSignednessForTesting(false); 48 return; 49 case DeltaSignedness::kForceSigned: 50 SetFixedLengthEncoderDeltaSignednessForTesting(true); 51 return; 52 } 53 RTC_DCHECK_NOTREACHED(); 54 } 55 56 uint64_t RandomWithMaxBitWidth(Random* prng, uint64_t max_width) { 57 RTC_DCHECK_GE(max_width, 1u); 58 RTC_DCHECK_LE(max_width, 64u); 59 60 const uint64_t low = prng->Rand(std::numeric_limits<uint32_t>::max()); 61 const uint64_t high = 62 max_width > 32u ? prng->Rand(std::numeric_limits<uint32_t>::max()) : 0u; 63 64 const uint64_t random_before_mask = (high << 32) | low; 65 66 if (max_width < 64) { 67 return random_before_mask & ((static_cast<uint64_t>(1) << max_width) - 1); 68 } else { 69 return random_before_mask; 70 } 71 } 72 73 // Encodes `values` based on `base`, then decodes the result and makes sure 74 // that it is equal to the original input. 75 // If `encoded_string` is non-null, the encoded result will also be written 76 // into it. 77 void TestEncodingAndDecoding(std::optional<uint64_t> base, 78 const std::vector<std::optional<uint64_t>>& values, 79 std::string* encoded_string = nullptr) { 80 const std::string encoded = EncodeDeltas(base, values); 81 if (encoded_string) { 82 *encoded_string = encoded; 83 } 84 85 const std::vector<std::optional<uint64_t>> decoded = 86 DecodeDeltas(encoded, base, values.size()); 87 88 EXPECT_EQ(decoded, values); 89 } 90 91 std::vector<std::optional<uint64_t>> CreateSequenceByFirstValue( 92 uint64_t first, 93 size_t sequence_length) { 94 std::vector<std::optional<uint64_t>> sequence(sequence_length); 95 std::iota(sequence.begin(), sequence.end(), first); 96 return sequence; 97 } 98 99 std::vector<std::optional<uint64_t>> CreateSequenceByLastValue( 100 uint64_t last, 101 size_t num_values) { 102 const uint64_t first = last - num_values + 1; 103 std::vector<std::optional<uint64_t>> result(num_values); 104 std::iota(result.begin(), result.end(), first); 105 return result; 106 } 107 108 // If `sequence_length` is greater than the number of deltas, the sequence of 109 // deltas will wrap around. 110 std::vector<std::optional<uint64_t>> CreateSequenceByOptionalDeltas( 111 uint64_t first, 112 const std::vector<std::optional<uint64_t>>& deltas, 113 size_t sequence_length) { 114 RTC_DCHECK_GE(sequence_length, 1); 115 116 std::vector<std::optional<uint64_t>> sequence(sequence_length); 117 118 uint64_t previous = first; 119 for (size_t i = 0, next_delta_index = 0; i < sequence.size(); ++i) { 120 if (deltas[next_delta_index].has_value()) { 121 sequence[i] = 122 std::optional<uint64_t>(previous + deltas[next_delta_index].value()); 123 previous = sequence[i].value(); 124 } 125 next_delta_index = (next_delta_index + 1) % deltas.size(); 126 } 127 128 return sequence; 129 } 130 131 size_t EncodingLengthUpperBound(size_t delta_max_bit_width, 132 size_t num_of_deltas, 133 DeltaSignedness signedness_override) { 134 std::optional<size_t> smallest_header_size_bytes; 135 switch (signedness_override) { 136 case DeltaSignedness::kNoOverride: 137 case DeltaSignedness::kForceUnsigned: 138 smallest_header_size_bytes = 1; 139 break; 140 case DeltaSignedness::kForceSigned: 141 smallest_header_size_bytes = 2; 142 break; 143 } 144 RTC_DCHECK(smallest_header_size_bytes); 145 146 return delta_max_bit_width * num_of_deltas + *smallest_header_size_bytes; 147 } 148 149 // If `sequence_length` is greater than the number of deltas, the sequence of 150 // deltas will wrap around. 151 std::vector<std::optional<uint64_t>> CreateSequenceByDeltas( 152 uint64_t first, 153 const std::vector<uint64_t>& deltas, 154 size_t sequence_length) { 155 RTC_DCHECK(!deltas.empty()); 156 std::vector<std::optional<uint64_t>> optional_deltas(deltas.size()); 157 for (size_t i = 0; i < deltas.size(); ++i) { 158 optional_deltas[i] = std::optional<uint64_t>(deltas[i]); 159 } 160 return CreateSequenceByOptionalDeltas(first, optional_deltas, 161 sequence_length); 162 } 163 164 // Tests of the delta encoding, parameterized by the number of values 165 // in the sequence created by the test. 166 class DeltaEncodingTest 167 : public ::testing::TestWithParam< 168 std::tuple<DeltaSignedness, size_t, bool, uint64_t>> { 169 public: 170 DeltaEncodingTest() 171 : signedness_(std::get<0>(GetParam())), 172 num_of_values_(std::get<1>(GetParam())), 173 optional_values_(std::get<2>(GetParam())), 174 partial_random_seed_(std::get<3>(GetParam())) { 175 MaybeSetSignedness(signedness_); 176 } 177 178 ~DeltaEncodingTest() override = default; 179 180 // Running with the same seed for all variants would make all tests start 181 // with the same sequence; avoid this by making the seed different. 182 uint64_t Seed() const { 183 // Multiply everything but by different primes to produce unique results. 184 return 2 * static_cast<uint64_t>(signedness_) + 3 * num_of_values_ + 185 5 * optional_values_ + 7 * partial_random_seed_; 186 } 187 188 const DeltaSignedness signedness_; 189 const uint64_t num_of_values_; 190 const bool optional_values_; 191 const uint64_t partial_random_seed_; // Explained where it's used. 192 }; 193 194 TEST_P(DeltaEncodingTest, AllValuesEqualToExistentBaseValue) { 195 const std::optional<uint64_t> base(3432); 196 std::vector<std::optional<uint64_t>> values(num_of_values_); 197 std::fill(values.begin(), values.end(), base); 198 std::string encoded; 199 TestEncodingAndDecoding(base, values, &encoded); 200 201 // Additional requirement - the encoding should be efficient in this 202 // case - the empty string will be used. 203 EXPECT_TRUE(encoded.empty()); 204 } 205 206 TEST_P(DeltaEncodingTest, AllValuesEqualToNonExistentBaseValue) { 207 if (!optional_values_) { 208 return; // Test irrelevant for this case. 209 } 210 211 const std::optional<uint64_t> base; 212 std::vector<std::optional<uint64_t>> values(num_of_values_); 213 std::fill(values.begin(), values.end(), base); 214 std::string encoded; 215 TestEncodingAndDecoding(base, values, &encoded); 216 217 // Additional requirement - the encoding should be efficient in this 218 // case - the empty string will be used. 219 EXPECT_TRUE(encoded.empty()); 220 } 221 222 TEST_P(DeltaEncodingTest, BaseNonExistentButSomeOtherValuesExist) { 223 if (!optional_values_) { 224 return; // Test irrelevant for this case. 225 } 226 227 const std::optional<uint64_t> base; 228 std::vector<std::optional<uint64_t>> values(num_of_values_); 229 230 Random prng(Seed()); 231 232 const uint64_t max_bit_width = 1 + prng.Rand(63); // [1, 64] 233 234 for (size_t i = 0; i < values.size();) { 235 // Leave a random number of values as non-existent. 236 const size_t non_existent_count = prng.Rand(values.size() - i - 1); 237 i += non_existent_count; 238 239 // Assign random values to a random number of values. (At least one, to 240 // prevent this iteration of the outer loop from being a no-op.) 241 const size_t existent_count = 242 std::max<size_t>(prng.Rand(values.size() - i - 1), 1); 243 for (size_t j = 0; j < existent_count; ++j) { 244 values[i + j] = RandomWithMaxBitWidth(&prng, max_bit_width); 245 } 246 i += existent_count; 247 } 248 249 TestEncodingAndDecoding(base, values); 250 } 251 252 TEST_P(DeltaEncodingTest, MinDeltaNoWrapAround) { 253 const std::optional<uint64_t> base(3432); 254 255 auto values = CreateSequenceByFirstValue(base.value() + 1, num_of_values_); 256 ASSERT_GT(values[values.size() - 1], base) << "Sanity; must not wrap around"; 257 258 if (optional_values_) { 259 // Arbitrarily make one of the values non-existent, to force 260 // optional-supporting encoding. 261 values[0] = std::optional<uint64_t>(); 262 } 263 264 TestEncodingAndDecoding(base, values); 265 } 266 267 TEST_P(DeltaEncodingTest, BigDeltaNoWrapAround) { 268 const uint64_t kBigDelta = 132828; 269 const std::optional<uint64_t> base(3432); 270 271 auto values = 272 CreateSequenceByFirstValue(base.value() + kBigDelta, num_of_values_); 273 ASSERT_GT(values[values.size() - 1], base) << "Sanity; must not wrap around"; 274 275 if (optional_values_) { 276 // Arbitrarily make one of the values non-existent, to force 277 // optional-supporting encoding. 278 values[0] = std::optional<uint64_t>(); 279 } 280 281 TestEncodingAndDecoding(base, values); 282 } 283 284 TEST_P(DeltaEncodingTest, MaxDeltaNoWrapAround) { 285 const std::optional<uint64_t> base(3432); 286 287 auto values = CreateSequenceByLastValue(std::numeric_limits<uint64_t>::max(), 288 num_of_values_); 289 ASSERT_GT(values[values.size() - 1], base) << "Sanity; must not wrap around"; 290 291 if (optional_values_) { 292 // Arbitrarily make one of the values non-existent, to force 293 // optional-supporting encoding. 294 values[0] = std::optional<uint64_t>(); 295 } 296 297 TestEncodingAndDecoding(base, values); 298 } 299 300 TEST_P(DeltaEncodingTest, SmallDeltaWithWrapAroundComparedToBase) { 301 if (optional_values_ && num_of_values_ == 1) { 302 return; // Inapplicable 303 } 304 305 const std::optional<uint64_t> base(std::numeric_limits<uint64_t>::max()); 306 307 auto values = CreateSequenceByDeltas(*base, {1, 10, 3}, num_of_values_); 308 ASSERT_LT(values[0], base) << "Sanity; must wrap around"; 309 310 if (optional_values_) { 311 // Arbitrarily make one of the values non-existent, to force 312 // optional-supporting encoding. 313 values[1] = std::optional<uint64_t>(); 314 } 315 316 TestEncodingAndDecoding(base, values); 317 } 318 319 TEST_P(DeltaEncodingTest, SmallDeltaWithWrapAroundInValueSequence) { 320 if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { 321 return; // Inapplicable. 322 } 323 324 const std::optional<uint64_t> base(std::numeric_limits<uint64_t>::max() - 2); 325 326 auto values = CreateSequenceByDeltas(*base, {1, 10, 3}, num_of_values_); 327 ASSERT_LT(values[values.size() - 1], values[0]) << "Sanity; must wrap around"; 328 329 if (optional_values_) { 330 // Arbitrarily make one of the values non-existent, to force 331 // optional-supporting encoding. 332 RTC_DCHECK_GT(values.size() - 1, 1u); // Wrap around not cancelled. 333 values[1] = std::optional<uint64_t>(); 334 } 335 336 TestEncodingAndDecoding(base, values); 337 } 338 339 // Suppress "integral constant overflow" warning; this is the test's focus. 340 #ifdef _MSC_VER 341 #pragma warning(push) 342 #pragma warning(disable : 4307) 343 #endif 344 TEST_P(DeltaEncodingTest, BigDeltaWithWrapAroundComparedToBase) { 345 if (optional_values_ && num_of_values_ == 1) { 346 return; // Inapplicable 347 } 348 349 const uint64_t kBigDelta = 132828; 350 const std::optional<uint64_t> base(std::numeric_limits<uint64_t>::max() - 351 kBigDelta + 3); 352 353 auto values = 354 CreateSequenceByFirstValue(base.value() + kBigDelta, num_of_values_); 355 ASSERT_LT(values[0], base.value()) << "Sanity; must wrap around"; 356 357 if (optional_values_) { 358 // Arbitrarily make one of the values non-existent, to force 359 // optional-supporting encoding. 360 values[1] = std::optional<uint64_t>(); 361 } 362 363 TestEncodingAndDecoding(base, values); 364 } 365 366 TEST_P(DeltaEncodingTest, BigDeltaWithWrapAroundInValueSequence) { 367 if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { 368 return; // Inapplicable. 369 } 370 371 const uint64_t kBigDelta = 132828; 372 const std::optional<uint64_t> base(std::numeric_limits<uint64_t>::max() - 373 kBigDelta + 3); 374 375 auto values = CreateSequenceByFirstValue(std::numeric_limits<uint64_t>::max(), 376 num_of_values_); 377 ASSERT_LT(values[values.size() - 1], values[0]) << "Sanity; must wrap around"; 378 379 if (optional_values_) { 380 // Arbitrarily make one of the values non-existent, to force 381 // optional-supporting encoding. 382 RTC_DCHECK_GT(values.size() - 1, 1u); // Wrap around not cancelled. 383 values[1] = std::optional<uint64_t>(); 384 } 385 386 TestEncodingAndDecoding(base, values); 387 } 388 #ifdef _MSC_VER 389 #pragma warning(pop) 390 #endif 391 392 TEST_P(DeltaEncodingTest, MaxDeltaWithWrapAroundComparedToBase) { 393 if (optional_values_ && num_of_values_ == 1) { 394 return; // Inapplicable 395 } 396 397 const std::optional<uint64_t> base(3432); 398 auto values = CreateSequenceByFirstValue(*base - 1, num_of_values_); 399 400 if (optional_values_) { 401 // Arbitrarily make one of the values non-existent, to force 402 // optional-supporting encoding. 403 values[1] = std::optional<uint64_t>(); 404 } 405 406 TestEncodingAndDecoding(base, values); 407 } 408 409 TEST_P(DeltaEncodingTest, MaxDeltaWithWrapAroundInValueSequence) { 410 if (num_of_values_ == 1 || (optional_values_ && num_of_values_ < 3)) { 411 return; // Inapplicable. 412 } 413 414 const std::optional<uint64_t> base(3432); 415 416 auto values = CreateSequenceByDeltas( 417 *base, {0, std::numeric_limits<uint64_t>::max(), 3}, num_of_values_); 418 // Wraps around continuously by virtue of being max(); will not ASSERT. 419 420 if (optional_values_) { 421 // Arbitrarily make one of the values non-existent, to force 422 // optional-supporting encoding. 423 RTC_DCHECK_GT(values.size() - 1, 1u); // Wrap around not cancelled. 424 values[1] = std::optional<uint64_t>(); 425 } 426 427 TestEncodingAndDecoding(base, values); 428 } 429 430 // If num_of_values_ == 1, a zero delta will yield an empty string; that's 431 // already covered by AllValuesEqualToExistentBaseValue, but it doesn't hurt to 432 // test again. For all other cases, we have a new test. 433 TEST_P(DeltaEncodingTest, ZeroDelta) { 434 const std::optional<uint64_t> base(3432); 435 436 // Arbitrary sequence of deltas with intentional zero deltas, as well as 437 // consecutive zeros. 438 const std::vector<uint64_t> deltas = {0, 312, 11, 1, 1, 0, 0, 12, 439 400321, 3, 3, 12, 5, 0, 6}; 440 auto values = CreateSequenceByDeltas(base.value(), deltas, num_of_values_); 441 442 if (optional_values_) { 443 // Arbitrarily make one of the values non-existent, to force 444 // optional-supporting encoding. 445 values[0] = std::optional<uint64_t>(); 446 } 447 448 TestEncodingAndDecoding(base, values); 449 } 450 451 INSTANTIATE_TEST_SUITE_P( 452 SignednessOverrideAndNumberOfValuesInSequence, 453 DeltaEncodingTest, 454 ::testing::Combine(::testing::Values(DeltaSignedness::kNoOverride, 455 DeltaSignedness::kForceUnsigned, 456 DeltaSignedness::kForceSigned), 457 ::testing::Values(1, 2, 100, 10000), 458 ::testing::Bool(), 459 ::testing::Values(10, 20, 30))); 460 461 // Tests over the quality of the compression (as opposed to its correctness). 462 // Not to be confused with tests of runtime efficiency. 463 class DeltaEncodingCompressionQualityTest 464 : public ::testing::TestWithParam< 465 std::tuple<DeltaSignedness, uint64_t, uint64_t, uint64_t>> { 466 public: 467 DeltaEncodingCompressionQualityTest() 468 : signedness_(std::get<0>(GetParam())), 469 delta_max_bit_width_(std::get<1>(GetParam())), 470 num_of_values_(std::get<2>(GetParam())), 471 partial_random_seed_(std::get<3>(GetParam())) { 472 MaybeSetSignedness(signedness_); 473 } 474 475 ~DeltaEncodingCompressionQualityTest() override = default; 476 477 // Running with the same seed for all variants would make all tests start 478 // with the same sequence; avoid this by making the seed different. 479 uint64_t Seed() const { 480 // Multiply everything but by different primes to produce unique results. 481 return 2 * static_cast<uint64_t>(signedness_) + 3 * delta_max_bit_width_ + 482 5 * delta_max_bit_width_ + 7 * num_of_values_ + 483 11 * partial_random_seed_; 484 } 485 486 const DeltaSignedness signedness_; 487 const uint64_t delta_max_bit_width_; 488 const uint64_t num_of_values_; 489 const uint64_t partial_random_seed_; // Explained where it's used. 490 }; 491 492 // If no wrap-around occurs in the stream, the width of the values does not 493 // matter to compression performance; only the deltas matter. 494 TEST_P(DeltaEncodingCompressionQualityTest, BaseDoesNotAffectEfficiency) { 495 // 1. Bases which will not produce a wrap-around. 496 // 2. The last base - 0xffffffffffffffff - does cause a wrap-around, but 497 // that still works, because the width is 64 anyway, and does not 498 // need to be conveyed explicitly in the encoding header. 499 const uint64_t bases[] = {0, 0x55, 0xffffffff, 500 std::numeric_limits<uint64_t>::max()}; 501 502 std::vector<uint64_t> deltas(num_of_values_); 503 504 // Allows us to make sure that the deltas do not produce a wrap-around. 505 uint64_t last_element[std::size(bases)]; 506 memcpy(last_element, bases, sizeof(bases)); 507 508 // Avoid empty `deltas` due to first element causing wrap-around. 509 deltas[0] = 1; 510 for (uint64_t& element : last_element) { 511 ++element; 512 } 513 514 Random prng(Seed()); 515 516 for (size_t i = 1; i < deltas.size(); ++i) { 517 const uint64_t delta = RandomWithMaxBitWidth(&prng, delta_max_bit_width_); 518 519 bool wrap_around = false; 520 for (size_t j = 0; j < std::size(last_element); ++j) { 521 last_element[j] += delta; 522 if (last_element[j] < bases[j]) { 523 wrap_around = true; 524 break; 525 } 526 } 527 528 if (wrap_around) { 529 deltas.resize(i); 530 break; 531 } 532 533 deltas[i] = delta; 534 } 535 536 std::string encodings[std::size(bases)]; 537 538 for (size_t i = 0; i < std::size(bases); ++i) { 539 const auto values = 540 CreateSequenceByDeltas(bases[i], deltas, num_of_values_); 541 // Produce the encoding and write it to encodings[i]. 542 // By using TestEncodingAndDecoding() to do this, we also sanity-test 543 // the encoding/decoding, though that is not the test's focus. 544 TestEncodingAndDecoding(bases[i], values, &encodings[i]); 545 EXPECT_LE(encodings[i].length(), 546 EncodingLengthUpperBound(delta_max_bit_width_, num_of_values_, 547 signedness_)); 548 } 549 550 // Test focus - all of the encodings should be the same, as they are based 551 // on the same delta sequence, and do not contain a wrap-around. 552 EXPECT_THAT(encodings, Each(Eq(encodings[0]))); 553 } 554 555 INSTANTIATE_TEST_SUITE_P( 556 SignednessOverrideAndDeltaMaxBitWidthAndNumberOfValuesInSequence, 557 DeltaEncodingCompressionQualityTest, 558 ::testing::Combine( 559 ::testing::Values(DeltaSignedness::kNoOverride, 560 DeltaSignedness::kForceUnsigned, 561 DeltaSignedness::kForceSigned), 562 ::testing::Values(1, 4, 8, 15, 16, 17, 31, 32, 33, 63, 64), 563 ::testing::Values(1, 2, 100, 10000), 564 ::testing::Values(11, 12, 13))); 565 566 // Similar to DeltaEncodingTest, but instead of semi-surgically producing 567 // specific cases, produce large amount of semi-realistic inputs. 568 class DeltaEncodingFuzzerLikeTest 569 : public ::testing::TestWithParam< 570 std::tuple<DeltaSignedness, uint64_t, uint64_t, bool, uint64_t>> { 571 public: 572 DeltaEncodingFuzzerLikeTest() 573 : signedness_(std::get<0>(GetParam())), 574 delta_max_bit_width_(std::get<1>(GetParam())), 575 num_of_values_(std::get<2>(GetParam())), 576 optional_values_(std::get<3>(GetParam())), 577 partial_random_seed_(std::get<4>(GetParam())) { 578 MaybeSetSignedness(signedness_); 579 } 580 581 ~DeltaEncodingFuzzerLikeTest() override = default; 582 583 // Running with the same seed for all variants would make all tests start 584 // with the same sequence; avoid this by making the seed different. 585 uint64_t Seed() const { 586 // Multiply everything but by different primes to produce unique results. 587 return 2 * static_cast<uint64_t>(signedness_) + 3 * delta_max_bit_width_ + 588 5 * delta_max_bit_width_ + 7 * num_of_values_ + 589 11 * static_cast<uint64_t>(optional_values_) + 590 13 * partial_random_seed_; 591 } 592 593 const DeltaSignedness signedness_; 594 const uint64_t delta_max_bit_width_; 595 const uint64_t num_of_values_; 596 const bool optional_values_; 597 const uint64_t partial_random_seed_; // Explained where it's used. 598 }; 599 600 TEST_P(DeltaEncodingFuzzerLikeTest, Test) { 601 const std::optional<uint64_t> base(3432); 602 603 Random prng(Seed()); 604 std::vector<std::optional<uint64_t>> deltas(num_of_values_); 605 for (size_t i = 0; i < deltas.size(); ++i) { 606 if (!optional_values_ || prng.Rand<bool>()) { 607 deltas[i] = RandomWithMaxBitWidth(&prng, delta_max_bit_width_); 608 } 609 } 610 const auto values = 611 CreateSequenceByOptionalDeltas(base.value(), deltas, num_of_values_); 612 613 TestEncodingAndDecoding(base, values); 614 } 615 616 INSTANTIATE_TEST_SUITE_P( 617 SignednessOverrideAndDeltaMaxBitWidthAndNumberOfValuesInSequence, 618 DeltaEncodingFuzzerLikeTest, 619 ::testing::Combine( 620 ::testing::Values(DeltaSignedness::kNoOverride, 621 DeltaSignedness::kForceUnsigned, 622 DeltaSignedness::kForceSigned), 623 ::testing::Values(1, 4, 8, 15, 16, 17, 31, 32, 33, 63, 64), 624 ::testing::Values(1, 2, 100, 10000), 625 ::testing::Bool(), 626 ::testing::Values(21, 22, 23))); 627 628 class DeltaEncodingSpecificEdgeCasesTest 629 : public ::testing::TestWithParam< 630 std::tuple<DeltaSignedness, uint64_t, bool>> { 631 public: 632 DeltaEncodingSpecificEdgeCasesTest() { 633 UnsetFixedLengthEncoderDeltaSignednessForTesting(); 634 } 635 636 ~DeltaEncodingSpecificEdgeCasesTest() override = default; 637 }; 638 639 // This case is special because it produces identical forward/backward deltas. 640 TEST_F(DeltaEncodingSpecificEdgeCasesTest, SignedDeltaWithOnlyTopBitOn) { 641 MaybeSetSignedness(DeltaSignedness::kForceSigned); 642 643 const std::optional<uint64_t> base(3432); 644 645 const uint64_t delta = static_cast<uint64_t>(1) << 63; 646 const std::vector<std::optional<uint64_t>> values = {base.value() + delta}; 647 648 TestEncodingAndDecoding(base, values); 649 } 650 651 TEST_F(DeltaEncodingSpecificEdgeCasesTest, MaximumUnsignedDelta) { 652 MaybeSetSignedness(DeltaSignedness::kForceUnsigned); 653 654 const std::optional<uint64_t> base((static_cast<uint64_t>(1) << 63) + 0x123); 655 656 const std::vector<std::optional<uint64_t>> values = {base.value() - 1}; 657 658 TestEncodingAndDecoding(base, values); 659 } 660 661 // Check that, if all deltas are set to -1, things still work. 662 TEST_P(DeltaEncodingSpecificEdgeCasesTest, ReverseSequence) { 663 MaybeSetSignedness(std::get<0>(GetParam())); 664 const uint64_t width = std::get<1>(GetParam()); 665 const bool wrap_around = std::get<2>(GetParam()); 666 667 const uint64_t value_mask = (width == 64) 668 ? std::numeric_limits<uint64_t>::max() 669 : ((static_cast<uint64_t>(1) << width) - 1); 670 671 const uint64_t base = wrap_around ? 1u : (0xf82d3 & value_mask); 672 const std::vector<std::optional<uint64_t>> values = { 673 (base - 1u) & value_mask, (base - 2u) & value_mask, 674 (base - 3u) & value_mask}; 675 676 TestEncodingAndDecoding(base, values); 677 } 678 679 INSTANTIATE_TEST_SUITE_P( 680 _, 681 DeltaEncodingSpecificEdgeCasesTest, 682 ::testing::Combine( 683 ::testing::Values(DeltaSignedness::kNoOverride, 684 DeltaSignedness::kForceUnsigned, 685 DeltaSignedness::kForceSigned), 686 ::testing::Values(1, 4, 8, 15, 16, 17, 31, 32, 33, 63, 64), 687 ::testing::Bool())); 688 689 } // namespace 690 } // namespace webrtc