#include "offset_allocator/offset_allocator.hpp"
#include "mutex.h"
#include "serializer.h"
#include "types.h"
#include <gtest/gtest.h>
#include <map>
#include <memory>
#include <random>
namespace mooncake::offset_allocator {
constexpr uint32 NUM_BINS = 240;
const uint32 bin_sizes[] = {
0, 1, 2, 3, 4, 5,
6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 18,
20, 22, 24, 26, 28, 30,
32, 36, 40, 44, 48, 52,
56, 60, 64, 72, 80, 88,
96, 104, 112, 120, 128, 144,
160, 176, 192, 208, 224, 240,
256, 288, 320, 352, 384, 416,
448, 480, 512, 576, 640, 704,
768, 832, 896, 960, 1024, 1152,
1280, 1408, 1536, 1664, 1792, 1920,
2048, 2304, 2560, 2816, 3072, 3328,
3584, 3840, 4096, 4608, 5120, 5632,
6144, 6656, 7168, 7680, 8192, 9216,
10240, 11264, 12288, 13312, 14336, 15360,
16384, 18432, 20480, 22528, 24576, 26624,
28672, 30720, 32768, 36864, 40960, 45056,
49152, 53248, 57344, 61440, 65536, 73728,
81920, 90112, 98304, 106496, 114688, 122880,
131072, 147456, 163840, 180224, 196608, 212992,
229376, 245760, 262144, 294912, 327680, 360448,
393216, 425984, 458752, 491520, 524288, 589824,
655360, 720896, 786432, 851968, 917504, 983040,
1048576, 1179648, 1310720, 1441792, 1572864, 1703936,
1835008, 1966080, 2097152, 2359296, 2621440, 2883584,
3145728, 3407872, 3670016, 3932160, 4194304, 4718592,
5242880, 5767168, 6291456, 6815744, 7340032, 7864320,
8388608, 9437184, 10485760, 11534336, 12582912, 13631488,
14680064, 15728640, 16777216, 18874368, 20971520, 23068672,
25165824, 27262976, 29360128, 31457280, 33554432, 37748736,
41943040, 46137344, 50331648, 54525952, 58720256, 62914560,
67108864, 75497472, 83886080, 92274688, 100663296, 109051904,
117440512, 125829120, 134217728, 150994944, 167772160, 184549376,
201326592, 218103808, 234881024, 251658240, 268435456, 301989888,
335544320, 369098752, 402653184, 436207616, 469762048, 503316480,
536870912, 603979776, 671088640, 738197504, 805306368, 872415232,
939524096, 1006632960, 1073741824, 1207959552, 1342177280, 1476395008,
1610612736, 1744830464, 1879048192, 2013265920, 2147483648, 2415919104,
2684354560, 2952790016, 3221225472, 3489660928, 3758096384, 4026531840,
};
class AllocatorWrapper;
class AllocationHandleWrapper {
public:
AllocationHandleWrapper(std::shared_ptr<AllocatorWrapper> allocator_wrapper,
OffsetAllocationHandle handle)
: m_allocator_wrapper(std::move(allocator_wrapper)),
m_handle(std::move(handle)) {}
AllocationHandleWrapper(AllocationHandleWrapper&& other) noexcept
: m_allocator_wrapper(std::move(other.m_allocator_wrapper)),
m_handle(std::move(other.m_handle)) {}
AllocationHandleWrapper& operator=(
AllocationHandleWrapper&& other) noexcept;
AllocationHandleWrapper(const AllocationHandleWrapper&) = delete;
AllocationHandleWrapper& operator=(const AllocationHandleWrapper&) = delete;
~AllocationHandleWrapper();
bool isValid() const { return m_handle.isValid(); }
uint64_t address() const { return m_handle.address(); }
uint64_t size() const { return m_handle.size(); }
OffsetAllocationHandle& getHandle() { return m_handle; }
private:
std::shared_ptr<AllocatorWrapper> m_allocator_wrapper;
OffsetAllocationHandle m_handle;
};
class AllocatorWrapper : public std::enable_shared_from_this<AllocatorWrapper> {
public:
static std::shared_ptr<AllocatorWrapper> create(uint64_t base, size_t size,
uint32 max_capacity) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint32> init_capacity_dist(1,
max_capacity);
uint32 init_capacity = init_capacity_dist(gen);
return std::shared_ptr<AllocatorWrapper>(
new AllocatorWrapper(base, size, init_capacity, max_capacity));
}
static std::shared_ptr<AllocatorWrapper> create(uint64_t base, size_t size,
uint32 init_capacity,
uint32 max_capacity) {
return std::shared_ptr<AllocatorWrapper>(
new AllocatorWrapper(base, size, init_capacity, max_capacity));
}
AllocatorWrapper(const AllocatorWrapper&) = delete;
AllocatorWrapper& operator=(const AllocatorWrapper&) = delete;
AllocatorWrapper(AllocatorWrapper&& other) = default;
AllocatorWrapper& operator=(AllocatorWrapper&& other) = default;
~AllocatorWrapper() = default;
std::optional<AllocationHandleWrapper> allocate(size_t size) {
if (!m_allocator) {
return std::nullopt;
}
auto handle = m_allocator->allocate(size);
if (!handle.has_value()) {
return std::nullopt;
}
EXPECT_EQ(handle->size(), size)
<< "Allocation size mismatch: " << handle->size() << " != " << size;
verifyAllocation(handle->address(), handle->address() + handle->size());
m_allocated_regions[handle->address()] = {
handle->address(), handle->address() + handle->size()};
return AllocationHandleWrapper(shared_from_this(), std::move(*handle));
}
void substituteAllocator(std::shared_ptr<OffsetAllocator> allocator) {
m_allocator = std::move(allocator);
}
std::shared_ptr<OffsetAllocator> getAllocator() const {
return m_allocator;
}
OffsetAllocStorageReport storageReport() const {
return m_allocator->storageReport();
}
OffsetAllocatorMetrics getMetrics() const {
return m_allocator->get_metrics();
}
private:
AllocatorWrapper(uint64_t base, size_t size, uint32 maxAllocs = 128 * 1024)
: m_allocator(
OffsetAllocator::create(base, size, maxAllocs / 2, maxAllocs)),
m_base(base),
m_buffer_size(size) {
}
AllocatorWrapper(uint64_t base, size_t size, uint32 init_capacity,
uint32 max_capacity)
: m_allocator(
OffsetAllocator::create(base, size, init_capacity, max_capacity)),
m_base(base),
m_buffer_size(size) {
}
void onHandleDeallocated(uint64_t address, uint64_t size) {
ASSERT_TRUE(m_allocated_regions.find(address) !=
m_allocated_regions.end())
<< "Allocation not found in tracking: " << address;
ASSERT_EQ(m_allocated_regions[address].end, address + size)
<< "Allocation size mismatch: " << m_allocated_regions[address].end
<< " != " << address + size;
m_allocated_regions.erase(address);
}
void verifyAllocation(uint64_t begin, uint64_t end) const {
ASSERT_TRUE(begin >= m_base && end <= m_base + m_buffer_size)
<< "Allocation is out of bounds: " << "Begin: " << begin
<< ", End: " << end << ", Base: " << m_base
<< ", Buffer Size: " << m_buffer_size;
auto it = m_allocated_regions.lower_bound(begin);
if (it != m_allocated_regions.begin()) {
auto prev_it = std::prev(it);
if (prev_it->second.end > begin) {
ASSERT_TRUE(false)
<< "Allocation overlaps with previous region: "
<< "New allocation [" << begin << ", " << end << ") "
<< "overlaps with existing region ["
<< prev_it->second.begin << ", " << prev_it->second.end
<< ")";
}
}
if (it != m_allocated_regions.end() && it->second.begin < end) {
ASSERT_TRUE(false)
<< "Allocation overlaps with current region: "
<< "New allocation [" << begin << ", " << end << ") "
<< "overlaps with existing region [" << it->second.begin << ", "
<< it->second.end << ")";
}
}
struct AllocatedRegion {
uint64_t begin;
uint64_t end;
};
std::shared_ptr<OffsetAllocator> m_allocator;
uint64_t m_base;
uint64_t m_buffer_size;
std::map<uint64_t, AllocatedRegion> m_allocated_regions;
friend class AllocationHandleWrapper;
};
AllocationHandleWrapper::~AllocationHandleWrapper() {
if (m_allocator_wrapper && m_handle.isValid()) {
m_allocator_wrapper->onHandleDeallocated(m_handle.address(),
m_handle.size());
}
}
AllocationHandleWrapper& AllocationHandleWrapper::operator=(
AllocationHandleWrapper&& other) noexcept {
if (this != &other) {
if (m_allocator_wrapper && m_handle.isValid()) {
m_allocator_wrapper->onHandleDeallocated(m_handle.address(),
m_handle.size());
}
m_allocator_wrapper = std::move(other.m_allocator_wrapper);
m_handle = std::move(other.m_handle);
}
return *this;
}
class OffsetAllocatorTest : public ::testing::Test {
protected:
void SetUp() override {}
void TearDown() override {}
OffsetAllocationHandle copyHandleWithNewAllocator(
const OffsetAllocationHandle& handle,
const std::shared_ptr<OffsetAllocator>& new_allocator) {
return OffsetAllocationHandle(new_allocator, handle.m_allocation,
handle.real_base, handle.requested_size);
}
void substituteWithNewAllocator(
AllocationHandleWrapper& handle,
std::shared_ptr<OffsetAllocator> new_allocator) {
handle.getHandle().m_allocator = new_allocator;
}
void assertAllocatorEQ(const std::shared_ptr<OffsetAllocator>& a,
const std::shared_ptr<OffsetAllocator>& b) {
MutexLocker lock_a(&a->m_mutex);
MutexLocker lock_b(&b->m_mutex);
ASSERT_EQ(a->m_base, b->m_base);
ASSERT_EQ(a->m_multiplier_bits, b->m_multiplier_bits);
ASSERT_EQ(a->m_capacity, b->m_capacity);
ASSERT_EQ(a->m_allocated_size, b->m_allocated_size);
ASSERT_EQ(a->m_allocated_num, b->m_allocated_num);
ASSERT_EQ(a->m_allocator->m_size, b->m_allocator->m_size);
ASSERT_EQ(a->m_allocator->m_current_capacity,
b->m_allocator->m_current_capacity);
ASSERT_EQ(a->m_allocator->m_max_capacity,
b->m_allocator->m_max_capacity);
ASSERT_EQ(a->m_allocator->m_freeStorage, b->m_allocator->m_freeStorage);
ASSERT_EQ(a->m_allocator->m_usedBinsTop, b->m_allocator->m_usedBinsTop);
ASSERT_EQ(a->m_allocator->m_freeOffset, b->m_allocator->m_freeOffset);
for (uint32 i = 0; i < NUM_TOP_BINS; ++i) {
ASSERT_EQ(a->m_allocator->m_usedBins[i],
b->m_allocator->m_usedBins[i]);
}
for (uint32 i = 0; i < NUM_LEAF_BINS; ++i) {
ASSERT_EQ(a->m_allocator->m_binIndices[i],
b->m_allocator->m_binIndices[i]);
}
for (uint32 i = 0; i < a->m_allocator->m_current_capacity; ++i) {
ASSERT_EQ(a->m_allocator->m_nodes[i].dataOffset,
b->m_allocator->m_nodes[i].dataOffset);
ASSERT_EQ(a->m_allocator->m_nodes[i].dataSize,
b->m_allocator->m_nodes[i].dataSize);
ASSERT_EQ(a->m_allocator->m_nodes[i].binListPrev,
b->m_allocator->m_nodes[i].binListPrev);
ASSERT_EQ(a->m_allocator->m_nodes[i].binListNext,
b->m_allocator->m_nodes[i].binListNext);
ASSERT_EQ(a->m_allocator->m_nodes[i].neighborPrev,
b->m_allocator->m_nodes[i].neighborPrev);
ASSERT_EQ(a->m_allocator->m_nodes[i].neighborNext,
b->m_allocator->m_nodes[i].neighborNext);
ASSERT_EQ(a->m_allocator->m_nodes[i].used,
b->m_allocator->m_nodes[i].used);
}
for (uint32 i = 0; i < a->m_allocator->m_current_capacity; ++i) {
ASSERT_EQ(a->m_allocator->m_freeNodes[i],
b->m_allocator->m_freeNodes[i]);
}
}
bool isAllocatorEqual(const std::shared_ptr<OffsetAllocator>& a,
const std::shared_ptr<OffsetAllocator>& b) {
MutexLocker lock_a(&a->m_mutex);
MutexLocker lock_b(&b->m_mutex);
if (memcmp(&a->m_base, &b->m_base, sizeof(a->m_base)) != 0)
return false;
if (memcmp(&a->m_multiplier_bits, &b->m_multiplier_bits,
sizeof(a->m_multiplier_bits)) != 0)
return false;
if (memcmp(&a->m_capacity, &b->m_capacity, sizeof(a->m_capacity)) != 0)
return false;
if (memcmp(&a->m_allocated_size, &b->m_allocated_size,
sizeof(a->m_allocated_size)) != 0)
return false;
if (memcmp(&a->m_allocated_num, &b->m_allocated_num,
sizeof(a->m_allocated_num)) != 0)
return false;
if (memcmp(&a->m_allocator->m_size, &b->m_allocator->m_size,
sizeof(a->m_allocator->m_size)) != 0)
return false;
if (memcmp(&a->m_allocator->m_current_capacity,
&b->m_allocator->m_current_capacity,
sizeof(a->m_allocator->m_current_capacity)) != 0)
return false;
if (memcmp(&a->m_allocator->m_max_capacity,
&b->m_allocator->m_max_capacity,
sizeof(a->m_allocator->m_max_capacity)) != 0)
return false;
if (memcmp(&a->m_allocator->m_freeStorage,
&b->m_allocator->m_freeStorage,
sizeof(a->m_allocator->m_freeStorage)) != 0)
return false;
if (memcmp(&a->m_allocator->m_usedBinsTop,
&b->m_allocator->m_usedBinsTop,
sizeof(a->m_allocator->m_usedBinsTop)) != 0)
return false;
if (memcmp(&a->m_allocator->m_freeOffset, &b->m_allocator->m_freeOffset,
sizeof(a->m_allocator->m_freeOffset)) != 0)
return false;
if (memcmp(a->m_allocator->m_usedBins, b->m_allocator->m_usedBins,
NUM_TOP_BINS * sizeof(a->m_allocator->m_usedBins[0])) != 0)
return false;
if (memcmp(a->m_allocator->m_binIndices, b->m_allocator->m_binIndices,
NUM_LEAF_BINS * sizeof(a->m_allocator->m_binIndices[0])) !=
0)
return false;
if (memcmp(a->m_allocator->m_nodes, b->m_allocator->m_nodes,
a->m_allocator->m_current_capacity *
sizeof(a->m_allocator->m_nodes[0])) != 0)
return false;
if (memcmp(a->m_allocator->m_freeNodes, b->m_allocator->m_freeNodes,
a->m_allocator->m_current_capacity *
sizeof(a->m_allocator->m_freeNodes[0])) != 0)
return false;
return true;
}
void testSerializeAllocator(
const std::shared_ptr<OffsetAllocator>& alloc_a,
const std::vector<OffsetAllocationHandle>& handles) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint32> size_dist(
1,
1024 * 64);
std::vector<SerializedByte> buffer;
ASSERT_EQ(serialize_to(alloc_a, buffer), ErrorCode::OK);
std::shared_ptr<OffsetAllocator> alloc_b =
deserialize_from<OffsetAllocator>(buffer);
ASSERT_NE(alloc_b, nullptr);
assertAllocatorEQ(alloc_a, alloc_b);
auto log_level = FLAGS_minloglevel;
FLAGS_minloglevel = google::GLOG_FATAL;
auto corrupted_buffer = buffer;
corrupted_buffer.pop_back();
alloc_b = deserialize_from<OffsetAllocator>(corrupted_buffer);
ASSERT_TRUE(alloc_b == nullptr || !isAllocatorEqual(alloc_a, alloc_b));
corrupted_buffer = buffer;
corrupted_buffer.push_back(0);
alloc_b = deserialize_from<OffsetAllocator>(corrupted_buffer);
ASSERT_TRUE(alloc_b == nullptr || !isAllocatorEqual(alloc_a, alloc_b));
FLAGS_minloglevel = log_level;
alloc_b = deserialize_from<OffsetAllocator>(buffer);
ASSERT_TRUE(alloc_b != nullptr);
for (const auto& handle : handles) {
OffsetAllocationHandle handle_copy =
copyHandleWithNewAllocator(handle, alloc_b);
}
}
};
TEST_F(OffsetAllocatorTest, BasicAllocation) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle = allocator->allocate(ALLOCATOR_SIZE);
ASSERT_TRUE(handle.has_value());
EXPECT_TRUE(handle->isValid());
EXPECT_NE(handle->address(), OffsetAllocation::NO_SPACE);
EXPECT_EQ(handle->size(), ALLOCATOR_SIZE);
auto handle2 = allocator->allocate(ALLOCATOR_SIZE);
ASSERT_FALSE(handle2.has_value());
handle.reset();
handle2 = allocator->allocate(ALLOCATOR_SIZE);
ASSERT_TRUE(handle2.has_value());
EXPECT_TRUE(handle2->isValid());
EXPECT_NE(handle2->address(), OffsetAllocation::NO_SPACE);
}
TEST_F(OffsetAllocatorTest, AllocationFailure) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle =
allocator->allocate(2 * ALLOCATOR_SIZE);
EXPECT_FALSE(handle.has_value());
}
TEST_F(OffsetAllocatorTest, MultipleAllocations) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<AllocationHandleWrapper> handles;
for (int i = 0; i < 10; ++i) {
auto handle = allocator->allocate(1000);
ASSERT_TRUE(handle.has_value());
handles.push_back(std::move(*handle));
}
for (size_t i = 0; i < handles.size(); ++i) {
EXPECT_TRUE(handles[i].isValid());
EXPECT_EQ(handles[i].size(), 1000);
for (size_t j = i + 1; j < handles.size(); ++j) {
EXPECT_NE(handles[i].address(), handles[j].address());
}
}
}
TEST_F(OffsetAllocatorTest, DifferentSizesNoOverlap) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<AllocationHandleWrapper> handles;
std::vector<uint32> sizes = {100, 500, 1000, 2000, 50, 1500, 800, 300};
for (uint32 size : sizes) {
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value()) << "Failed to allocate size: " << size;
EXPECT_EQ(handle->size(), size);
handles.push_back(std::move(*handle));
}
for (const auto& handle : handles) {
EXPECT_TRUE(handle.isValid());
}
}
TEST_F(OffsetAllocatorTest, StorageReports) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
OffsetAllocStorageReport report = allocator->storageReport();
EXPECT_GT(report.totalFreeSpace, 0);
EXPECT_GT(report.largestFreeRegion, 0);
auto handle = allocator->allocate(1000);
ASSERT_TRUE(handle.has_value());
OffsetAllocStorageReport newReport = allocator->storageReport();
EXPECT_LT(newReport.totalFreeSpace, report.totalFreeSpace);
}
TEST_F(OffsetAllocatorTest, ContinuousRandomAllocationDeallocation) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint32> size_dist(1,
1024 * 64);
const int max_iterations = 20000;
for (int i = 0; i < max_iterations; ++i) {
uint32_t size = size_dist(gen);
auto handle = allocator->allocate(size);
EXPECT_TRUE(handle.has_value()) << "Failed to allocate size: " << size;
}
auto full_space_handle = allocator->allocate(ALLOCATOR_SIZE);
ASSERT_TRUE(full_space_handle.has_value());
EXPECT_EQ(full_space_handle->size(), ALLOCATOR_SIZE);
}
TEST_F(OffsetAllocatorTest, FullSizeAllocation) {
for (uint32 size : bin_sizes) {
if (size == 0) continue;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, size, MAX_ALLOCS);
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value());
}
}
TEST_F(OffsetAllocatorTest, RepeatedLargeSizeAllocation) {
for (size_t i = 0; i < NUM_BINS; ++i) {
uint32_t bin_size = bin_sizes[i];
if (bin_size < 1024) continue;
constexpr uint32_t MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, bin_size + 10, MAX_ALLOCS);
EXPECT_EQ(allocator->storageReport().totalFreeSpace, bin_size + 10);
for (uint32_t j = 0; j < 10; j++) {
auto handle = allocator->allocate(bin_size - (10 - j));
ASSERT_TRUE(handle.has_value());
}
}
}
TEST_F(OffsetAllocatorTest, MaxNumAllocations) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<AllocationHandleWrapper> handles;
for (uint32 i = 0; i < MAX_ALLOCS - 1; ++i) {
auto handle = allocator->allocate(1024);
ASSERT_TRUE(handle.has_value())
<< "Failed to allocate size: " << 1024 << " at iteration: " << i;
handles.push_back(std::move(*handle));
}
auto handle = allocator->allocate(1024);
ASSERT_FALSE(handle.has_value());
}
TEST_F(OffsetAllocatorTest, FullAllocationAfterRandomAllocationAndFree) {
const uint32 ALLOCATOR_SIZE = bin_sizes[NUM_BINS - 1];
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint32> size_dist(1, ALLOCATOR_SIZE / 1000);
std::vector<AllocationHandleWrapper> handles;
for (uint32 i = 0; i < MAX_ALLOCS; ++i) {
uint32 size = size_dist(gen);
auto handle = allocator->allocate(size);
if (handle.has_value()) {
handles.push_back(std::move(*handle));
}
}
handles.clear();
auto handle = allocator->allocate(ALLOCATOR_SIZE);
ASSERT_TRUE(handle.has_value());
}
TEST_F(OffsetAllocatorTest, AllocationSameSizeAfterFree) {
constexpr uint32 ALLOCATOR_SIZE = 2048;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle = allocator->allocate(1023);
ASSERT_TRUE(handle.has_value());
auto handle2 = allocator->allocate(16);
ASSERT_TRUE(handle2.has_value());
handle.reset();
handle = allocator->allocate(1023);
ASSERT_TRUE(handle.has_value());
}
TEST_F(OffsetAllocatorTest, RandomRepeatAllocationSameSizeAfterFree) {
const uint32 ALLOCATOR_SIZE = bin_sizes[NUM_BINS - 1];
constexpr uint32 MAX_ALLOCS = 10000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<uint32> size_dist(1, ALLOCATOR_SIZE / 100);
std::vector<AllocationHandleWrapper> handles;
std::vector<uint32> alloc_sizes;
for (int i = 0; i < 2000; ++i) {
uint32 size = size_dist(gen);
auto handle = allocator->allocate(size);
if (handle.has_value()) {
handles.push_back(std::move(*handle));
alloc_sizes.push_back(size);
}
std::uniform_int_distribution<uint32> index_dist(0, handles.size() - 1);
uint32 index = index_dist(gen);
std::swap(handles[index], handles.back());
std::swap(alloc_sizes[index], alloc_sizes.back());
uint32 test_size = alloc_sizes.back();
handles.pop_back();
alloc_sizes.pop_back();
auto handle2 = allocator->allocate(test_size);
ASSERT_TRUE(handle2.has_value());
handles.push_back(std::move(*handle2));
alloc_sizes.push_back(test_size);
}
}
TEST_F(OffsetAllocatorTest, BasicLargeAllocatorSize) {
constexpr size_t MIN_BUFFER_SIZE = (1ull << 30);
constexpr size_t MAX_BUFFER_SIZE = (1ull << 40);
constexpr uint32 MAX_ALLOCS = 10000;
for (size_t buffer_size = MIN_BUFFER_SIZE; buffer_size <= MAX_BUFFER_SIZE;
buffer_size *= 2) {
auto allocator = AllocatorWrapper::create(0, buffer_size, MAX_ALLOCS);
size_t max_alloc_size = allocator->storageReport().largestFreeRegion;
ASSERT_EQ(max_alloc_size, buffer_size);
auto handle = allocator->allocate(1);
ASSERT_TRUE(handle.has_value());
handle.reset();
handle = allocator->allocate(max_alloc_size - 1);
ASSERT_TRUE(handle.has_value());
handle.reset();
handle = allocator->allocate(max_alloc_size);
ASSERT_TRUE(handle.has_value());
EXPECT_EQ(handle->size(), max_alloc_size);
}
}
TEST_F(OffsetAllocatorTest, PowerOfTwoLargeAllocatorSize) {
constexpr size_t MIN_BUFFER_SIZE = (1ull << 30);
constexpr size_t MAX_BUFFER_SIZE = (1ull << 40);
constexpr uint32 MAX_ALLOCS = 10000;
std::random_device rd;
std::mt19937 gen(rd());
for (size_t buffer_size = MIN_BUFFER_SIZE; buffer_size <= MAX_BUFFER_SIZE;
buffer_size *= 2) {
auto allocator = AllocatorWrapper::create(0, buffer_size, MAX_ALLOCS);
size_t max_alloc_size = buffer_size / 100;
std::vector<AllocationHandleWrapper> handles;
std::vector<size_t> alloc_sizes;
std::uniform_int_distribution<size_t> size_dist(1, max_alloc_size);
for (int i = 0; i < 200; ++i) {
size_t size = size_dist(gen);
auto handle = allocator->allocate(size);
if (handle.has_value()) {
handles.push_back(std::move(*handle));
alloc_sizes.push_back(size);
}
std::uniform_int_distribution<uint32> index_dist(
0, handles.size() - 1);
uint32 index = index_dist(gen);
std::swap(handles[index], handles.back());
std::swap(alloc_sizes[index], alloc_sizes.back());
uint32 test_size = alloc_sizes.back();
handles.pop_back();
alloc_sizes.pop_back();
auto handle2 = allocator->allocate(test_size);
ASSERT_TRUE(handle2.has_value());
handles.push_back(std::move(*handle2));
alloc_sizes.push_back(test_size);
}
}
}
TEST_F(OffsetAllocatorTest, MaxAllocSizeWithLargeAllocatorSize) {
constexpr size_t MIN_BUFFER_SIZE = (1ull << 31) + 1;
constexpr size_t MAX_BUFFER_SIZE = (1ull << 40);
constexpr uint32 MAX_ALLOCS = 10000;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<size_t> buffer_size_dist(MIN_BUFFER_SIZE,
MAX_BUFFER_SIZE);
for (int i = 0; i < 100; i++) {
size_t buffer_size = buffer_size_dist(gen);
auto allocator = AllocatorWrapper::create(0, buffer_size, MAX_ALLOCS);
size_t max_alloc_size = allocator->storageReport().largestFreeRegion;
ASSERT_GT(max_alloc_size, buffer_size / 2);
auto handle = allocator->allocate(max_alloc_size);
ASSERT_TRUE(handle.has_value());
}
}
TEST_F(OffsetAllocatorTest, RandomSmallAllocWithLargeAllocatorSize) {
constexpr size_t MIN_BUFFER_SIZE = (1ull << 31) + 1;
constexpr size_t MAX_BUFFER_SIZE = (1ull << 40);
constexpr uint32 MAX_ALLOCS = 10000;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<size_t> buffer_size_dist(MIN_BUFFER_SIZE,
MAX_BUFFER_SIZE);
for (int i = 0; i < 100; i++) {
size_t buffer_size = buffer_size_dist(gen);
auto allocator = AllocatorWrapper::create(0, buffer_size, MAX_ALLOCS);
size_t max_alloc_size = buffer_size / 100;
std::vector<AllocationHandleWrapper> handles;
std::vector<size_t> alloc_sizes;
std::uniform_int_distribution<size_t> size_dist(1, max_alloc_size);
for (int j = 0; j < 200; ++j) {
size_t size = size_dist(gen);
auto handle = allocator->allocate(size);
if (handle.has_value()) {
handles.push_back(std::move(*handle));
alloc_sizes.push_back(size);
}
std::uniform_int_distribution<uint32> index_dist(
0, handles.size() - 1);
uint32 index = index_dist(gen);
std::swap(handles[index], handles.back());
std::swap(alloc_sizes[index], alloc_sizes.back());
uint32 test_size = alloc_sizes.back();
handles.pop_back();
alloc_sizes.pop_back();
auto handle2 = allocator->allocate(test_size);
ASSERT_TRUE(handle2.has_value());
handles.push_back(std::move(*handle2));
alloc_sizes.push_back(test_size);
}
}
}
TEST_F(OffsetAllocatorTest, ZeroSizeAllocation) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle = allocator->allocate(0);
EXPECT_FALSE(handle.has_value()) << "Zero size allocation should fail";
}
TEST_F(OffsetAllocatorTest, OneByteAllocation) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle = allocator->allocate(1);
ASSERT_TRUE(handle.has_value());
EXPECT_TRUE(handle->isValid());
EXPECT_EQ(handle->size(), 1);
EXPECT_NE(handle->address(), OffsetAllocation::NO_SPACE);
}
TEST_F(OffsetAllocatorTest, ExactCapacityAllocation) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle = allocator->allocate(ALLOCATOR_SIZE);
ASSERT_TRUE(handle.has_value());
EXPECT_EQ(handle->size(), ALLOCATOR_SIZE);
EXPECT_EQ(handle->address(), 0);
auto handle2 = allocator->allocate(1);
EXPECT_FALSE(handle2.has_value());
}
TEST_F(OffsetAllocatorTest, OversizeAllocation) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle = allocator->allocate(ALLOCATOR_SIZE + 1);
EXPECT_FALSE(handle.has_value())
<< "Allocation larger than capacity should fail";
}
TEST_F(OffsetAllocatorTest, JustBelowBinSizeAllocation) {
constexpr uint32 ALLOCATOR_SIZE = 2048;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle = allocator->allocate(1023);
ASSERT_TRUE(handle.has_value());
EXPECT_EQ(handle->size(), 1023);
auto handle2 = allocator->allocate(1024);
ASSERT_TRUE(handle2.has_value());
EXPECT_EQ(handle2->size(), 1024);
}
TEST_F(OffsetAllocatorTest, MaxAllocationCountEdgeCase) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 10;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<AllocationHandleWrapper> handles;
for (uint32 i = 0; i < MAX_ALLOCS - 1; ++i) {
auto handle = allocator->allocate(1024);
ASSERT_TRUE(handle.has_value()) << "Failed at iteration " << i;
handles.push_back(std::move(*handle));
}
auto handle = allocator->allocate(1024);
EXPECT_FALSE(handle.has_value()) << "Should fail at max allocation count";
handles.pop_back();
handle = allocator->allocate(1024);
EXPECT_TRUE(handle.has_value());
}
TEST_F(OffsetAllocatorTest, VerySmallAllocatorSize) {
constexpr uint32 ALLOCATOR_SIZE = 16;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle = allocator->allocate(16);
ASSERT_TRUE(handle.has_value());
EXPECT_EQ(handle->size(), 16);
auto handle2 = allocator->allocate(1);
EXPECT_FALSE(handle2.has_value());
}
TEST_F(OffsetAllocatorTest, AllocatorSizeMinusOne) {
constexpr uint32 ALLOCATOR_SIZE = 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle = allocator->allocate(ALLOCATOR_SIZE - 1);
ASSERT_TRUE(handle.has_value());
EXPECT_EQ(handle->size(), ALLOCATOR_SIZE - 1);
}
TEST_F(OffsetAllocatorTest, PowerOfTwoAllocation) {
constexpr uint32 ALLOCATOR_SIZE = 2048;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<uint32> power_of_two_sizes = {1, 2, 4, 8, 16, 32,
64, 128, 256, 512, 1024};
for (uint32 size : power_of_two_sizes) {
if (size <= ALLOCATOR_SIZE) {
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value())
<< "Failed to allocate size: " << size;
EXPECT_EQ(handle->size(), size);
}
}
}
TEST_F(OffsetAllocatorTest, BinSizeCalculation) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle1 = allocator->allocate(100);
ASSERT_TRUE(handle1.has_value());
auto handle2 = allocator->allocate(200);
ASSERT_TRUE(handle2.has_value());
auto handle3 = allocator->allocate(500);
ASSERT_TRUE(handle3.has_value());
EXPECT_TRUE(handle1->isValid());
EXPECT_TRUE(handle2->isValid());
EXPECT_TRUE(handle3->isValid());
}
TEST_F(OffsetAllocatorTest, BinOverflowScenarios) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<AllocationHandleWrapper> handles;
uint32 small_size = 64;
for (int i = 0; i < 100; ++i) {
auto handle = allocator->allocate(small_size);
if (handle.has_value()) {
handles.push_back(std::move(*handle));
} else {
break;
}
}
for (const auto& handle : handles) {
EXPECT_TRUE(handle.isValid());
EXPECT_EQ(handle.size(), small_size);
}
}
TEST_F(OffsetAllocatorTest, BinMergingBehavior) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
auto handle1 = allocator->allocate(1024);
auto handle2 = allocator->allocate(1024);
auto handle3 = allocator->allocate(1024);
ASSERT_TRUE(handle1.has_value());
ASSERT_TRUE(handle2.has_value());
ASSERT_TRUE(handle3.has_value());
handle2 = std::nullopt;
handle1 = std::nullopt;
handle3 = std::nullopt;
auto large_handle = allocator->allocate(ALLOCATOR_SIZE);
ASSERT_TRUE(large_handle.has_value());
EXPECT_EQ(large_handle->size(), ALLOCATOR_SIZE);
}
TEST_F(OffsetAllocatorTest, BinSelectionEdgeCases) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<uint32> edge_sizes = {
1,
2,
3,
7,
8,
15,
16,
31,
32,
63,
64,
127,
128,
255,
256,
511,
512,
1023,
1024,
};
for (uint32 size : edge_sizes) {
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value()) << "Failed to allocate size: " << size;
EXPECT_EQ(handle->size(), size);
}
}
TEST_F(OffsetAllocatorTest, BinSystemLargeAllocations) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<uint32> large_sizes = {
1024 * 1024,
2 * 1024 * 1024,
4 * 1024 * 1024,
8 * 1024 * 1024,
16 * 1024 * 1024,
32 * 1024 * 1024,
64 * 1024 * 1024,
128 * 1024 * 1024,
256 * 1024 * 1024,
512 * 1024 * 1024,
};
for (uint32 size : large_sizes) {
if (size <= ALLOCATOR_SIZE) {
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value())
<< "Failed to allocate size: " << size;
EXPECT_EQ(handle->size(), size);
}
}
}
TEST_F(OffsetAllocatorTest, BinSystemMixedPatterns) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<AllocationHandleWrapper> handles;
std::vector<uint32> mixed_sizes = {16, 64, 256, 1024, 4096, 16384, 65536};
for (uint32 size : mixed_sizes) {
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value()) << "Failed to allocate size: " << size;
EXPECT_EQ(handle->size(), size);
handles.push_back(std::move(*handle));
}
for (const auto& handle : handles) {
EXPECT_TRUE(handle.isValid());
}
}
TEST_F(OffsetAllocatorTest, BinSystemRepeatedCycles) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<uint32> test_sizes = {64, 128, 256, 512, 1024, 2048, 4096};
for (int cycle = 0; cycle < 10; ++cycle) {
std::vector<AllocationHandleWrapper> cycle_handles;
for (uint32 size : test_sizes) {
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value())
<< "Failed to allocate size: " << size << " in cycle " << cycle;
EXPECT_EQ(handle->size(), size);
cycle_handles.push_back(std::move(*handle));
}
for (const auto& handle : cycle_handles) {
EXPECT_TRUE(handle.isValid());
}
}
auto full_handle = allocator->allocate(ALLOCATOR_SIZE);
ASSERT_TRUE(full_handle.has_value());
EXPECT_EQ(full_handle->size(), ALLOCATOR_SIZE);
}
TEST_F(OffsetAllocatorTest, BinSystemNonAlignedSizes) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<uint32> non_aligned_sizes = {
17,
33,
65,
129,
257,
513,
1025,
2049,
4097,
};
for (uint32 size : non_aligned_sizes) {
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value()) << "Failed to allocate size: " << size;
EXPECT_EQ(handle->size(), size);
}
}
TEST_F(OffsetAllocatorTest, BinSystemPrimeSizes) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<uint32> prime_sizes = {
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43,
47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107,
109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181,
191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263,
269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349,
353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433,
439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521,
523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613,
617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701,
709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809,
811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887,
907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997,
1009, 1013};
for (uint32 size : prime_sizes) {
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value())
<< "Failed to allocate prime size: " << size;
EXPECT_EQ(handle->size(), size);
}
}
TEST_F(OffsetAllocatorTest, BinSystemFibonacciSizes) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<uint32> fibonacci_sizes = {
1, 1, 2, 3, 5, 8, 13, 21, 34, 55,
89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765,
10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811};
for (uint32 size : fibonacci_sizes) {
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value())
<< "Failed to allocate Fibonacci size: " << size;
EXPECT_EQ(handle->size(), size);
}
}
TEST_F(OffsetAllocatorTest, BinSystemPageSizeMultiples) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
std::vector<uint32> page_size_multiples = {
4096,
8192,
16384,
32768,
65536,
131072,
262144,
524288,
1048576
};
for (uint32 size : page_size_multiples) {
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value())
<< "Failed to allocate page size multiple: " << size;
EXPECT_EQ(handle->size(), size);
}
}
TEST_F(OffsetAllocatorTest, MetricsInterface) {
constexpr uint32 ALLOCATOR_SIZE = 1024 * 1024;
constexpr uint32 MAX_ALLOCS = 1000;
auto allocator = AllocatorWrapper::create(0, ALLOCATOR_SIZE, MAX_ALLOCS);
OffsetAllocatorMetrics initial_metrics = allocator->getMetrics();
EXPECT_EQ(initial_metrics.allocated_size_, 0);
EXPECT_EQ(initial_metrics.allocated_num_, 0);
EXPECT_EQ(initial_metrics.capacity, ALLOCATOR_SIZE);
EXPECT_GT(initial_metrics.total_free_space_, 0);
EXPECT_GT(initial_metrics.largest_free_region_, 0);
EXPECT_EQ(initial_metrics.total_free_space_, ALLOCATOR_SIZE);
constexpr size_t ALLOC_SIZE_1 = 1024;
auto handle1 = allocator->allocate(ALLOC_SIZE_1);
ASSERT_TRUE(handle1.has_value());
OffsetAllocatorMetrics after_first_alloc = allocator->getMetrics();
EXPECT_EQ(after_first_alloc.allocated_size_, ALLOC_SIZE_1);
EXPECT_EQ(after_first_alloc.allocated_num_, 1);
EXPECT_EQ(after_first_alloc.capacity, ALLOCATOR_SIZE);
EXPECT_LT(after_first_alloc.total_free_space_,
initial_metrics.total_free_space_);
EXPECT_EQ(after_first_alloc.total_free_space_,
ALLOCATOR_SIZE - ALLOC_SIZE_1);
constexpr size_t ALLOC_SIZE_2 = 2048;
auto handle2 = allocator->allocate(ALLOC_SIZE_2);
ASSERT_TRUE(handle2.has_value());
OffsetAllocatorMetrics after_second_alloc = allocator->getMetrics();
EXPECT_EQ(after_second_alloc.allocated_size_, ALLOC_SIZE_1 + ALLOC_SIZE_2);
EXPECT_EQ(after_second_alloc.allocated_num_, 2);
EXPECT_EQ(after_second_alloc.capacity, ALLOCATOR_SIZE);
EXPECT_LT(after_second_alloc.total_free_space_,
after_first_alloc.total_free_space_);
EXPECT_EQ(after_second_alloc.total_free_space_,
ALLOCATOR_SIZE - ALLOC_SIZE_1 - ALLOC_SIZE_2);
handle1.reset();
OffsetAllocatorMetrics after_first_free = allocator->getMetrics();
EXPECT_EQ(after_first_free.allocated_size_, ALLOC_SIZE_2);
EXPECT_EQ(after_first_free.allocated_num_, 1);
EXPECT_EQ(after_first_free.capacity, ALLOCATOR_SIZE);
EXPECT_GT(after_first_free.total_free_space_,
after_second_alloc.total_free_space_);
EXPECT_EQ(after_first_free.total_free_space_,
ALLOCATOR_SIZE - ALLOC_SIZE_2);
handle2.reset();
OffsetAllocatorMetrics after_all_free = allocator->getMetrics();
EXPECT_EQ(after_all_free.allocated_size_, 0);
EXPECT_EQ(after_all_free.allocated_num_, 0);
EXPECT_EQ(after_all_free.capacity, ALLOCATOR_SIZE);
EXPECT_EQ(after_all_free.total_free_space_, ALLOCATOR_SIZE);
EXPECT_EQ(after_all_free.largest_free_region_, ALLOCATOR_SIZE);
}
TEST_F(OffsetAllocatorTest, SerializationEmptyAllocator) {
const uint64_t base = 1024 * 16;
const size_t size = 1024 * 1024;
const uint32_t init_capacity = 1000;
const uint32_t max_capacity = 10000;
std::shared_ptr<OffsetAllocator> alloc_a =
OffsetAllocator::create(base, size, init_capacity, max_capacity);
testSerializeAllocator(alloc_a, {});
}
TEST_F(OffsetAllocatorTest, SerializationOneElementAllocator) {
const uint64_t base = 1024 * 16;
const size_t size = 1024 * 1024;
const uint32_t init_capacity = 1;
const uint32_t max_capacity = 10000;
std::shared_ptr<OffsetAllocator> alloc_a =
OffsetAllocator::create(base, size, init_capacity, max_capacity);
auto handle = alloc_a->allocate(1024);
ASSERT_TRUE(handle.has_value());
std::vector<OffsetAllocationHandle> handles;
handles.push_back(std::move(*handle));
testSerializeAllocator(alloc_a, handles);
}
TEST_F(OffsetAllocatorTest, SerializationRandomAllocatedAllocator) {
constexpr size_t MIN_BUFFER_SIZE = (1ull << 31) + 1;
constexpr size_t MAX_BUFFER_SIZE = (1ull << 40);
constexpr uint32 MAX_ALLOCS = 10000;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<size_t> buffer_size_dist(MIN_BUFFER_SIZE,
MAX_BUFFER_SIZE);
for (int i = 0; i < 100; i++) {
size_t buffer_size = buffer_size_dist(gen);
auto alloc_a = OffsetAllocator::create(0, buffer_size, 1, MAX_ALLOCS);
size_t max_alloc_size = buffer_size / 100;
std::vector<OffsetAllocationHandle> handles;
std::vector<size_t> alloc_sizes;
std::uniform_int_distribution<size_t> size_dist(1, max_alloc_size);
for (int j = 0; j < 200; ++j) {
size_t size = size_dist(gen);
auto handle = alloc_a->allocate(size);
if (handle.has_value()) {
handles.push_back(std::move(*handle));
alloc_sizes.push_back(size);
}
std::uniform_int_distribution<uint32> index_dist(
0, handles.size() - 1);
uint32 index = index_dist(gen);
std::swap(handles[index], handles.back());
std::swap(alloc_sizes[index], alloc_sizes.back());
uint32 test_size = alloc_sizes.back();
handles.pop_back();
alloc_sizes.pop_back();
auto handle2 = alloc_a->allocate(test_size);
ASSERT_TRUE(handle2.has_value());
handles.push_back(std::move(*handle2));
alloc_sizes.push_back(test_size);
}
testSerializeAllocator(alloc_a, handles);
}
}
TEST_F(OffsetAllocatorTest, AllocateAfterDeserialization) {
const uint64_t base = 1024 * 16;
const size_t size = 1024 * 1024;
const uint32_t init_capacity = 10;
const uint32_t max_capacity = 10000;
std::shared_ptr<AllocatorWrapper> allocator =
AllocatorWrapper::create(base, size, init_capacity, max_capacity);
std::random_device rd;
std::mt19937 gen(rd());
std::vector<AllocationHandleWrapper> handles;
for (int i = 0; i < 100; i++) {
std::uniform_int_distribution<size_t> size_dist(1, 1024);
size_t size = size_dist(gen);
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value());
handles.push_back(std::move(*handle));
std::uniform_int_distribution<size_t> free_dist(0, 1);
if (free_dist(gen) == 1 && !handles.empty()) {
std::uniform_int_distribution<size_t> index_dist(
0, handles.size() - 1);
size_t random_index = index_dist(gen);
std::swap(handles[random_index], handles.back());
handles.pop_back();
}
}
std::vector<SerializedByte> buffer;
ASSERT_EQ(serialize_to(allocator->getAllocator(), buffer), ErrorCode::OK);
std::shared_ptr<OffsetAllocator> alloc_b =
deserialize_from<OffsetAllocator>(buffer);
ASSERT_NE(alloc_b, nullptr);
allocator->substituteAllocator(alloc_b);
for (auto& handle : handles) {
substituteWithNewAllocator(handle, alloc_b);
}
for (int i = 0; i < 100; i++) {
std::uniform_int_distribution<size_t> size_dist(1, 1024);
size_t size = size_dist(gen);
auto handle = allocator->allocate(size);
ASSERT_TRUE(handle.has_value());
handles.push_back(std::move(*handle));
std::uniform_int_distribution<size_t> free_dist(0, 1);
if (free_dist(gen) == 1 && !handles.empty()) {
std::uniform_int_distribution<size_t> index_dist(
0, handles.size() - 1);
size_t random_index = index_dist(gen);
std::swap(handles[random_index], handles.back());
handles.pop_back();
}
}
}
TEST_F(OffsetAllocatorTest, ChainedAllocationAndDeserialization) {
constexpr size_t MIN_BUFFER_SIZE = (1ull << 31) + 1;
constexpr size_t MAX_BUFFER_SIZE = (1ull << 40);
constexpr uint32 INIT_CAPACITY = 1;
constexpr uint32 MAX_ALLOCS = 10000;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<size_t> buffer_size_dist(MIN_BUFFER_SIZE,
MAX_BUFFER_SIZE);
for (int i = 0; i < 10; i++) {
size_t buffer_size = buffer_size_dist(gen);
auto allocator =
AllocatorWrapper::create(0, buffer_size, INIT_CAPACITY, MAX_ALLOCS);
size_t max_alloc_size = buffer_size / 100;
std::vector<AllocationHandleWrapper> handles;
std::vector<size_t> alloc_sizes;
std::uniform_int_distribution<size_t> size_dist(1, max_alloc_size);
for (int j = 0; j < 10; j++) {
for (int k = 0; k < 100; k++) {
size_t size = size_dist(gen);
auto handle = allocator->allocate(size);
if (handle.has_value()) {
handles.push_back(std::move(*handle));
alloc_sizes.push_back(size);
}
std::uniform_int_distribution<uint32> index_dist(
0, handles.size() - 1);
uint32 index = index_dist(gen);
std::swap(handles[index], handles.back());
std::swap(alloc_sizes[index], alloc_sizes.back());
uint32 test_size = alloc_sizes.back();
handles.pop_back();
alloc_sizes.pop_back();
auto handle2 = allocator->allocate(test_size);
ASSERT_TRUE(handle2.has_value());
handles.push_back(std::move(*handle2));
alloc_sizes.push_back(test_size);
}
}
std::vector<SerializedByte> buffer;
ASSERT_EQ(serialize_to(allocator->getAllocator(), buffer),
ErrorCode::OK);
std::shared_ptr<OffsetAllocator> new_alloc =
deserialize_from<OffsetAllocator>(buffer);
ASSERT_NE(new_alloc, nullptr);
assertAllocatorEQ(allocator->getAllocator(), new_alloc);
allocator->substituteAllocator(new_alloc);
for (auto& handle : handles) {
substituteWithNewAllocator(handle, new_alloc);
}
}
}
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}