#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN #include "arena_allocator.hpp" #include #include #include #include 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(ptr) % alignof(std::max_align_t) == 0); } SUBCASE("custom alignment") { void *ptr8 = arena.allocate(1, 8); CHECK(reinterpret_cast(ptr8) % 8 == 0); void *ptr16 = arena.allocate(1, 16); CHECK(reinterpret_cast(ptr16) % 16 == 0); void *ptr32 = arena.allocate(1, 32); CHECK(reinterpret_cast(ptr32) % 32 == 0); } SUBCASE("alignment with larger allocations") { ArenaAllocator fresh_arena; void *ptr = fresh_arena.allocate(100, 64); CHECK(reinterpret_cast(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(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("hello world"); CHECK(ptr != nullptr); CHECK(std::strcmp(ptr->data, "hello world") == 0); } SUBCASE("construct multiple objects") { int *ptr1 = arena.construct(10); int *ptr2 = arena.construct(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(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 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(42, "first"); TestPOD *obj2 = arena.construct(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(ptr2) % 8 == 0); } SUBCASE("large alignment requirements") { ArenaAllocator fresh_arena; void *ptr = fresh_arena.allocate(1, 128); CHECK(ptr != nullptr); CHECK(reinterpret_cast(ptr) % 128 == 0); } }