#include "client_buffer.hpp"
#include <glog/logging.h>
#include <gtest/gtest.h>
#include <atomic>
#include <cstddef>
#include <cstring>
#include <thread>
#include <vector>
namespace mooncake {
class ClientBufferTest : public ::testing::Test {
protected:
void SetUp() override {
google::InitGoogleLogging("ClientBufferTest");
FLAGS_logtostderr = 1;
}
void TearDown() override {
google::ShutdownGoogleLogging();
}
void VerifyBufferHandle(const BufferHandle& handle, size_t expected_size) {
EXPECT_NE(handle.ptr(), nullptr);
EXPECT_EQ(handle.size(), expected_size);
void* ptr = handle.ptr();
std::memset(ptr, 0xAB, expected_size);
const uint8_t* data = static_cast<const uint8_t*>(ptr);
for (size_t i = 0; i < expected_size; ++i) {
EXPECT_EQ(data[i], 0xAB) << "Memory corruption at offset " << i;
}
}
void VerifyAlignment(void* ptr, size_t alignment = 64) {
uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
EXPECT_EQ(addr % alignment, 0)
<< "Memory not aligned to " << alignment << " bytes";
}
};
TEST_F(ClientBufferTest, ZeroSizeAllocator) {
auto allocator = ClientBufferAllocator::create(0);
EXPECT_NE(allocator, nullptr);
auto handle_opt = allocator->allocate(1024);
EXPECT_FALSE(handle_opt.has_value());
}
TEST_F(ClientBufferTest, MultipleAllocations) {
const size_t buffer_size = 1024 * 1024;
const size_t alloc_size = 64 * 1024;
const int num_allocations = 8;
auto allocator = ClientBufferAllocator::create(buffer_size);
ASSERT_NE(allocator, nullptr);
std::vector<BufferHandle> handles;
handles.reserve(num_allocations);
for (int i = 0; i < num_allocations; ++i) {
auto handle_opt = allocator->allocate(alloc_size);
ASSERT_TRUE(handle_opt.has_value()) << "Failed to allocate block " << i;
BufferHandle handle = std::move(handle_opt.value());
VerifyBufferHandle(handle, alloc_size);
handles.push_back(std::move(handle));
}
for (const auto& handle : handles) {
EXPECT_NE(handle.ptr(), nullptr);
EXPECT_EQ(handle.size(), alloc_size);
}
}
TEST_F(ClientBufferTest, AllocationTooLarge) {
const size_t buffer_size = 1024 * 1024;
auto allocator = ClientBufferAllocator::create(buffer_size);
ASSERT_NE(allocator, nullptr);
auto handle_opt = allocator->allocate(buffer_size + 1);
EXPECT_FALSE(handle_opt.has_value());
}
TEST_F(ClientBufferTest, ZeroSizeAllocation) {
const size_t buffer_size = 1024 * 1024;
auto allocator = ClientBufferAllocator::create(buffer_size);
ASSERT_NE(allocator, nullptr);
auto handle_opt = allocator->allocate(0);
EXPECT_FALSE(handle_opt.has_value());
}
TEST_F(ClientBufferTest, SmallAllocation) {
const size_t buffer_size = 1024 * 1024;
auto allocator = ClientBufferAllocator::create(buffer_size);
ASSERT_NE(allocator, nullptr);
auto handle_opt = allocator->allocate(1);
ASSERT_TRUE(handle_opt.has_value());
BufferHandle handle = std::move(handle_opt.value());
EXPECT_NE(handle.ptr(), nullptr);
EXPECT_EQ(handle.size(), 1);
uint8_t* ptr = static_cast<uint8_t*>(handle.ptr());
*ptr = 0xFF;
EXPECT_EQ(*ptr, 0xFF);
}
TEST_F(ClientBufferTest, BufferHandleMoveConstructor) {
const size_t buffer_size = 1024 * 1024;
const size_t alloc_size = 1024;
auto allocator = ClientBufferAllocator::create(buffer_size);
ASSERT_NE(allocator, nullptr);
auto handle_opt = allocator->allocate(alloc_size);
ASSERT_TRUE(handle_opt.has_value());
BufferHandle handle1 = std::move(handle_opt.value());
void* original_ptr = handle1.ptr();
size_t original_size = handle1.size();
BufferHandle handle2 = std::move(handle1);
EXPECT_EQ(handle2.ptr(), original_ptr);
EXPECT_EQ(handle2.size(), original_size);
EXPECT_EQ(handle1.ptr(), nullptr);
EXPECT_EQ(handle1.size(), 0);
VerifyBufferHandle(handle2, alloc_size);
}
TEST_F(ClientBufferTest, SplitIntoSlices) {
const size_t buffer_size = 1024 * 1024;
const size_t alloc_size = 100 * 1024;
auto allocator = ClientBufferAllocator::create(buffer_size);
ASSERT_NE(allocator, nullptr);
auto handle_opt = allocator->allocate(alloc_size);
ASSERT_TRUE(handle_opt.has_value());
BufferHandle handle = std::move(handle_opt.value());
auto slices = split_into_slices(handle);
size_t total_slice_size = 0;
for (const auto& slice : slices) {
EXPECT_NE(slice.ptr, nullptr);
EXPECT_GT(slice.size, 0);
EXPECT_LE(slice.size, kMaxSliceSize);
total_slice_size += slice.size;
}
EXPECT_EQ(total_slice_size, alloc_size);
if (slices.size() > 1) {
for (size_t i = 1; i < slices.size(); ++i) {
char* prev_end =
static_cast<char*>(slices[i - 1].ptr) + slices[i - 1].size;
char* curr_start = static_cast<char*>(slices[i].ptr);
EXPECT_EQ(prev_end, curr_start)
<< "Slices are not contiguous at index " << i;
}
}
}
TEST_F(ClientBufferTest, SplitIntoSlicesSmallBuffer) {
const size_t buffer_size = 1024 * 1024;
const size_t alloc_size = 64;
auto allocator = ClientBufferAllocator::create(buffer_size);
ASSERT_NE(allocator, nullptr);
auto handle_opt = allocator->allocate(alloc_size);
ASSERT_TRUE(handle_opt.has_value());
BufferHandle handle = std::move(handle_opt.value());
auto slices = split_into_slices(handle);
EXPECT_EQ(slices.size(), 1);
EXPECT_EQ(slices[0].ptr, handle.ptr());
EXPECT_EQ(slices[0].size, alloc_size);
}
TEST_F(ClientBufferTest, MemoryExhaustion) {
const size_t buffer_size =
64 * 1024;
const size_t alloc_size = 8 * 1024;
auto allocator = ClientBufferAllocator::create(buffer_size);
ASSERT_NE(allocator, nullptr);
std::vector<BufferHandle> handles;
int successful_allocations = 0;
for (int i = 0; i < 10; ++i) {
auto handle_opt = allocator->allocate(alloc_size);
if (handle_opt.has_value()) {
handles.push_back(std::move(handle_opt.value()));
successful_allocations++;
} else {
break;
}
}
EXPECT_EQ(successful_allocations, 8);
auto final_handle_opt = allocator->allocate(alloc_size);
EXPECT_FALSE(final_handle_opt.has_value());
handles.pop_back();
auto new_handle_opt = allocator->allocate(alloc_size);
EXPECT_TRUE(new_handle_opt.has_value());
}
TEST_F(ClientBufferTest, CalculateTotalSizeMemoryReplica) {
Replica::Descriptor replica;
MemoryDescriptor mem_desc;
AllocatedBuffer::Descriptor buf1;
buf1.size_ = 1024;
buf1.buffer_address_ = 0x1000;
AllocatedBuffer::Descriptor buf2;
buf2.size_ = 2048;
buf2.buffer_address_ = 0x2000;
AllocatedBuffer::Descriptor buf3;
buf3.size_ = 512;
buf3.buffer_address_ = 0x3000;
mem_desc.buffer_descriptors = {buf1, buf2, buf3};
replica.descriptor_variant = mem_desc;
replica.status = ReplicaStatus::COMPLETE;
uint64_t total_size = calculate_total_size(replica);
EXPECT_EQ(total_size, 1024 + 2048 + 512);
}
TEST_F(ClientBufferTest, CalculateTotalSizeDiskReplica) {
Replica::Descriptor replica;
DiskDescriptor disk_desc;
disk_desc.object_size = 4096;
replica.descriptor_variant = disk_desc;
replica.status = ReplicaStatus::COMPLETE;
uint64_t total_size = calculate_total_size(replica);
EXPECT_EQ(total_size, 4096);
}
TEST_F(ClientBufferTest, CalculateTotalSizeEmptyMemoryReplica) {
Replica::Descriptor replica;
MemoryDescriptor mem_desc;
replica.descriptor_variant = mem_desc;
replica.status = ReplicaStatus::COMPLETE;
uint64_t total_size = calculate_total_size(replica);
EXPECT_EQ(total_size, 0);
}
TEST_F(ClientBufferTest, AllocateSlicesMemoryReplica) {
const size_t buffer_size = 1024 * 1024;
const size_t alloc_size = 4096;
auto allocator = ClientBufferAllocator::create(buffer_size);
ASSERT_NE(allocator, nullptr);
auto handle_opt = allocator->allocate(alloc_size);
ASSERT_TRUE(handle_opt.has_value());
BufferHandle handle = std::move(handle_opt.value());
Replica::Descriptor replica;
MemoryDescriptor mem_desc;
AllocatedBuffer::Descriptor buf1;
buf1.size_ = 1024;
AllocatedBuffer::Descriptor buf2;
buf2.size_ = 2048;
AllocatedBuffer::Descriptor buf3;
buf3.size_ = 1024;
mem_desc.buffer_descriptors = {buf1, buf2, buf3};
replica.descriptor_variant = mem_desc;
replica.status = ReplicaStatus::COMPLETE;
std::vector<Slice> slices;
int result = allocateSlices(slices, replica, handle);
EXPECT_EQ(result, 0);
EXPECT_EQ(slices.size(), 3);
EXPECT_EQ(slices[0].size, 1024);
EXPECT_EQ(slices[1].size, 2048);
EXPECT_EQ(slices[2].size, 1024);
char* base_ptr = static_cast<char*>(handle.ptr());
EXPECT_EQ(slices[0].ptr, base_ptr);
EXPECT_EQ(slices[1].ptr, base_ptr + 1024);
EXPECT_EQ(slices[2].ptr, base_ptr + 1024 + 2048);
}
TEST_F(ClientBufferTest, AllocateSlicesDiskReplica) {
const size_t buffer_size = 1024 * 1024;
const size_t alloc_size = 8192;
auto allocator = ClientBufferAllocator::create(buffer_size);
ASSERT_NE(allocator, nullptr);
auto handle_opt = allocator->allocate(alloc_size);
ASSERT_TRUE(handle_opt.has_value());
BufferHandle handle = std::move(handle_opt.value());
Replica::Descriptor replica;
DiskDescriptor disk_desc;
disk_desc.object_size = 8192;
replica.descriptor_variant = disk_desc;
replica.status = ReplicaStatus::COMPLETE;
std::vector<Slice> slices;
int result = allocateSlices(slices, replica, handle);
EXPECT_EQ(result, 0);
EXPECT_GE(slices.size(), 1);
size_t total_slice_size = 0;
for (const auto& slice : slices) {
EXPECT_NE(slice.ptr, nullptr);
EXPECT_GT(slice.size, 0);
EXPECT_LE(slice.size, kMaxSliceSize);
total_slice_size += slice.size;
}
EXPECT_EQ(total_slice_size, 8192);
}
TEST_F(ClientBufferTest, AllocateSlicesEmptyMemoryReplica) {
const size_t buffer_size = 1024 * 1024;
const size_t alloc_size = 1024;
auto allocator = ClientBufferAllocator::create(buffer_size);
ASSERT_NE(allocator, nullptr);
auto handle_opt = allocator->allocate(alloc_size);
ASSERT_TRUE(handle_opt.has_value());
BufferHandle handle = std::move(handle_opt.value());
Replica::Descriptor replica;
MemoryDescriptor mem_desc;
replica.descriptor_variant = mem_desc;
replica.status = ReplicaStatus::COMPLETE;
std::vector<Slice> slices;
int result = allocateSlices(slices, replica, handle);
EXPECT_EQ(result, 0);
EXPECT_EQ(slices.size(), 0);
}
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}