| /* | |
| * Licensed to the Apache Software Foundation (ASF) under one | |
| * or more contributor license agreements. See the NOTICE file | |
| * distributed with this work for additional information | |
| * regarding copyright ownership. The ASF licenses this file | |
| * to you 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 | |
| * | |
| * http://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 "paimon/common/utils/crc32c.h" | |
| #include <array> | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <cstring> | |
| #include <string> | |
| #include <vector> | |
| #include "gtest/gtest.h" | |
| namespace paimon::test { | |
| namespace { | |
| constexpr size_t kPatternLength = 64; | |
| constexpr uint32_t kPatternCrc32 = 2227724424u; | |
| /// Built as exact bytes: converting 128-255 to a plain `char` is implementation | |
| /// defined, and whether `char` is signed depends on the ABI and compiler flags. | |
| std::vector<uint8_t> MakePattern(size_t length) { | |
| std::vector<uint8_t> pattern(length); | |
| for (size_t i = 0; i < length; ++i) { | |
| pattern[i] = static_cast<uint8_t>((i * 31 + 7) % 256); | |
| } | |
| return pattern; | |
| } | |
| const char* AsChars(const std::vector<uint8_t>& bytes) { | |
| return reinterpret_cast<const char*>(bytes.data()); | |
| } | |
| } // namespace | |
| TEST(CRC32CTest, TestSimple) { | |
| char a = 'a'; | |
| ASSERT_EQ(CRC32C::calculate(&a, 1), 3904355907); | |
| std::string data = "hello paimon c++"; | |
| ASSERT_EQ(CRC32C::calculate(data.c_str(), data.size()), 1311805437); | |
| } | |
| // The SST block trailer and the B-tree global index persist whatever this | |
| // produces, so it has to keep matching zlib's CRC-32 (polynomial 0x04C11DB7) | |
| // byte for byte -- the class name notwithstanding, it is not Castagnoli. | |
| // Expected values come from Python's zlib.crc32, not from this implementation. | |
| TEST(CRC32CTest, TestMatchesZlibCrc32ForAllLengthResidues) { | |
| struct Case { | |
| size_t length; | |
| uint32_t crc; | |
| }; | |
| // Lengths cover every residue modulo 8, so each combination of the 8/4/2/1-byte | |
| // steps a hardware kernel would take is exercised at least once. | |
| const std::vector<Case> cases = { | |
| {0, 0u}, {1, 1281784366u}, {2, 3700752837u}, | |
| {3, 1063713196u}, {4, 1199752182u}, {5, 4062898429u}, | |
| {6, 690260224u}, {7, 1044920610u}, {8, 2807432232u}, | |
| {15, 2407004859u}, {16, 104245397u}, {31, 1171692296u}, | |
| {32, 1227094950u}, {63, 2034968221u}, {kPatternLength, kPatternCrc32}, | |
| }; | |
| const std::vector<uint8_t> pattern = MakePattern(kPatternLength); | |
| for (const Case& test_case : cases) { | |
| ASSERT_EQ(CRC32C::calculate(AsChars(pattern), test_case.length), test_case.crc) | |
| << "length=" << test_case.length; | |
| } | |
| } | |
| // A hardware kernel would consume leading bytes one at a time until the pointer | |
| // is aligned, so pinning alignment independence now keeps such a kernel from | |
| // landing with an alignment-dependent checksum. | |
| TEST(CRC32CTest, TestIsIndependentOfInputAlignment) { | |
| const std::vector<uint8_t> pattern = MakePattern(kPatternLength); | |
| // alignas so that `offset` really is the address residue: a char array's own | |
| // alignment is 1, so its base could otherwise sit at any residue itself. | |
| alignas(8) std::array<char, kPatternLength + 8> shifted{}; | |
| for (size_t offset = 0; offset < 8; ++offset) { | |
| std::memcpy(shifted.data() + offset, pattern.data(), pattern.size()); | |
| ASSERT_EQ(CRC32C::calculate(shifted.data() + offset, pattern.size()), kPatternCrc32) | |
| << "offset=" << offset; | |
| } | |
| } | |
| // SstFileWriter checksums a block and then folds the compression byte into the | |
| // running value, so chaining must agree with a single pass over the same bytes. | |
| TEST(CRC32CTest, TestRunningChecksumMatchesSinglePass) { | |
| std::string data = "hello paimon c++"; | |
| for (size_t split = 0; split <= data.size(); ++split) { | |
| uint32_t running = CRC32C::calculate(data.c_str(), split); | |
| running = CRC32C::calculate(data.c_str() + split, data.size() - split, running); | |
| ASSERT_EQ(running, CRC32C::calculate(data.c_str(), data.size())) << "split=" << split; | |
| } | |
| } | |
| // The test above only shows the two paths agree with each other, which would | |
| // still hold if both were wrong. Pin the seeded path to zlib's own output too. | |
| // Expected values come from Python's zlib.crc32(data, seed). | |
| TEST(CRC32CTest, TestSeededChecksumMatchesZlibCrc32) { | |
| constexpr uint32_t kSeed = 0x12345678u; | |
| const std::vector<uint8_t> pattern = MakePattern(kPatternLength); | |
| // Continuing a stream without appending anything leaves the running value alone. | |
| ASSERT_EQ(CRC32C::calculate(AsChars(pattern), 0, kSeed), kSeed); | |
| ASSERT_EQ(CRC32C::calculate(AsChars(pattern), pattern.size(), kSeed), 3207811452u); | |
| std::string data = "hello paimon c++"; | |
| ASSERT_EQ(CRC32C::calculate(data.c_str(), data.size(), kSeed), 671566677u); | |
| } | |
| } // namespace paimon::test |