359 lines
9.1 KiB
C++
359 lines
9.1 KiB
C++
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
|
#include "arena_allocator.hpp"
|
|
#include <cstring>
|
|
#include <doctest/doctest.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
TEST_CASE("ArenaAllocator basic construction") {
|
|
ArenaAllocator arena;
|
|
CHECK(arena.num_blocks() == 0);
|
|
CHECK(arena.used_bytes() == 0);
|
|
CHECK(arena.total_allocated() == 0);
|
|
CHECK(arena.available_in_current_block() == 0);
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator custom initial size") {
|
|
ArenaAllocator arena(2048);
|
|
CHECK(arena.num_blocks() == 0);
|
|
CHECK(arena.total_allocated() == 0);
|
|
CHECK(arena.available_in_current_block() == 0);
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator basic allocation") {
|
|
ArenaAllocator arena;
|
|
|
|
SUBCASE("allocate zero bytes returns nullptr") {
|
|
void *ptr = arena.allocate(0);
|
|
CHECK(ptr == nullptr);
|
|
CHECK(arena.used_bytes() == 0);
|
|
}
|
|
|
|
SUBCASE("allocate single byte") {
|
|
void *ptr = arena.allocate(1);
|
|
CHECK(ptr != nullptr);
|
|
CHECK(arena.used_bytes() >= 1);
|
|
}
|
|
|
|
SUBCASE("allocate multiple bytes") {
|
|
void *ptr1 = arena.allocate(100);
|
|
void *ptr2 = arena.allocate(200);
|
|
|
|
CHECK(ptr1 != nullptr);
|
|
CHECK(ptr2 != nullptr);
|
|
CHECK(ptr1 != ptr2);
|
|
CHECK(arena.used_bytes() >= 300);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator alignment") {
|
|
ArenaAllocator arena;
|
|
|
|
SUBCASE("default alignment") {
|
|
void *ptr = arena.allocate(1);
|
|
CHECK(reinterpret_cast<uintptr_t>(ptr) % alignof(std::max_align_t) == 0);
|
|
}
|
|
|
|
SUBCASE("custom alignment") {
|
|
void *ptr8 = arena.allocate(1, 8);
|
|
CHECK(reinterpret_cast<uintptr_t>(ptr8) % 8 == 0);
|
|
|
|
void *ptr16 = arena.allocate(1, 16);
|
|
CHECK(reinterpret_cast<uintptr_t>(ptr16) % 16 == 0);
|
|
|
|
void *ptr32 = arena.allocate(1, 32);
|
|
CHECK(reinterpret_cast<uintptr_t>(ptr32) % 32 == 0);
|
|
}
|
|
|
|
SUBCASE("alignment with larger allocations") {
|
|
ArenaAllocator fresh_arena;
|
|
void *ptr = fresh_arena.allocate(100, 64);
|
|
CHECK(reinterpret_cast<uintptr_t>(ptr) % 64 == 0);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator block management") {
|
|
ArenaAllocator arena(128);
|
|
|
|
SUBCASE("single block allocation") {
|
|
void *ptr = arena.allocate(64);
|
|
CHECK(ptr != nullptr);
|
|
CHECK(arena.num_blocks() == 1);
|
|
CHECK(arena.used_bytes() == 64);
|
|
}
|
|
|
|
SUBCASE("multiple blocks when size exceeded") {
|
|
void *ptr1 = arena.allocate(100);
|
|
CHECK(arena.num_blocks() == 1);
|
|
|
|
void *ptr2 = arena.allocate(50);
|
|
CHECK(arena.num_blocks() == 2);
|
|
CHECK(ptr1 != ptr2);
|
|
}
|
|
|
|
SUBCASE("allocation larger than block size grows arena") {
|
|
void *ptr = arena.allocate(200);
|
|
CHECK(ptr != nullptr);
|
|
CHECK(arena.num_blocks() == 1);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator construct template") {
|
|
ArenaAllocator arena;
|
|
|
|
SUBCASE("construct int") {
|
|
int *ptr = arena.construct<int>(42);
|
|
CHECK(ptr != nullptr);
|
|
CHECK(*ptr == 42);
|
|
}
|
|
|
|
SUBCASE("construct array") {
|
|
struct FixedString {
|
|
char data[16];
|
|
FixedString(const char *str) {
|
|
std::strncpy(data, str, sizeof(data) - 1);
|
|
data[sizeof(data) - 1] = '\0';
|
|
}
|
|
};
|
|
FixedString *ptr = arena.construct<FixedString>("hello world");
|
|
CHECK(ptr != nullptr);
|
|
CHECK(std::strcmp(ptr->data, "hello world") == 0);
|
|
}
|
|
|
|
SUBCASE("construct multiple objects") {
|
|
int *ptr1 = arena.construct<int>(10);
|
|
int *ptr2 = arena.construct<int>(20);
|
|
|
|
CHECK(ptr1 != ptr2);
|
|
CHECK(*ptr1 == 10);
|
|
CHECK(*ptr2 == 20);
|
|
}
|
|
|
|
SUBCASE("construct with multiple arguments") {
|
|
struct IntPair {
|
|
int first;
|
|
int second;
|
|
IntPair(int a, int b) : first(a), second(b) {}
|
|
};
|
|
auto *ptr = arena.construct<IntPair>(42, 24);
|
|
CHECK(ptr != nullptr);
|
|
CHECK(ptr->first == 42);
|
|
CHECK(ptr->second == 24);
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator reset functionality") {
|
|
ArenaAllocator arena;
|
|
|
|
arena.allocate(100);
|
|
arena.allocate(200);
|
|
size_t used_before = arena.used_bytes();
|
|
CHECK(used_before > 0);
|
|
|
|
arena.reset();
|
|
CHECK(arena.used_bytes() == 0);
|
|
CHECK(arena.num_blocks() >= 1);
|
|
|
|
void *ptr = arena.allocate(50);
|
|
CHECK(ptr != nullptr);
|
|
CHECK(arena.used_bytes() == 50);
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator reset memory leak test") {
|
|
ArenaAllocator arena(32); // Smaller initial size
|
|
|
|
// Force multiple blocks
|
|
arena.allocate(30); // First block (32 bytes)
|
|
CHECK(arena.num_blocks() == 1);
|
|
|
|
arena.allocate(30); // Should create second block (64 bytes due to doubling)
|
|
CHECK(arena.num_blocks() == 2);
|
|
|
|
arena.allocate(100); // Should create third block (128 bytes due to doubling,
|
|
// or larger for 100)
|
|
CHECK(arena.num_blocks() == 3);
|
|
|
|
size_t total_before = arena.total_allocated();
|
|
CHECK(total_before > 32);
|
|
|
|
arena.reset();
|
|
|
|
// After reset, only first block should remain (others freed to prevent memory
|
|
// leak)
|
|
CHECK(arena.num_blocks() == 1);
|
|
CHECK(arena.total_allocated() == 32); // Only first block size
|
|
CHECK(arena.used_bytes() == 0);
|
|
|
|
// Should be able to use the first block again
|
|
void *ptr = arena.allocate(20);
|
|
CHECK(ptr != nullptr);
|
|
CHECK(arena.used_bytes() == 20);
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator memory tracking") {
|
|
ArenaAllocator arena(512);
|
|
|
|
CHECK(arena.total_allocated() == 0);
|
|
CHECK(arena.used_bytes() == 0);
|
|
CHECK(arena.available_in_current_block() == 0);
|
|
|
|
arena.allocate(100);
|
|
CHECK(arena.used_bytes() >= 100);
|
|
CHECK(arena.available_in_current_block() <= 412);
|
|
|
|
arena.allocate(400);
|
|
CHECK(arena.num_blocks() == 1);
|
|
|
|
arena.allocate(50);
|
|
CHECK(arena.num_blocks() == 2);
|
|
CHECK(arena.total_allocated() >= 1024);
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator stress test") {
|
|
ArenaAllocator arena(1024);
|
|
|
|
SUBCASE("many small allocations") {
|
|
std::vector<void *> ptrs;
|
|
for (int i = 0; i < 1000; ++i) {
|
|
void *ptr = arena.allocate(8);
|
|
CHECK(ptr != nullptr);
|
|
ptrs.push_back(ptr);
|
|
}
|
|
|
|
for (size_t i = 1; i < ptrs.size(); ++i) {
|
|
CHECK(ptrs[i] != ptrs[i - 1]);
|
|
}
|
|
}
|
|
|
|
SUBCASE("alternating small and large allocations") {
|
|
for (int i = 0; i < 50; ++i) {
|
|
void *small_ptr = arena.allocate(16);
|
|
void *large_ptr = arena.allocate(256);
|
|
CHECK(small_ptr != nullptr);
|
|
CHECK(large_ptr != nullptr);
|
|
CHECK(small_ptr != large_ptr);
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator move semantics") {
|
|
ArenaAllocator arena1(512);
|
|
arena1.allocate(100);
|
|
size_t used_bytes = arena1.used_bytes();
|
|
size_t num_blocks = arena1.num_blocks();
|
|
|
|
ArenaAllocator arena2 = std::move(arena1);
|
|
CHECK(arena2.used_bytes() == used_bytes);
|
|
CHECK(arena2.num_blocks() == num_blocks);
|
|
|
|
void *ptr = arena2.allocate(50);
|
|
CHECK(ptr != nullptr);
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator edge cases") {
|
|
SUBCASE("very small block size") {
|
|
ArenaAllocator arena(16);
|
|
void *ptr = arena.allocate(8);
|
|
CHECK(ptr != nullptr);
|
|
CHECK(arena.num_blocks() == 1);
|
|
}
|
|
|
|
SUBCASE("allocation exactly block size") {
|
|
ArenaAllocator arena(64);
|
|
void *ptr = arena.allocate(64);
|
|
CHECK(ptr != nullptr);
|
|
CHECK(arena.num_blocks() == 1);
|
|
|
|
void *ptr2 = arena.allocate(1);
|
|
CHECK(ptr2 != nullptr);
|
|
CHECK(arena.num_blocks() == 2);
|
|
}
|
|
|
|
SUBCASE("multiple resets") {
|
|
ArenaAllocator arena;
|
|
for (int i = 0; i < 10; ++i) {
|
|
arena.allocate(100);
|
|
arena.reset();
|
|
CHECK(arena.used_bytes() == 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct TestPOD {
|
|
int value;
|
|
char name[16];
|
|
|
|
TestPOD(int v, const char *n) : value(v) {
|
|
std::strncpy(name, n, sizeof(name) - 1);
|
|
name[sizeof(name) - 1] = '\0';
|
|
}
|
|
};
|
|
|
|
TEST_CASE("ArenaAllocator with custom objects") {
|
|
ArenaAllocator arena;
|
|
|
|
TestPOD *obj1 = arena.construct<TestPOD>(42, "first");
|
|
TestPOD *obj2 = arena.construct<TestPOD>(84, "second");
|
|
|
|
CHECK(obj1 != nullptr);
|
|
CHECK(obj2 != nullptr);
|
|
CHECK(obj1 != obj2);
|
|
CHECK(obj1->value == 42);
|
|
CHECK(std::strcmp(obj1->name, "first") == 0);
|
|
CHECK(obj2->value == 84);
|
|
CHECK(std::strcmp(obj2->name, "second") == 0);
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator geometric growth policy") {
|
|
ArenaAllocator arena(64);
|
|
|
|
SUBCASE("normal geometric growth doubles size") {
|
|
arena.allocate(60); // Fill first block
|
|
size_t initial_total = arena.total_allocated();
|
|
|
|
arena.allocate(10); // Force new block
|
|
CHECK(arena.num_blocks() == 2);
|
|
CHECK(arena.total_allocated() == initial_total + 128); // 64 * 2 = 128
|
|
}
|
|
|
|
SUBCASE("large allocation creates appropriately sized block") {
|
|
arena.allocate(60); // Fill first block
|
|
size_t initial_total = arena.total_allocated();
|
|
|
|
arena.allocate(200); // Force large block
|
|
CHECK(arena.num_blocks() == 2);
|
|
CHECK(arena.total_allocated() >= initial_total + 200); // At least 200 bytes
|
|
}
|
|
|
|
SUBCASE("multiple growths maintain O(log n) blocks") {
|
|
size_t allocation_size = 32;
|
|
|
|
for (int i = 0; i < 10; ++i) {
|
|
arena.allocate(allocation_size);
|
|
}
|
|
|
|
// Should have grown logarithmically, not linearly
|
|
CHECK(arena.num_blocks() < 6); // Much less than 10
|
|
}
|
|
}
|
|
|
|
TEST_CASE("ArenaAllocator alignment edge cases") {
|
|
ArenaAllocator arena;
|
|
|
|
SUBCASE("unaligned then aligned allocation") {
|
|
void *ptr1 = arena.allocate(1, 1);
|
|
void *ptr2 = arena.allocate(8, 8);
|
|
|
|
CHECK(ptr1 != nullptr);
|
|
CHECK(ptr2 != nullptr);
|
|
CHECK(reinterpret_cast<uintptr_t>(ptr2) % 8 == 0);
|
|
}
|
|
|
|
SUBCASE("large alignment requirements") {
|
|
ArenaAllocator fresh_arena;
|
|
void *ptr = fresh_arena.allocate(1, 128);
|
|
CHECK(ptr != nullptr);
|
|
CHECK(reinterpret_cast<uintptr_t>(ptr) % 128 == 0);
|
|
}
|
|
}
|