blob: 9b4533c7017db4e9c6c294013a248e5d9611b2f7 [file]
/*
* 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