blob: 3f57cbf84f445d30764a9baa5c51faebc614b87c [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 a
*
* 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 "common/allocator/byte_stream.h"
#include <gtest/gtest.h>
#include <cstdint>
#include <cstring>
namespace common {
TEST(FloatConversionTest, FloatToBytesAndBytesToFloat) {
float actual_float = -1234.5678f;
uint8_t actual_bytes[4];
float_to_bytes(actual_float, actual_bytes);
float expect_float = bytes_to_float(actual_bytes);
EXPECT_FLOAT_EQ(expect_float, actual_float);
}
TEST(DoubleConversionTest, DoubleToBytesAndBytesToDouble) {
double actual_double = -1.23456789e-20;
uint8_t actual_bytes[8];
double_to_bytes(actual_double, actual_bytes);
double expect_double = bytes_to_double(actual_bytes);
EXPECT_DOUBLE_EQ(expect_double, actual_double);
}
class ByteStreamTest : public ::testing::Test {
protected:
void SetUp() override {
byte_stream_ = new ByteStream(16, MOD_DEFAULT, false);
}
void TearDown() override { delete byte_stream_; }
void write_to_stream(const uint8_t* data, uint32_t size) {
ASSERT_EQ(byte_stream_->write_buf(data, size), common::E_OK);
}
void read_from_stream(uint8_t* buffer, uint32_t want_len,
uint32_t& read_len) {
ASSERT_EQ(byte_stream_->read_buf(buffer, want_len, read_len),
common::E_OK);
}
void wrap_external_buffer(const char* buffer, int32_t size) {
byte_stream_->wrap_from(buffer, size);
}
ByteStream* byte_stream_;
};
TEST_F(ByteStreamTest, WriteReadTest) {
const uint8_t data[] = {0x01, 0x02, 0x03};
const uint32_t data_size = sizeof(data);
write_to_stream(data, data_size);
uint8_t read_buffer[data_size];
uint32_t read_len = 0;
read_from_stream(read_buffer, data_size, read_len);
ASSERT_EQ(read_len, data_size);
for (uint32_t i = 0; i < data_size; ++i) {
ASSERT_EQ(read_buffer[i], data[i]);
}
}
TEST_F(ByteStreamTest, WriteReadLargeQuantities) {
for (int i = 0; i < 1024 * 1024; i++) {
const uint8_t data = i & 0xff;
write_to_stream(&data, 1);
}
static uint8_t read_buffer[1024 * 1024];
for (int i = 0; i < 1024 * 1024; i++) {
uint32_t read_len = 0;
read_from_stream(read_buffer + i, 1, read_len);
}
for (int i = 0; i < 1024 * 1024; i++) {
EXPECT_EQ(read_buffer[i], i & 0xff);
}
}
TEST_F(ByteStreamTest, WrapExternalBufferTest) {
const char externalBuffer[] = "Hello, World!";
const int32_t bufferSize = sizeof(externalBuffer);
wrap_external_buffer(externalBuffer, bufferSize);
ASSERT_TRUE(byte_stream_->is_wrapped());
ASSERT_STREQ(byte_stream_->get_wrapped_buf(), externalBuffer);
}
TEST_F(ByteStreamTest, SizeTest) {
const uint8_t data[] = {0x01, 0x02, 0x03};
const uint32_t data_size = sizeof(data);
write_to_stream(data, data_size);
ASSERT_EQ(byte_stream_->total_size(), data_size);
ASSERT_EQ(byte_stream_->remaining_size(), data_size);
uint8_t read_buffer[data_size];
uint32_t read_len = 0;
read_from_stream(read_buffer, 2, read_len);
ASSERT_EQ(byte_stream_->remaining_size(), data_size - 2);
}
TEST_F(ByteStreamTest, MarkReadPositionTest) {
const uint8_t data[] = {0x01, 0x02, 0x03};
const uint32_t data_size = sizeof(data);
write_to_stream(data, data_size);
byte_stream_->mark_read_pos();
uint8_t read_buffer[data_size];
uint32_t read_len = 0;
read_from_stream(read_buffer, 2, read_len);
uint32_t markedLen = byte_stream_->get_mark_len();
ASSERT_EQ(markedLen, read_len);
}
TEST_F(ByteStreamTest, ResetTest) {
const uint8_t data[] = {0x01, 0x02, 0x03};
const uint32_t data_size = sizeof(data);
write_to_stream(data, data_size);
byte_stream_->reset();
ASSERT_EQ(byte_stream_->total_size(), 0);
ASSERT_EQ(byte_stream_->remaining_size(), 0);
ASSERT_EQ(byte_stream_->read_pos(), 0);
ASSERT_FALSE(byte_stream_->is_wrapped());
}
TEST_F(ByteStreamTest, WriteMoreThanPageSizeTest) {
const uint8_t data[20] = {0x01, 0x02, 0x03};
const uint32_t data_size = sizeof(data);
write_to_stream(data, data_size);
ASSERT_EQ(byte_stream_->total_size(), 20);
ASSERT_EQ(byte_stream_->remaining_size(), 20);
uint8_t read_buffer[data_size];
uint32_t read_len = 0;
read_from_stream(read_buffer, data_size, read_len);
ASSERT_EQ(read_len, data_size);
for (uint32_t i = 0; i < data_size; ++i) {
ASSERT_EQ(read_buffer[i], data[i]);
}
}
TEST_F(ByteStreamTest, ReadMoreThanAvailableTest) {
const uint8_t data[] = {0x01, 0x02, 0x03};
const uint32_t data_size = sizeof(data);
write_to_stream(data, data_size);
uint8_t read_buffer[4];
uint32_t read_len = 0;
int readResult = byte_stream_->read_buf(read_buffer, 4, read_len);
ASSERT_EQ(readResult, common::E_PARTIAL_READ);
ASSERT_EQ(read_len, data_size);
}
// Regression: the ctor used to take page_size verbatim, but hot read/write
// paths use `& (page_size-1)` as a bitmask. A non-power-of-2 page_size
// would cause page-crossing logic to misfire, corrupting written data.
// Constructing with 1000 should still round-trip cleanly across many pages.
// Regression: round_up_pow2 used `while (ps < n) ps <<= 1`, which overflows
// to 0 once ps passes 2^31 and never matches, looping forever. Verify the
// clamped helper returns the largest representable power of two instead.
TEST(ByteStreamCtorTest, RoundUpPow2ClampsHugeInput) {
EXPECT_EQ(round_up_pow2(0u), 1u);
EXPECT_EQ(round_up_pow2(1u), 1u);
EXPECT_EQ(round_up_pow2(1000u), 1024u);
EXPECT_EQ(round_up_pow2(1024u), 1024u);
EXPECT_EQ(round_up_pow2(0x80000000u), 0x80000000u);
EXPECT_EQ(round_up_pow2(0x80000001u), 0x80000000u);
EXPECT_EQ(round_up_pow2(0xFFFFFFFFu), 0x80000000u);
}
TEST(ByteStreamCtorTest, NonPowerOfTwoPageSizeRoundTrip) {
ByteStream bs(1000, MOD_DEFAULT, false);
// Span ~5 pages: 1024 * 5 = 5120 bytes.
const uint32_t N = 5120;
std::vector<uint8_t> data(N);
for (uint32_t i = 0; i < N; i++) {
data[i] = static_cast<uint8_t>((i * 31 + 7) & 0xff);
}
ASSERT_EQ(bs.write_buf(data.data(), N), common::E_OK);
std::vector<uint8_t> out(N, 0);
uint32_t read_len = 0;
ASSERT_EQ(bs.read_buf(out.data(), N, read_len), common::E_OK);
ASSERT_EQ(read_len, N);
for (uint32_t i = 0; i < N; i++) {
ASSERT_EQ(out[i], data[i]) << "mismatch at idx " << i;
}
}
TEST_F(ByteStreamTest, WrapAndClearTest) {
const char externalBuffer[] = "Hello, World!";
const int32_t bufferSize = sizeof(externalBuffer);
wrap_external_buffer(externalBuffer, bufferSize);
byte_stream_->clear_wrapped_buf();
ASSERT_EQ(byte_stream_->get_wrapped_buf(), nullptr);
}
class SerializationUtilTest : public ::testing::Test {
protected:
void SetUp() override {
byte_stream_ = new ByteStream(16, MOD_DEFAULT, false);
}
void TearDown() override { delete byte_stream_; }
ByteStream* byte_stream_;
};
TEST_F(SerializationUtilTest, WriteReadUI8) {
uint8_t value_to_write = 0x12;
uint8_t value_read = 0;
EXPECT_EQ(SerializationUtil::write_ui8(value_to_write, *byte_stream_),
common::E_OK);
EXPECT_EQ(SerializationUtil::read_ui8(value_read, *byte_stream_),
common::E_OK);
EXPECT_EQ(value_to_write, value_read);
}
TEST_F(SerializationUtilTest, WriteReadUI16) {
uint16_t value_to_write = 0x1234;
uint16_t value_read = 0;
EXPECT_EQ(SerializationUtil::write_ui16(value_to_write, *byte_stream_),
common::E_OK);
EXPECT_EQ(SerializationUtil::read_ui16(value_read, *byte_stream_),
common::E_OK);
EXPECT_EQ(value_to_write, value_read);
}
TEST_F(SerializationUtilTest, WriteReadUI32) {
uint32_t value_to_write = 0x12345678;
uint32_t value_read = 0;
EXPECT_EQ(SerializationUtil::write_ui32(value_to_write, *byte_stream_),
common::E_OK);
EXPECT_EQ(SerializationUtil::read_ui32(value_read, *byte_stream_),
common::E_OK);
EXPECT_EQ(value_to_write, value_read);
}
TEST_F(SerializationUtilTest, WriteReadUI64) {
uint64_t value_to_write = 0x123456789ABCDEF0;
uint64_t value_read = 0;
EXPECT_EQ(SerializationUtil::write_ui64(value_to_write, *byte_stream_),
common::E_OK);
EXPECT_EQ(SerializationUtil::read_ui64(value_read, *byte_stream_),
common::E_OK);
EXPECT_EQ(value_to_write, value_read);
}
TEST_F(SerializationUtilTest, WriteReadFloat) {
float value_to_write = 3.14f;
float value_read = 0.0;
EXPECT_EQ(SerializationUtil::write_float(value_to_write, *byte_stream_),
common::E_OK);
EXPECT_EQ(SerializationUtil::read_float(value_read, *byte_stream_),
common::E_OK);
EXPECT_EQ(value_to_write, value_read);
}
TEST_F(SerializationUtilTest, WriteReadDouble) {
double value_to_write = 3.141592653589793;
double value_read = 0.0;
EXPECT_EQ(SerializationUtil::write_double(value_to_write, *byte_stream_),
common::E_OK);
EXPECT_EQ(SerializationUtil::read_double(value_read, *byte_stream_),
common::E_OK);
EXPECT_EQ(value_to_write, value_read);
}
TEST_F(SerializationUtilTest, WriteReadString) {
std::string value_to_write = "Hello, World!";
std::string value_read = "";
EXPECT_EQ(SerializationUtil::write_str(value_to_write, *byte_stream_),
common::E_OK);
EXPECT_EQ(SerializationUtil::read_str(value_read, *byte_stream_),
common::E_OK);
EXPECT_EQ(value_to_write, value_read);
}
TEST_F(SerializationUtilTest, WriteReadIntLEPaddedBitWidth_BitWidthTooLarge) {
int32_t value = 123;
EXPECT_EQ(SerializationUtil::write_int_little_endian_padded_on_bit_width(
value, *byte_stream_, 40),
common::E_TSFILE_CORRUPTED);
byte_stream_->reset();
int32_t read_val = 0;
EXPECT_EQ(SerializationUtil::read_int_little_endian_padded_on_bit_width(
*byte_stream_, 40, read_val),
common::E_TSFILE_CORRUPTED);
}
TEST_F(SerializationUtilTest, WriteReadIntLEPaddedBitWidthBoundaryValue) {
std::vector<int32_t> test_values = {
132100, 1, -1, 12345678, -87654321, INT32_MAX, INT32_MIN};
int bit_width = 32;
for (int32_t original_value : test_values) {
byte_stream_->reset();
EXPECT_EQ(
SerializationUtil::write_int_little_endian_padded_on_bit_width(
original_value, *byte_stream_, bit_width),
common::E_OK);
int32_t read_value = 0;
EXPECT_EQ(SerializationUtil::read_int_little_endian_padded_on_bit_width(
*byte_stream_, bit_width, read_value),
common::E_OK);
EXPECT_EQ(read_value, original_value)
<< "Mismatch with bit_width = " << bit_width;
}
}
// Regression: total_size_ was widened to uint64_t but the read-cursor APIs
// stayed uint32_t. A stream that legitimately reaches >4 GiB would have
// remaining_size() / read_pos() / set_read_pos() truncating to the low 32
// bits and silently mis-positioning later reads. Lock the widened type at
// compile time so a partial revert can't reintroduce truncation, and
// round-trip a moderate value via the API to catch arithmetic mistakes.
TEST(ByteStreamWidthTest, ReadCursorApisAre64Bit) {
ByteStream s(64, common::MOD_DEFAULT);
static_assert(sizeof(decltype(s.read_pos())) >= sizeof(uint64_t),
"ByteStream::read_pos() must return a 64-bit type");
static_assert(sizeof(decltype(s.remaining_size())) >= sizeof(uint64_t),
"ByteStream::remaining_size() must return a 64-bit type");
static_assert(sizeof(decltype(s.get_mark_len())) >= sizeof(uint64_t),
"ByteStream::get_mark_len() must return a 64-bit type");
// Round-trip a position via set_read_pos / read_pos on a small wrapped
// buffer. Combined with the static_asserts above this guards the path
// arithmetic: a partial revert that kept the signature 64-bit but
// truncated read_pos_ to uint32_t internally would fail set_read_pos →
// read_pos on values near a 32-bit boundary.
constexpr int32_t kLen = 256;
std::vector<char> backing(kLen, 0);
ByteStream wrapped(common::MOD_DEFAULT);
wrapped.wrap_from(backing.data(), kLen);
wrapped.set_read_pos(static_cast<uint64_t>(kLen - 7));
EXPECT_EQ(wrapped.read_pos(), static_cast<uint64_t>(kLen - 7));
EXPECT_EQ(wrapped.remaining_size(), 7u);
}
// Regression for the 64 KiB page memory-pressure account: ByteStream pages
// are allocated up to OUT_STREAM_PAGE_SIZE bytes even when only a handful of
// bytes have been written, so a chunk-group with many sparse measurements
// can pin tens of megabytes that total_size() can't see. allocated_bytes()
// must reflect the real allocated footprint.
TEST(ByteStreamAllocatedBytesTest, ReportsPageAllocationsNotLogicalSize) {
constexpr uint32_t kPageSize = 4096;
ByteStream s(kPageSize, common::MOD_DEFAULT);
EXPECT_EQ(s.allocated_bytes(), 0u);
// First write triggers one page allocation; logical size is 4 bytes but
// the real footprint should be the rounded page size.
uint8_t payload[4] = {1, 2, 3, 4};
ASSERT_EQ(s.write_buf(payload, 4), common::E_OK);
EXPECT_EQ(s.total_size(), 4u);
EXPECT_GE(s.allocated_bytes(), kPageSize);
EXPECT_EQ(s.allocated_bytes() % kPageSize, 0u);
}
// Regression for finding 21 (MSVC reinterpret_cast<atomic<T>*> UB): the
// OptionalAtomic storage is now a real std::atomic<T>, so atomic ops never
// observe a non-atomic backing object. Lock the storage type at compile
// time so a future refactor can't reintroduce the bare T fallback.
TEST(OptionalAtomicStorageTest, BackingStorageIsRealAtomic) {
OptionalAtomic<uint64_t> oa(0, /*enable_atomic=*/true);
static_assert(!std::is_copy_constructible<OptionalAtomic<uint64_t>>::value,
"OptionalAtomic must not be copyable — the std::atomic<T> "
"storage forces explicit load/store");
EXPECT_EQ(oa.load(), 0u);
oa.store(42);
EXPECT_EQ(oa.load(), 42u);
EXPECT_EQ(oa.atomic_aaf(8), 50u);
EXPECT_EQ(oa.load(), 50u);
EXPECT_EQ(oa.atomic_faa(1), 50u);
EXPECT_EQ(oa.load(), 51u);
}
} // namespace common